import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i0 from '@angular/core';
import { EventEmitter, Component, ChangeDetectionStrategy, Input, Output, Directive, Injectable, Optional, Self, ContentChild, ViewChild, NgModule } from '@angular/core';
import * as i2$1 from '@angular/forms';
import { FormsModule } from '@angular/forms';
import * as i2 from '@angular/material/progress-spinner';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import * as i4 from '@deja-js/component/v2/mouse-dragdrop';
import { MouseDragDropModule } from '@deja-js/component/v2/mouse-dragdrop';
import * as i1$1 from '@deja-js/component/core';
import { Destroy, KeyCodes } from '@deja-js/component/core';
import { filter, take, tap, delay, takeUntil, debounceTime, Subject, BehaviorSubject, map, mergeWith, switchMap, fromEvent, of, from, reduce, timer, ReplaySubject } from 'rxjs';
import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { Rect, Position, Directions, Size } from '@deja-js/component/core/graphics';

class DejaTileComponent extends Destroy {
    constructor(el, changeDetectorRef) {
        super();
        this.changeDetectorRef = changeDetectorRef;
        // eslint-disable-next-line @angular-eslint/no-output-native
        this.close = new EventEmitter();
        this.progressDiameter = 100;
        this.element = el.nativeElement;
        this.element.setAttribute('hidden', '0');
    }
    set tile(tile) {
        this._tile = tile;
        if (tile) {
            const toggleAttribute = (attribute, value) => {
                if (value) {
                    this.element.setAttribute(attribute, value.toString());
                }
                else {
                    this.element.removeAttribute(attribute);
                }
            };
            if (tile.fading) {
                this.element.setAttribute('fading', '0');
                this.changeDetectorRef.markForCheck();
            }
            tile.pixelBounds$.pipe(filter(bounds => !!bounds), take(1), filter(() => tile.fading), tap(() => {
                this.element.setAttribute('fading', '1');
                this.changeDetectorRef.markForCheck();
            }), delay(200), takeUntil(this.destroyed$)).subscribe(() => {
                this.element.removeAttribute('fading');
                this.changeDetectorRef.markForCheck();
            });
            tile.pixelBounds$.pipe(filter(bounds => !!bounds), takeUntil(this.destroyed$)).subscribe(bounds => {
                if (!tile.isHidden) {
                    this.element.removeAttribute('hidden');
                }
                this.element.style.left = `${bounds.left}px`;
                this.element.style.top = `${bounds.top}px`;
                this.element.style.width = `${bounds.width}px`;
                this.element.style.height = `${bounds.height}px`;
                this.progressDiameter = Math.min(100, Math.round(Math.max(bounds.width * 0.4, bounds.height * 0.4)));
                this.changeDetectorRef.markForCheck();
            });
            tile.pressed$.pipe(tap(value => toggleAttribute('pressed', value)), takeUntil(this.destroyed$)).subscribe(() => this.changeDetectorRef.markForCheck());
            tile.selected$.pipe(tap(value => toggleAttribute('selected', value)), takeUntil(this.destroyed$)).subscribe(() => this.changeDetectorRef.markForCheck());
            tile.dragging$.pipe(tap(value => toggleAttribute('drag', value)), takeUntil(this.destroyed$)).subscribe(() => this.changeDetectorRef.markForCheck());
            tile.dropping$.pipe(tap(value => toggleAttribute('drop', value)), takeUntil(this.destroyed$)).subscribe(() => this.changeDetectorRef.markForCheck());
            tile.cutted$.pipe(tap(value => toggleAttribute('cutted', value)), takeUntil(this.destroyed$)).subscribe(() => this.changeDetectorRef.markForCheck());
            tile.deleted$.pipe(tap(() => this.element.remove()), takeUntil(this.destroyed$)).subscribe(() => this.changeDetectorRef.markForCheck());
            const toggleHide$ = tile.hidden$.pipe(tap(value => toggleAttribute('hidden', value ? '1' : '2')));
            // Hide
            toggleHide$.pipe(debounceTime(1000), filter(value => value), tap(() => this.element.setAttribute('hidden', '0')), takeUntil(this.destroyed$)).subscribe(() => this.changeDetectorRef.markForCheck());
            // Show
            toggleHide$.pipe(debounceTime(1), filter(value => !value), tap(() => this.element.removeAttribute('hidden')), takeUntil(this.destroyed$)).subscribe(() => this.changeDetectorRef.markForCheck());
            // Refresh
            tile.refresh$.pipe(debounceTime(1), takeUntil(this.destroyed$)).subscribe(() => this.changeDetectorRef.markForCheck());
        }
    }
    get tile() {
        return this._tile;
    }
}
DejaTileComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTileComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
DejaTileComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.1.1", type: DejaTileComponent, selector: "deja-tile", inputs: { template: "template", tile: "tile" }, outputs: { close: "close" }, usesInheritance: true, ngImport: i0, template: "<ng-template [ngTemplateOutlet]=\"template\" [ngTemplateOutletContext]=\"{ $implicit: tile }\"></ng-template>\n<mat-progress-spinner *ngIf=\"tile && tile.isPending\" mode=\"indeterminate\" [diameter]=\"progressDiameter\" class=\"accent\"></mat-progress-spinner>\n", styles: [":host{display:block;position:absolute;transition-property:left,top,opacity;transition-duration:.5s;transition-timing-function:ease;opacity:1}:host[expanded]{transition-property:height}:host[cutted]{opacity:.5}:host[drag],:host[drop]{z-index:100}:host[drag]{opacity:.5;user-select:none;transition:none}:host[fading=\"0\"]{opacity:0;transition:none}:host[fading=\"1\"]{opacity:0}:host[hidden=\"0\"]{visibility:hidden;transition:none}:host[hidden=\"1\"]{opacity:0;transition:1s ease-in-out opacity}:host[hidden=\"2\"]{visibility:visible;opacity:0;transition:none}:host mat-progress-spinner{position:absolute;z-index:1;right:.2rem;bottom:.2rem;transform:translate(-60%,-60%)}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: i2.MatProgressSpinner, selector: "mat-progress-spinner, mat-spinner", inputs: ["color", "diameter", "strokeWidth", "mode", "value"], exportAs: ["matProgressSpinner"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTileComponent, decorators: [{
            type: Component,
            args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'deja-tile', template: "<ng-template [ngTemplateOutlet]=\"template\" [ngTemplateOutletContext]=\"{ $implicit: tile }\"></ng-template>\n<mat-progress-spinner *ngIf=\"tile && tile.isPending\" mode=\"indeterminate\" [diameter]=\"progressDiameter\" class=\"accent\"></mat-progress-spinner>\n", styles: [":host{display:block;position:absolute;transition-property:left,top,opacity;transition-duration:.5s;transition-timing-function:ease;opacity:1}:host[expanded]{transition-property:height}:host[cutted]{opacity:.5}:host[drag],:host[drop]{z-index:100}:host[drag]{opacity:.5;user-select:none;transition:none}:host[fading=\"0\"]{opacity:0;transition:none}:host[fading=\"1\"]{opacity:0}:host[hidden=\"0\"]{visibility:hidden;transition:none}:host[hidden=\"1\"]{opacity:0;transition:1s ease-in-out opacity}:host[hidden=\"2\"]{visibility:visible;opacity:0;transition:none}:host mat-progress-spinner{position:absolute;z-index:1;right:.2rem;bottom:.2rem;transform:translate(-60%,-60%)}\n"] }]
        }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { template: [{
                type: Input
            }], close: [{
                type: Output
            }], tile: [{
                type: Input
            }] } });

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */
class DejaTilePositionDirective {
    constructor(el) {
        this.element = el.nativeElement;
        this.element.style.display = 'none';
    }
    set bounds(rect) {
        if (rect) {
            const { left, top, width, height } = rect;
            this.element.style.left = `${left}px`;
            this.element.style.top = `${top}px`;
            this.element.style.width = `${width}px`;
            this.element.style.height = `${height}px`;
            this.element.style.display = 'block';
        }
        else {
            this.element.style.display = 'none';
        }
    }
}
DejaTilePositionDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTilePositionDirective, deps: [{ token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Directive });
DejaTilePositionDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "14.1.1", type: DejaTilePositionDirective, selector: "[deja-tile-position]", inputs: { bounds: "bounds" }, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTilePositionDirective, decorators: [{
            type: Directive,
            args: [{
                    selector: '[deja-tile-position]'
                }]
        }], ctorParameters: function () { return [{ type: i0.ElementRef }]; }, propDecorators: { bounds: [{
                type: Input
            }] } });

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */
class DejaTilesLayoutProvider extends Destroy {
    constructor(clipboardService) {
        super();
        this.clipboardService = clipboardService;
        this.refreshTiles$ = new Subject();
        this.ensureVisible$ = new Subject();
        this.ensureBounds$ = new Subject();
        this.dragging$ = new BehaviorSubject(false);
        this.dragSelection$ = new Subject();
        this.dragDropInfos$ = new Subject();
        this.selectionRect$ = new Subject();
        this.dragover$ = new Subject();
        this.dragleave$ = new Subject();
        this.deleteTiles$ = new Subject();
        this.designMode = false;
        this.layoutCompleted = new Subject();
        this.layoutChanged = new Subject();
        this.tilesAdded = new Subject();
        this.tilesDeleted = new Subject();
        this.selectionChanged = new Subject();
        this.contentAdding = new Subject();
        this.contentRemoving = new Subject();
        this._tileMinWidth = 10;
        this._tileMinWidthUnit = '%';
        this._tileMaxWidth = 100;
        this._tileMaxWidthUnit = '%';
        this._tileMinHeight = 10;
        this._tileMinHeightUnit = '%';
        this._tileMaxHeight = 100;
        this._tileMaxHeightUnit = '%';
        this._maxWidth = 100;
        this._maxWidthUnit = '%';
        this.tilesDic = new Map();
        this._targetBounds = new Rect();
        this.destination = new Rect();
        this.dragPageOffset = new Position();
        this.dragOriginalPosition = new Position();
        this.selectedIds = new Array();
        this.refreshTiles$.pipe(debounceTime(30), tap(() => {
            // Size the container
            this.container.style.width = '';
            this.container.style.height = '';
        }), delay(10), takeUntil(this.destroyed$)).subscribe(params => {
            const placeAtTheEnd = new Array();
            const containerBounds = this.container.getBoundingClientRect();
            if (params?.resetWidth || !this.hundredPercentWith) {
                this.hundredPercentWith = containerBounds.width;
            }
            let height = containerBounds.height;
            let width = containerBounds.width;
            let maxWidth = 0;
            let maxHeight = 0;
            const tiles = this.tiles || new Array();
            const selectedTileIds = new Array();
            tiles.forEach((tile) => {
                if (tile.percentBounds && !tile.percentBounds.isEmpty()) {
                    const bounds = this.getPixelBounds(tile.percentBounds);
                    if (bounds.bottom > maxWidth) {
                        maxWidth = bounds.right;
                    }
                    if (bounds.right > maxHeight) {
                        maxHeight = bounds.bottom;
                    }
                    if (!tile.isDragging) {
                        tile.pixelBounds = bounds;
                    }
                }
                else {
                    placeAtTheEnd.push(tile);
                }
                if (tile.isSelected && !tile.isHidden) {
                    selectedTileIds.push(tile.id);
                }
                if (tile.pixelBounds && tile.pixelBounds.bottom > height) {
                    height = tile.pixelBounds.bottom;
                }
            });
            let top = maxHeight;
            let left = 0;
            placeAtTheEnd.forEach(tile => {
                tile.percentBounds = tile.percentBounds || new Rect(this.getPercentSize(left), this.getPercentSize(top), 3 * this.getTileMinPercentWidth(), 3 * this.getTileMinPercentHeight());
                let pixelBounds = this.getPixelBounds(tile.percentBounds);
                if (pixelBounds.right > width) {
                    top = maxHeight;
                    left = 0;
                    tile.percentBounds.left = 0;
                    tile.percentBounds.top = this.getPercentSize(top);
                    pixelBounds = this.getPixelBounds(tile.percentBounds);
                }
                if (pixelBounds.bottom > maxHeight) {
                    maxHeight = pixelBounds.bottom;
                }
                tile.pixelBounds = this.getPixelBounds(tile.percentBounds);
                left += pixelBounds.width;
            });
            if (this.dragTarget) {
                const dragBounds = this.getPixelBounds(this.dragTarget);
                if (height <= dragBounds.bottom) {
                    height = dragBounds.bottom + dragBounds.height;
                }
                if (width <= dragBounds.right) {
                    width = dragBounds.right + dragBounds.width;
                }
            }
            const minHeight = this.getPixelSize(2 * this._tileMinHeight, this._tileMinHeightUnit);
            if (height < minHeight) {
                height = minHeight;
            }
            this.container.style.width = `${width}px`;
            this.container.style.height = `${height}px`;
            if (params?.ensureVisible) {
                this.ensureVisible$.next(params.ensureVisible);
            }
            if (params?.ensureBounds) {
                this.ensureBounds$.next(params.ensureBounds);
            }
            this.selectedTiles = selectedTileIds;
            const event = new CustomEvent('DejaTilesEvent', { cancelable: false });
            if (placeAtTheEnd.length > 0) {
                event.tiles = placeAtTheEnd;
                this.layoutChanged.next(event);
            }
            event.tiles = tiles;
            this.layoutCompleted.next(event);
        });
        const ensureTile$ = this.ensureVisible$.pipe(delay(1), map(id => this.tilesDic.get(id)), filter(tile => !!tile), map(tile => tile.percentBounds));
        this.ensureBounds$.pipe(mergeWith(ensureTile$), takeUntil(this.destroyed$)).subscribe(percentBounds => {
            const { left, right, top, bottom } = this.getPixelBounds(percentBounds);
            const findScrollContainer = (container) => {
                // eslint-disable-next-line no-loops/no-loops
                while (container && container.tagName !== 'DEJA-TILES') {
                    container = container.parentElement;
                }
                return container;
            };
            const scrollContainer = findScrollContainer(this.container);
            if (!scrollContainer) {
                return;
            }
            const containerBounds = this.container.getBoundingClientRect();
            const scrollBounds = scrollContainer.getBoundingClientRect();
            if (left + containerBounds.left < scrollBounds.bottom) {
                scrollContainer.scrollLeft += left + containerBounds.left - scrollBounds.bottom;
            }
            else if (right + containerBounds.left > scrollBounds.right) {
                scrollContainer.scrollLeft += right + containerBounds.left - scrollBounds.right;
            }
            if (top + containerBounds.top < scrollBounds.top) {
                scrollContainer.scrollTop += top + containerBounds.top - scrollBounds.top;
            }
            else if (bottom + containerBounds.top > scrollBounds.bottom) {
                scrollContainer.scrollTop += bottom + containerBounds.top - scrollBounds.bottom;
            }
        });
        this.dragSelection$.pipe(switchMap(dragSelection => {
            const mouseUp$ = fromEvent(this._container.ownerDocument, 'mouseup').pipe(tap(() => this.selectionRect$.next(null)));
            const mouseMove$ = fromEvent(this._container, 'mousemove');
            return mouseMove$.pipe(
            // eslint-disable-next-line rxjs/no-unsafe-takeuntil
            takeUntil(mouseUp$), filter(event => event.buttons === 1), tap(event => {
                const containerBounds = this._container.getBoundingClientRect();
                // Select all tiles between start position and current position
                dragSelection.selectedRect = Rect.fromPoints(dragSelection.startPosition, new Position(event.pageX - containerBounds.left, event.pageY - containerBounds.top));
                this.selectionRect$.next(dragSelection.selectedRect);
                const selection = this.hitTest(dragSelection.selectedRect);
                this.selectedTiles = selection.map(tile => tile.id);
            }));
        }), takeUntil(this.destroyed$)).subscribe();
        const leave$ = this.dragleave$;
        this.dragDropInfos$.pipe(switchMap(dragDropInfos => {
            const tiles = dragDropInfos && ((dragDropInfos.tiles?.length && dragDropInfos.tiles) || (dragDropInfos.currentTile && [dragDropInfos.currentTile]));
            if (!tiles) {
                return of(null);
            }
            const externalDrop = !dragDropInfos.tiles;
            const mouseMove$ = fromEvent(this._container, 'mousemove');
            const keyUp$ = fromEvent(this._container.ownerDocument, 'keyup');
            const escape$ = keyUp$.pipe(filter(event => event.code === KeyCodes.Escape));
            const mouseButtonReleased$ = mouseMove$.pipe(filter(event => event.buttons !== 1));
            const cancel$ = leave$.pipe(mergeWith(mouseButtonReleased$, escape$), tap(() => {
                this.removeTemporaryTile();
                this.cancelDrag(tiles);
            }));
            const mouseUp$ = fromEvent(this._container.ownerDocument, 'mouseup').pipe(tap(() => this.drop(tiles)));
            const kill$ = mouseUp$.pipe(mergeWith(cancel$));
            const dragover$ = this.dragover$.pipe(map(cursor => cursor.originalEvent));
            return mouseMove$.pipe(mergeWith(dragover$), 
            // eslint-disable-next-line rxjs/no-unsafe-takeuntil
            takeUntil(kill$), tap(event => {
                const containerBounds = this._container.getBoundingClientRect();
                const x = event.pageX - containerBounds.left;
                const y = event.pageY - containerBounds.top;
                if (!dragDropInfos.enabled) {
                    if (Math.abs(dragDropInfos.startX - x) >= 15 || Math.abs(dragDropInfos.startY - y) >= 15) {
                        if (externalDrop) {
                            // Allow drag and drop of new tiles from outside the component
                            const tempTile = dragDropInfos.currentTile;
                            // Clear current selection
                            this.selectedTiles = [tempTile.id];
                            let bounds = tempTile.percentBounds;
                            if (!bounds || bounds.isEmpty()) {
                                bounds = new Rect(0, 0, 15, 15);
                            }
                            const idealBounds = this.getFreePlace(new Rect(this.getPercentSize(x) - bounds.width / 2, this.getPercentSize(y) - bounds.height / 2, bounds.width, bounds.height));
                            dragDropInfos.startX = idealBounds.left - bounds.width / 2;
                            dragDropInfos.startY = idealBounds.top - bounds.height / 2;
                            tempTile.percentBounds = idealBounds;
                            tempTile.dragging$.next(true);
                            this._cursor = 'move';
                            this.tiles.push(tempTile);
                            this.tilesDic.set(tempTile.id, tempTile);
                            // Start tile drag and drop
                            this.dragging$.next(true);
                            dragDropInfos.enabled = true;
                            this.startDrag(tiles, this.getPixelSize(idealBounds.left + idealBounds.width / 2), this.getPixelSize(idealBounds.top + idealBounds.height / 2));
                        }
                        else {
                            // Start tile drag and drop
                            this.dragging$.next(true);
                            dragDropInfos.enabled = true;
                            this.startDrag(tiles, dragDropInfos.startX, dragDropInfos.startY);
                        }
                    }
                }
                else {
                    this.drag(tiles, x, y);
                }
            }));
        }), takeUntil(this.destroyed$)).subscribe();
        // Delete stream for clipboard
        this.deleteTiles$.pipe(takeUntil(this.destroyed$)).subscribe(tilesToDelete => this.deleteTiles(tilesToDelete));
    }
    set container(container) {
        this._container = container;
        if (this._container) {
            const leave$ = fromEvent(container, 'mouseleave');
            const mouseUp$ = fromEvent(container.ownerDocument, 'mouseup');
            const mouseEnter$ = fromEvent(container, 'mouseenter');
            // Cursor provider
            mouseEnter$.pipe(switchMap(() => {
                if (this.designMode) {
                    const leaveCursor$ = leave$.pipe(tap(() => this.container.style.cursor = ''));
                    const mouseMove$ = fromEvent(container, 'mousemove');
                    return mouseMove$.pipe(
                    // eslint-disable-next-line rxjs/no-unsafe-takeuntil
                    takeUntil(leaveCursor$), filter(event => !!this.container && event.buttons === 0), map(event => this._cursor = this.getCursorFromHtmlElement(event.pageX, event.pageY, event.target)));
                }
                else {
                    return of('');
                }
            }), takeUntil(this.destroyed$)).subscribe(cursor => this.container.style.cursor = cursor);
            mouseEnter$.pipe(switchMap(() => {
                const mouseDownEvent$ = fromEvent(container, 'mousedown');
                const mouseDown$ = mouseDownEvent$.pipe(filter(event => event.buttons === 1), filter(event => !this.isElementInsideDejaEditor(event.target)), map(event => ({ event: event, clickedTile: this.getTileComponentFromHTMLElement(event.target) })));
                // Pressed and selected tile observers
                return mouseDown$.pipe(
                // eslint-disable-next-line rxjs/no-unsafe-takeuntil
                takeUntil(leave$), switchMap(({ event, clickedTile }) => {
                    const target = event.target;
                    if (this.currentTile) {
                        this.currentTile.isPressed = false;
                    }
                    this.currentTile = clickedTile;
                    if (this.currentTile) {
                        this.currentTile.isPressed = true;
                        if (event.ctrlKey) {
                            // Multi-selection is available in design mode, selection on the mouse up
                        }
                        else {
                            if (!this.currentTile.isSelected || this._cursor !== 'move') {
                                this.selectedTiles = [this.currentTile.id];
                            }
                            if (this.designMode) {
                                const containerBounds = this._container.getBoundingClientRect();
                                const x = event.pageX - containerBounds.left;
                                const y = event.pageY - containerBounds.top;
                                this.dragDropInfos$.next({
                                    enabled: false,
                                    startX: x,
                                    startY: y,
                                    currentTile: this.currentTile,
                                    tiles: this.tiles.filter(tile => tile.isSelected)
                                });
                            }
                        }
                        return mouseUp$.pipe(take(1), filter(() => !!this.currentTile));
                    }
                    else if (target === this.container || target.parentElement === this.container) {
                        if (event.buttons === 1) {
                            // Start drag selection
                            const containerBounds = this._container.getBoundingClientRect();
                            this.dragSelection$.next({ startPosition: new Position(event.pageX - containerBounds.left, event.pageY - containerBounds.top), selectedRect: new Rect() });
                        }
                        // Unselect all tiles
                        if (this.currentTile) {
                            this.currentTile.isPressed = false;
                        }
                        this.selectedTiles = [];
                    }
                    return of(null);
                }));
            }), filter(mouseUpEvent => !!mouseUpEvent), takeUntil(this.destroyed$)).subscribe(mouseUpEvent => {
                if (this.currentTile.isPressed) {
                    this.currentTile.isPressed = false;
                    // Multi-selection
                    if (mouseUpEvent.ctrlKey) {
                        this.currentTile.isSelected = !this.currentTile.isSelected;
                        this.selectedTiles = this.tiles
                            .filter(tile => tile.isSelected)
                            .map(tile => tile.id);
                    }
                }
                if (this.designMode) {
                    this._cursor = this.getCursorFromHtmlElement(mouseUpEvent.pageX, mouseUpEvent.pageY, mouseUpEvent.target);
                    this.container.style.cursor = this._cursor;
                }
                this.currentTile = undefined;
            });
        }
    }
    get container() {
        return this._container;
    }
    set tiles(tiles) {
        this._tiles = tiles;
        this.tilesDic = this.tiles.reduce((dic, t) => dic.set(t.id, t), new Map());
        this.refreshTiles$.next({ resetWidth: true });
    }
    get tiles() {
        return this._tiles || (this._tiles = new Array());
    }
    set selectedTiles(selectedIds) {
        const selectedTiles = new Array();
        let raiseEvent = false;
        const idsDic = selectedIds.reduce((set, id) => set.add(id), new Set());
        const previousIdsDic = this.selectedIds.reduce((set, id) => set.add(id), new Set());
        if (this.tiles?.length) {
            this.tiles.forEach((tile) => {
                if (idsDic.has(tile.id) !== previousIdsDic.has(tile.id)) {
                    raiseEvent = true;
                }
                tile.isSelected = idsDic.has(tile.id);
                if (tile.isSelected) {
                    selectedTiles.push(tile);
                }
            });
        }
        else {
            raiseEvent = this.selectedIds.length > 0;
        }
        this.selectedIds = selectedIds;
        if (raiseEvent) {
            const event = new CustomEvent('DejaTilesAddEvent', { cancelable: false });
            event.tiles = selectedTiles;
            this.selectionChanged.next(event);
        }
    }
    set tileMinWidth(value) {
        this.extractValueAndUnit('_tileMinWidth', value);
    }
    set tileMaxWidth(value) {
        this.extractValueAndUnit('_tileMaxWidth', value);
    }
    set tileMinHeight(value) {
        this.extractValueAndUnit('_tileMinHeight', value);
    }
    set tileMaxHeight(value) {
        this.extractValueAndUnit('_tileMaxHeight', value);
    }
    set maxWidth(value) {
        this.extractValueAndUnit('_maxWidth', value);
    }
    get targetBounds() {
        return this._targetBounds;
    }
    set targetBounds(targetBounds) {
        this._targetBounds = targetBounds;
        if (targetBounds) {
            this.selectionRect$.next(new Rect({
                height: this.getPixelSize(targetBounds.height || 0),
                left: this.getPixelSize(targetBounds.left || 0),
                top: this.getPixelSize(targetBounds.top || 0),
                width: this.getPixelSize(targetBounds.width || 0)
            }));
        }
        else {
            this.selectionRect$.next(null);
        }
    }
    copySelection() {
        const selectedTiles = this.tiles.filter(tile => tile.isSelected);
        if (selectedTiles.length) {
            this.copyTiles(selectedTiles, false);
        }
        return selectedTiles;
    }
    cutSelection() {
        const selectedTiles = this.tiles.filter(tile => tile.isSelected);
        if (selectedTiles.length) {
            this.copyTiles(selectedTiles, true);
        }
        return selectedTiles;
    }
    deleteSelection() {
        const selectedTiles = this.tiles.filter(tile => tile.isSelected);
        if (selectedTiles.length) {
            this.removeTiles(selectedTiles.map(tile => tile.id));
        }
        return selectedTiles;
    }
    paste() {
        if (!this.clipboardService || !this.clipboardService.isAvailable('tiles')) {
            return new Array();
        }
        const sourceTiles = this.clipboardService.get('tiles');
        // Unselect all tiles
        this.tiles.forEach(tile => tile.isSelected = false);
        // Get max rectangle
        let bounds;
        sourceTiles.forEach(tile => {
            bounds = bounds ? Rect.union(bounds, tile.percentBounds) : new Rect(tile.percentBounds);
        });
        const targetBounds = this.getFreePlace(new Rect(0, 0, bounds.width, bounds.height));
        const newTiles = sourceTiles.map(tile => {
            const newTile = tile.clone();
            newTile.percentBounds = new Rect(targetBounds.left + tile.percentBounds.left - bounds.left, targetBounds.top + tile.percentBounds.top - bounds.top, tile.percentBounds.width, tile.percentBounds.height);
            newTile.isSelected = true;
            return newTile;
        });
        this.addTiles(newTiles);
        return newTiles;
    }
    // eslint-disable-next-line @typescript-eslint/naming-convention
    getTileElementFromHTMLElement(element) {
        let tileElement = element;
        // eslint-disable-next-line no-loops/no-loops
        while (tileElement && tileElement.tagName !== 'DEJA-TILE') {
            tileElement = tileElement.parentElement;
            if (tileElement === this.container) {
                return undefined;
            }
        }
        if (tileElement?.parentElement !== this.container) {
            return undefined; // For nested tiles components
        }
        return tileElement;
    }
    isElementInsideDejaEditor(element) {
        let tileElement = element;
        // eslint-disable-next-line no-loops/no-loops
        while (tileElement && tileElement !== this.container) {
            if (tileElement.tagName === 'DEJA-EDITOR') {
                return true;
            }
            tileElement = tileElement.parentElement;
        }
        return false;
    }
    // eslint-disable-next-line @typescript-eslint/naming-convention
    getTileComponentFromHTMLElement(element) {
        const tileElement = this.getTileElementFromHTMLElement(element);
        return tileElement && this.tilesDic.get(tileElement.id);
    }
    deleteTiles(tilesToDelete) {
        if (!tilesToDelete || tilesToDelete.length === 0) {
            return;
        }
        // For event after removed finished
        const event = new CustomEvent('DejaTilesDeletedEvent', { cancelable: false });
        event.tiles = tilesToDelete;
        tilesToDelete.forEach(tile => {
            this.tilesDic.delete(tile.id);
            tile.delete();
        });
        let index = this.tiles.length;
        // eslint-disable-next-line no-loops/no-loops
        while (--index >= 0) {
            const tile = this.tiles[index];
            if (!this.tilesDic.has(tile.id)) {
                this.tiles.splice(index, 1);
            }
        }
        this.refreshTiles$.next({ resetWidth: true });
        event.tiles = this.tiles;
        this.tilesDeleted.next(event);
    }
    removeTiles(tileIdsToRemove) {
        if (!tileIdsToRemove || tileIdsToRemove.length === 0) {
            return;
        }
        const tilesToRemove = tileIdsToRemove.map(id => this.tilesDic.get(id));
        // Delete selected tiles components
        tilesToRemove.forEach(tile => {
            tile.isHidden = true;
        });
        const event = new CustomEvent('DejaTilesRemoveEvent', { cancelable: true });
        event.tiles = this.tiles;
        event.removed = tilesToRemove;
        event.cancel$ = new Subject();
        const cancelSubscription = event.cancel$.pipe(take(1), takeUntil(this.destroyed$)).subscribe(value => {
            if (value) {
                tilesToRemove.forEach(tile => tile.isHidden = false);
            }
            else {
                this.deleteTiles(tilesToRemove);
            }
        });
        // Forward event
        this.contentRemoving.next(event);
        // Remove immediately
        if (!event.defaultPrevented) {
            cancelSubscription.unsubscribe();
            this.deleteTiles(tilesToRemove);
        }
    }
    getFreePlace(idealBounds) {
        const freePlaces = new Array();
        const tiles = this.tiles.filter(t => !t.isDragging);
        let maxHeight = 0;
        tiles.forEach(t => {
            if (t.percentBounds.bottom > maxHeight) {
                maxHeight = t.percentBounds.bottom;
            }
        });
        // eslint-disable-next-line no-loops/no-loops
        for (let x = 0; x <= this._maxWidth - idealBounds.width; x += this._tileMinWidth) {
            // eslint-disable-next-line no-loops/no-loops
            for (let y = 0; y <= maxHeight - idealBounds.height; y += this._tileMinHeight) {
                const currentBounds = new Rect(x, y, idealBounds.width, idealBounds.height);
                if (!tiles.find(t => t.percentBounds.intersectWith(currentBounds))) {
                    freePlaces.push(currentBounds);
                }
            }
        }
        if (freePlaces.length > 0) {
            // add at the nearest free place
            freePlaces.sort((bounds1, bounds2) => {
                const calcDistance = (bounds) => Math.min(Math.abs(bounds.left - idealBounds.left), Math.abs(bounds.right - idealBounds.right)) + 200 * Math.min(Math.abs(bounds.top - idealBounds.top), Math.abs(bounds.bottom - idealBounds.bottom));
                return calcDistance(bounds1) - calcDistance(bounds2);
            });
            return freePlaces[0];
        }
        // Add at the end
        return new Rect(0, maxHeight, idealBounds.width, idealBounds.height);
    }
    moveTile(id, bounds) {
        const tile = this.tiles.find(t => t.id === id);
        if (tile) {
            tile.percentBounds = bounds;
            this.refreshTiles$.next(null);
        }
    }
    hitTest(pixelBounds) {
        const percentBounds = new Rect(this.getPercentSize(pixelBounds.left), this.getPercentSize(pixelBounds.top), this.getPercentSize(pixelBounds.width), this.getPercentSize(pixelBounds.height));
        return this.tiles.filter(t => t.percentBounds.intersectWith(percentBounds));
    }
    getPercentSize(value) {
        return Math.round(value * 100 / this.hundredPercentWith);
    }
    // Drag and drop from outside provider
    dragEnter(dragContext, dragCursor) {
        if (!this.designMode || !this._container) {
            return false;
        }
        const tile = dragContext.tile;
        if (!tile) {
            return false;
        }
        const containerBounds = this._container.getBoundingClientRect();
        const { pageX, pageY } = dragCursor.originalEvent;
        const x = pageX - containerBounds.left;
        const y = pageY - containerBounds.top;
        // Create a temporary tile for drag and drop
        const tempTile = tile.clone();
        tempTile.isTemporary = true;
        this.dragDropInfos$.next({
            enabled: false,
            startX: x,
            startY: y,
            currentTile: tempTile
        });
        return true;
    }
    startDrag(tiles, pageX, pageY) {
        // Save layout
        const savedLayout = this.saveLayout();
        // Bring all tiles together
        let targetBounds;
        tiles.forEach(tile => {
            targetBounds = targetBounds ? Rect.union(targetBounds, tile.percentBounds) : tile.percentBounds;
            tile.isDragging = true;
        });
        this.dragRelativePosition = new Map();
        tiles.forEach(tile => this.dragRelativePosition.set(tile.id, new Position(tile.percentBounds.left - targetBounds.left, tile.percentBounds.top - targetBounds.top)));
        this.dragPageOffset = new Position(pageX, pageY);
        this.dragOriginalPosition = new Position(targetBounds.left, targetBounds.top);
        this.targetBounds = savedLayout.targetBounds = savedLayout.validBounds = targetBounds;
        this.originalLayout = savedLayout;
        this.validLayout = undefined;
    }
    cancelDrag(tiles) {
        this.clearMoveTimer();
        from(tiles).pipe(filter(tile => !!tile), takeUntil(this.destroyed$)).subscribe(tile => {
            tile.isDragging = false;
            tile.isDropping = true;
        });
        // Restore original layout
        if (this.originalLayout) {
            this.restoreLayout(this.originalLayout);
        }
        this.endDrag();
    }
    drop(tiles) {
        let changed;
        this.clearMoveTimer();
        if (this.validLayout) {
            this.restoreLayout(this.validLayout);
            if (this._cursor !== 'move') {
                // Only one tile can be resized at time
                const tile = tiles[0];
                tile.percentBounds = new Rect(this.validLayout.validBounds);
                tile.isDragging = false;
            }
            else {
                from(tiles).pipe(filter(tile => !!tile), tap(tile => {
                    const left = this.validLayout.validBounds.left + this.dragRelativePosition.get(tile.id).left;
                    const top = this.validLayout.validBounds.top + this.dragRelativePosition.get(tile.id).top;
                    tile.percentBounds = new Rect(left, top, tile.percentBounds.width, tile.percentBounds.height);
                    tile.isDragging = false;
                    tile.isDropping = true;
                    if (tile.isTemporary) {
                        this.tilesDic.delete(tile.id);
                        tile.makeId();
                        this.tilesDic.set(tile.id, tile);
                        this.addTiles([tile]);
                    }
                }), delay(1000), takeUntil(this.destroyed$)).subscribe(tile => tile.isDropping = false);
            }
            changed = this.tiles.filter(t => {
                const layoutInfo = this.originalLayout[t.id];
                return !Rect.equals(t.percentBounds, layoutInfo?.bounds);
            });
            this.endDrag();
        }
        else {
            this.removeTemporaryTile();
            this.cancelDrag(tiles);
        }
        if (changed) {
            const event = new CustomEvent('DejaTilesEvent', { cancelable: true });
            event.tiles = this.tiles;
            this.layoutChanged.next(event);
        }
        return changed;
    }
    endDrag() {
        this.originalLayout = undefined;
        this.validLayout = undefined;
        this.targetBounds = undefined;
        this.dragging$.next(false);
        this.dragDropInfos$.next(null);
        this.dragTarget = undefined;
        this.copyTiles(null);
        this.refreshTiles$.next(null);
    }
    drag(tiles, pageX, pageY) {
        // Search related coords
        const offset = new Position(pageX - this.dragPageOffset.left, pageY - this.dragPageOffset.top);
        const offsetLeft = offset.left + this.getPixelSize(this.dragOriginalPosition.left);
        const offsetTop = offset.top + this.getPixelSize(this.dragOriginalPosition.top);
        const sizeMin = this.getTileMinPixelSize();
        const sizeMax = this.getTileMaxPixelSize();
        if (this._cursor !== 'move') {
            // Only one tile can be resized at time
            const tile = tiles[0];
            const bounds = this.getPixelBounds(tile.percentBounds);
            const offsetRight = offsetLeft + bounds.width;
            const offsetBottom = offsetTop + bounds.height;
            const right = bounds.right;
            const bottom = bounds.bottom;
            switch (this._cursor) {
                case 'nw-resize':
                    bounds.left = Math.max(Math.min(offsetLeft, bounds.right - sizeMin.width), bounds.right - sizeMax.width);
                    bounds.right = right;
                    bounds.top = Math.max(Math.min(offsetTop, bounds.bottom - sizeMin.height), bounds.bottom - sizeMax.height);
                    bounds.bottom = bottom;
                    this.size(tile, new Position(offsetLeft, offsetTop), Directions.left + Directions.top);
                    break;
                case 'sw-resize':
                    bounds.left = Math.max(Math.min(offsetLeft, bounds.right - sizeMin.width), bounds.right - sizeMax.width);
                    bounds.right = right;
                    bounds.bottom = Math.max(Math.min(offsetBottom, bounds.top + sizeMax.height), bounds.top + sizeMin.height);
                    this.size(tile, new Position(offsetLeft, offsetBottom), Directions.left + Directions.bottom);
                    break;
                case 'w-resize':
                    bounds.left = Math.max(Math.min(offsetLeft, bounds.right - sizeMin.width), bounds.right - sizeMax.width);
                    bounds.right = right;
                    this.size(tile, new Position(offsetLeft, 0), Directions.left);
                    break;
                case 'ne-resize':
                    bounds.right = Math.max(Math.min(offsetRight, bounds.left + sizeMax.width), bounds.left + sizeMin.width);
                    bounds.top = Math.max(Math.min(offsetTop, bounds.bottom - sizeMin.height), bounds.bottom - sizeMax.height);
                    bounds.bottom = bottom;
                    this.size(tile, new Position(offsetRight, offsetTop), Directions.right + Directions.top);
                    break;
                case 'se-resize':
                    bounds.right = Math.max(Math.min(offsetRight, bounds.left + sizeMax.width), bounds.left + sizeMin.width);
                    bounds.bottom = Math.max(Math.min(offsetBottom, bounds.top + sizeMax.height), bounds.top + sizeMin.height);
                    this.size(tile, new Position(offsetRight, offsetBottom), Directions.right + Directions.bottom);
                    break;
                case 'e-resize':
                    bounds.right = Math.max(Math.min(offsetRight, bounds.left + sizeMax.width), bounds.left + sizeMin.width);
                    this.size(tile, new Position(offsetRight, 0), Directions.right);
                    break;
                case 'n-resize':
                    bounds.top = Math.max(Math.min(offsetTop, bounds.bottom - sizeMin.height), bounds.bottom - sizeMax.height);
                    bounds.bottom = bottom;
                    this.size(tile, new Position(0, offsetTop), Directions.top);
                    break;
                case 's-resize':
                    bounds.bottom = Math.max(Math.min(offsetBottom, bounds.top + sizeMax.height), bounds.top + sizeMin.height);
                    this.size(tile, new Position(0, offsetBottom), Directions.bottom);
                    break;
                default:
                // throw new Error('Invalid direction');
            }
            tile.pixelBounds = bounds;
        }
        else {
            tiles.forEach(tile => {
                tile.pixelBounds = new Rect(offsetLeft + this.getPixelSize(this.dragRelativePosition.get(tile.id).left), offsetTop + this.getPixelSize(this.dragRelativePosition.get(tile.id).top), this.getPixelSize(tile.percentBounds.width), this.getPixelSize(tile.percentBounds.height));
            });
            // Assign new drag and drop rectangle
            this.dragTarget = new Rect(this.getPercentSize(offsetLeft), this.getPercentSize(offsetTop), this.targetBounds.width, this.targetBounds.height);
            this.move();
        }
    }
    addTiles(newTiles) {
        if (!newTiles || newTiles.length === 0) {
            return;
        }
        newTiles.forEach(newTile => {
            if (!this.tiles.find(t => t.id === newTile.id)) {
                if (this.tiles.find(t => newTile.percentBounds && t.percentBounds?.intersectWith(newTile.percentBounds))) {
                    newTile.percentBounds = undefined;
                }
                if (!newTile.percentBounds) {
                    newTile.percentBounds = this.getFreePlace(newTile.idealBounds);
                }
                this.tiles.push(newTile);
                this.tilesDic.set(newTile.id, newTile);
            }
        });
        const event = new CustomEvent('DejaTilesAddEvent', { cancelable: true });
        event.tiles = this.tiles;
        event.added = newTiles;
        event.cancel$ = new Subject();
        // Delete provider if cut operation
        const deleteSourceProvider$ = this.clipboardService?.get('tiles-provider');
        // Hide originals if cut
        let sourceTiles;
        if (deleteSourceProvider$) {
            sourceTiles = this.clipboardService.get('tiles');
            sourceTiles.forEach(tile => {
                tile.isHidden = true;
            });
        }
        const deleteSourceTiles = () => {
            if (sourceTiles) {
                deleteSourceProvider$.next(sourceTiles);
                this.clipboardService.clear();
            }
        };
        const validateNewTiles = (tiles) => {
            // Remove original tiles if cut operation
            deleteSourceTiles();
            const e = new CustomEvent('DejaTileAddedEvent', { cancelable: false });
            e.tiles = this.tiles;
            e.added = tiles;
            this.tilesAdded.next(e);
        };
        // Get total rectangle
        let bounds;
        newTiles.forEach(tile => {
            bounds = bounds ? Rect.union(bounds, tile.percentBounds) : new Rect(tile.percentBounds);
        });
        this.refreshTiles$.next({ ensureBounds: bounds });
        this.contentAdding.next(event);
        if (!event.defaultPrevented) {
            // Add immediately
            validateNewTiles(newTiles);
        }
        else {
            event.cancel$.pipe(take(1), filter(value => !value), takeUntil(this.destroyed$)).subscribe(validateNewTiles);
            event.cancel$.pipe(take(1), filter(value => !!value), switchMap(() => {
                // Reshow original tiles if cut operation
                if (sourceTiles) {
                    sourceTiles.forEach(tile => {
                        tile.isHidden = false;
                        tile.isCutted = true;
                    });
                }
                // Canceled, hide and remove added after effect
                return from(newTiles);
            }), tap(tile => tile.isHidden = true), delay(1000), 
            // eslint-disable-next-line @typescript-eslint/no-unsafe-return
            reduce((acc, cur) => [...acc, cur], new Array()), take(1), takeUntil(this.destroyed$)).subscribe(tiles => this.deleteTiles(tiles));
        }
    }
    size(tile, pixelPos, directions) {
        // Calc new tile bounds
        const percentPos = new Position(this.getPercentSize(pixelPos.left), this.getPercentSize(pixelPos.top));
        const dragBounds = tile.percentBounds.clone();
        const newTargetBounds = tile.percentBounds.clone();
        let minWidth;
        let minHeight;
        let maxWidth;
        let maxHeight;
        // eslint-disable-next-line no-bitwise
        if (directions & Directions.left) {
            minWidth = this.getTileMinPercentWidth();
            maxWidth = this.getTileMaxPercentWidth();
            const dLeft = percentPos.left;
            const tLeft = dragBounds.left < dLeft ? minWidth * Math.ceil(dLeft / minWidth) : minWidth * Math.floor(dLeft / minWidth);
            const tWidth = Math.min(maxWidth, Math.max(minWidth, newTargetBounds.right - tLeft));
            dragBounds.width = dragBounds.right - dLeft;
            dragBounds.left = dLeft;
            newTargetBounds.left = newTargetBounds.right - tWidth;
            newTargetBounds.width = tWidth;
        }
        // eslint-disable-next-line no-bitwise
        if (directions & Directions.right) {
            minWidth = minWidth || this.getTileMinPercentWidth();
            maxWidth = maxWidth || this.getTileMaxPercentWidth();
            const dRight = percentPos.left;
            const tRight = dragBounds.right < dRight ? minWidth * Math.ceil(dRight / minWidth) : minWidth * Math.floor(dRight / minWidth);
            dragBounds.width = dRight - dragBounds.left;
            newTargetBounds.width = Math.min(maxWidth, Math.max(minWidth, tRight - newTargetBounds.left));
        }
        // eslint-disable-next-line no-bitwise
        if (directions & Directions.top) {
            minHeight = this.getTileMinPercentHeight();
            maxHeight = this.getTileMaxPercentHeight();
            const dTop = percentPos.top;
            const tTop = dragBounds.top < dTop ? minHeight * Math.ceil(dTop / minHeight) : minHeight * Math.floor(dTop / minHeight);
            const tHeight = Math.min(maxHeight, Math.max(minHeight, newTargetBounds.bottom - tTop));
            dragBounds.height = dragBounds.bottom - dTop;
            dragBounds.top = dTop;
            newTargetBounds.top = newTargetBounds.bottom - tHeight;
            newTargetBounds.height = tHeight;
        }
        // eslint-disable-next-line no-bitwise
        if (directions & Directions.bottom) {
            minHeight = minHeight || this.getTileMinPercentHeight();
            maxHeight = maxHeight || this.getTileMaxPercentHeight();
            const dBottom = percentPos.top;
            const tBottom = dragBounds.bottom < dBottom ? minHeight * Math.ceil(dBottom / minHeight) : minHeight * Math.floor(dBottom / minHeight);
            dragBounds.height = dBottom - dragBounds.top;
            newTargetBounds.height = Math.min(maxHeight, Math.max(minHeight, tBottom - newTargetBounds.top));
        }
        if (Rect.equals(newTargetBounds, this.destination)) {
            // Nothing change, wait for timers
            return;
        }
        // Restore the original layout before moving something
        this.restoreLayout(this.originalLayout);
        this.destination = newTargetBounds.clone();
        // Check if location is free without pushing tiles
        const result = this.tiles.find(t => !t.isDragging && t.percentBounds.intersectWith(newTargetBounds));
        if (!result) {
            this.targetBounds = newTargetBounds;
            // Save layout
            this.validLayout = this.saveLayout();
            this.validLayout.targetBounds = this.validLayout.validBounds = newTargetBounds;
            this.refreshTiles$.next(null);
        }
        else if (newTargetBounds) {
            // Location must be freed
            // Ensure new destination
            const ensureBounds = this.ensureTarget(newTargetBounds, dragBounds, directions);
            if (ensureBounds) {
                this.targetBounds = ensureBounds;
                this.validLayout = this.saveLayout();
                this.validLayout.targetBounds = newTargetBounds;
                this.validLayout.validBounds = ensureBounds;
                this.refreshTiles$.next(null);
            }
        }
    }
    clearMoveTimer() {
        if (this.moveTimOut) {
            this.moveTimOut.unsubscribe();
            this.moveTimOut = undefined;
        }
    }
    move() {
        const minWidth = this.getTileMinPercentWidth();
        const minHeight = this.getTileMinPercentHeight();
        // Search a new target
        let newTargetBounds = this.ensureContainer(new Rect(minWidth * Math.round(this.dragTarget.left / minWidth), minHeight * Math.round(this.dragTarget.top / minHeight), this.dragTarget.width, this.dragTarget.height));
        if (this.lastTargetBounds && Math.abs(newTargetBounds.left - this.lastTargetBounds.left) < minWidth && Math.abs(newTargetBounds.top - this.lastTargetBounds.top) < minHeight) {
            // Nothing change, wait for timers
            return;
        }
        this.lastTargetBounds = newTargetBounds;
        // Check if location is free without pushing tiles
        const maxOverlaps = this.tiles
            .filter(tile => !tile.isDragging)
            .reduce((overlaps, tile) => {
            const overlapsInfos = Rect.overlapInfos(tile.percentBounds, newTargetBounds);
            return Math.max(Math.min(overlapsInfos.height, overlapsInfos.width), overlaps);
        }, 0);
        // Restore the original layout before moving something if the user move back in an empty original position
        if (!this.tiles.find(t => {
            const config = this.originalLayout[t.id];
            return config?.bounds.intersectWith(newTargetBounds);
        })) {
            this.restoreLayout(this.originalLayout);
        }
        if (maxOverlaps === 0) {
            this.clearMoveTimer();
            this.targetBounds = newTargetBounds.clone();
            this.destination = newTargetBounds.clone();
            // Save layout
            this.validLayout = this.saveLayout();
            this.validLayout.targetBounds = this.validLayout.validBounds = newTargetBounds;
            this.refreshTiles$.next(null);
        }
        else {
            if (!Rect.equals(this.targetBounds, newTargetBounds)) {
                this.clearMoveTimer();
            }
            if (maxOverlaps > 3) {
                const timerBounds = newTargetBounds.clone();
                this.moveTimOut = timer(500).pipe(take(1), takeUntil(this.destroyed$)).subscribe(() => {
                    // Restore the original layout before moving something
                    this.restoreLayout(this.originalLayout);
                    this.destination = timerBounds;
                    // Ensure new destination
                    const ensureBounds = this.ensureTarget(timerBounds, this.dragTarget, Directions.all);
                    if (ensureBounds) {
                        this.targetBounds = ensureBounds;
                        this.validLayout = this.saveLayout();
                        this.validLayout.targetBounds = timerBounds;
                        this.validLayout.validBounds = ensureBounds;
                        this.refreshTiles$.next(null);
                    }
                });
            }
            // Find best location between last valid bounds and current bounds
            const freePlaces = new Array();
            const tiles = this.tiles.filter(t => !t.isDragging);
            // eslint-disable-next-line no-loops/no-loops
            for (let x = Math.min(this.targetBounds.left, newTargetBounds.left); x <= Math.max(this.targetBounds.left, newTargetBounds.left); x++) {
                // eslint-disable-next-line no-loops/no-loops
                for (let y = Math.min(this.targetBounds.top, newTargetBounds.top); y <= Math.max(this.targetBounds.top, newTargetBounds.top); y++) {
                    const currentBounds = new Rect(x, y, newTargetBounds.width, newTargetBounds.height);
                    if (!tiles.find(t => t.percentBounds.intersectWith(currentBounds))) {
                        freePlaces.push(currentBounds);
                    }
                }
            }
            if (freePlaces.length > 0) {
                // add at the nearest free place
                freePlaces.sort((bounds1, bounds2) => {
                    const calcDistance = (bounds) => Math.min(Math.abs(bounds.left - newTargetBounds.left), Math.abs(bounds.right - newTargetBounds.right)) + 200 * Math.min(Math.abs(bounds.top - newTargetBounds.top), Math.abs(bounds.bottom - newTargetBounds.bottom));
                    return calcDistance(bounds1) - calcDistance(bounds2);
                });
                newTargetBounds = freePlaces[0];
                this.targetBounds = newTargetBounds.clone();
                this.destination = newTargetBounds.clone();
                // Save layout
                this.validLayout = this.saveLayout();
                this.validLayout.targetBounds = this.validLayout.validBounds = newTargetBounds;
                this.refreshTiles$.next(null);
            }
        }
    }
    // Ensure that the specified bounds are inside the tiles area. Return the corrected rectangle.
    ensureContainer(percentBounds) {
        if (percentBounds.left < 0) {
            percentBounds = percentBounds.offset(-percentBounds.left, 0);
        }
        if (percentBounds.top < 0) {
            percentBounds = percentBounds.offset(0, -percentBounds.top);
        }
        const maxPercentWidth = this.getMaxPercentWidth();
        if (maxPercentWidth && percentBounds.right > maxPercentWidth) {
            percentBounds = percentBounds.offset(maxPercentWidth - percentBounds.right, 0);
        }
        const maxPercentHeight = this.getMaxPercentHeight();
        if (maxPercentHeight && percentBounds.bottom > maxPercentHeight) {
            percentBounds = percentBounds.offset(0, maxPercentHeight - percentBounds.bottom);
        }
        return percentBounds;
    }
    // Ensure that a tile can be dropped at the specified bounds. Return the corrected rectangle.
    ensureTarget(bounds, effectiveBounds, directions, originalBounds, retryCount = 10) {
        if (!effectiveBounds) {
            effectiveBounds = bounds;
        }
        // Backup bounds
        if (!originalBounds) {
            originalBounds = bounds.clone();
        }
        if (!directions) {
            directions = Directions.all;
        }
        const leftTilesToPush = new Array();
        const rightTilesToPush = new Array();
        const topTilesToPush = new Array();
        const bottomTilesToPush = new Array();
        // eslint-disable-next-line no-loops/no-loops
        const swap = this.tiles
            .filter(tile => !tile.isDragging && tile.percentBounds.intersectWith(bounds))
            .find(tile => {
            const swapTargetRect = new Rect(this.dragOriginalPosition.left, this.dragOriginalPosition.top, bounds.width, bounds.height);
            if (Math.abs(tile.percentBounds.left - effectiveBounds.left) < 1 &&
                Math.abs(tile.percentBounds.top - effectiveBounds.top) < 1 &&
                Math.abs(tile.percentBounds.width - effectiveBounds.width) < 1 &&
                Math.abs(tile.percentBounds.height - effectiveBounds.height) < 1 &&
                (Math.abs(effectiveBounds.left - swapTargetRect.right) <= 4 || Math.abs(effectiveBounds.right - swapTargetRect.left) <= 4 || Math.abs(effectiveBounds.top - swapTargetRect.bottom) <= 4 || Math.abs(effectiveBounds.bottom - swapTargetRect.top) <= 4)) {
                // swap
                tile.percentBounds = swapTargetRect;
                return true;
            }
            else {
                const hol = tile.percentBounds.left - effectiveBounds.left; // Ce qui depasse a gauche
                const hor = effectiveBounds.right - tile.percentBounds.right; // Ce qui depasse a droite
                const vot = tile.percentBounds.top - effectiveBounds.top; // Ce qui depasse en haut
                const vob = effectiveBounds.bottom - tile.percentBounds.bottom; // Ce qui depasse en bas
                const hoe = Math.max(0, Math.min(tile.percentBounds.right, effectiveBounds.right) - Math.max(tile.percentBounds.left, effectiveBounds.left)) / Math.min(tile.percentBounds.width, effectiveBounds.width);
                const voe = Math.max(0, Math.min(tile.percentBounds.bottom, effectiveBounds.bottom) - Math.max(tile.percentBounds.top, effectiveBounds.top)) / Math.min(tile.percentBounds.height, effectiveBounds.height);
                // Calc preferred direction
                let preferredDirection;
                // eslint-disable-next-line no-bitwise
                if (voe >= hoe && directions & Directions.horizontal) {
                    // horizontal
                    // eslint-disable-next-line no-bitwise
                    preferredDirection = hor >= hol && (directions & Directions.left) ? Directions.left : Directions.right;
                    // eslint-disable-next-line no-bitwise
                }
                else if (directions & Directions.vertical) {
                    // vertical
                    // eslint-disable-next-line no-bitwise
                    preferredDirection = vob >= vot && (directions & Directions.top) ? Directions.top : Directions.bottom;
                }
                switch (preferredDirection) {
                    case Directions.left:
                        leftTilesToPush.push(tile);
                        break;
                    case Directions.top:
                        topTilesToPush.push(tile);
                        break;
                    case Directions.right:
                        rightTilesToPush.push(tile);
                        break;
                    default:
                        bottomTilesToPush.push(tile);
                        break;
                }
            }
            return undefined;
        });
        if (swap) {
            return bounds;
        }
        // try first horizontal move
        let remain = 0;
        if (leftTilesToPush.length) {
            remain = this.pushHorizontal(bounds, -1, leftTilesToPush);
            if (remain) {
                bounds = this.ensureContainer(bounds.offset(remain, 0));
                // eslint-disable-next-line no-bitwise
                return retryCount ? this.ensureTarget(bounds, effectiveBounds, directions & ~Directions.left, originalBounds, retryCount - 1) : null;
            }
        }
        // Now try right
        if (rightTilesToPush.length) {
            remain = this.pushHorizontal(bounds, 1, rightTilesToPush);
        }
        if (remain > 0) {
            // No horizontal place, restore original position
            this.restoreLayout(this.originalLayout);
            // eslint-disable-next-line no-bitwise
            return retryCount ? this.ensureTarget(originalBounds, effectiveBounds, directions & Directions.vertical, undefined, retryCount - 1) : null;
        }
        else {
            // Try top
            if (topTilesToPush.length) {
                remain = this.pushVertical(bounds, -1, topTilesToPush);
                if (remain) {
                    bounds = this.ensureContainer(bounds.offset(0, remain));
                    return retryCount ? this.ensureTarget(bounds, effectiveBounds, Directions.bottom, undefined, retryCount - 1) : null;
                }
            }
            // And finally bottom
            remain = this.pushVertical(bounds, 1, bottomTilesToPush);
            if (remain) {
                // Destination is not available, keep tile at the original place
                return null;
            }
        }
        return bounds;
    }
    saveLayout() {
        const layout = {};
        layout.height = this.getTileMinPercentHeight();
        this.tiles.forEach(tile => {
            const y = this.getPixelSize(tile.percentBounds.top || 0);
            const h = this.getPixelSize(tile.percentBounds.height || this._tileMinHeight);
            if (y + h > layout.height) {
                layout.height = y + h;
            }
            layout[tile.id] = {
                bounds: tile.percentBounds.clone(),
                id: tile.id
            };
        });
        return layout;
    }
    getPixelBounds(rect) {
        return Rect.fromLTRB(Math.round(rect.left * this.hundredPercentWith / 100), Math.round(rect.top * this.hundredPercentWith / 100), Math.round(rect.right * this.hundredPercentWith / 100), Math.round(rect.bottom * this.hundredPercentWith / 100));
    }
    getPixelSize(value, unit) {
        if (!unit || unit === '%') {
            return Math.round(value * this.hundredPercentWith / 100);
        }
        else {
            return value;
        }
    }
    getSizePercentLimit(prop) {
        const self = this;
        const unit = self[`${prop}Unit`];
        if (!unit || unit === 'px') {
            return this.getPercentSize(self[prop]);
        }
        else {
            return self[prop];
        }
    }
    getSizePixelLimit(prop) {
        const self = this;
        const unit = self[`${prop}Unit`];
        if (!unit || unit === 'px') {
            return self[prop];
        }
        else {
            return this.getPixelSize(self[prop]);
        }
    }
    getTileMinPixelSize() {
        return new Size(this.getSizePixelLimit('_tileMinWidth'), this.getSizePixelLimit('_tileMinHeight'));
    }
    getTileMaxPixelSize() {
        return new Size(this.getSizePixelLimit('_tileMaxWidth'), this.getSizePixelLimit('_tileMaxHeight'));
    }
    getTileMinPercentWidth() {
        return Math.max(1, this.getSizePercentLimit('_tileMinWidth'));
    }
    getTileMaxPercentWidth() {
        return Math.max(5, this.getSizePercentLimit('_tileMaxWidth'));
    }
    getTileMinPercentHeight() {
        return Math.max(1, this.getSizePercentLimit('_tileMinHeight'));
    }
    getTileMaxPercentHeight() {
        return Math.max(5, this.getSizePercentLimit('_tileMaxHeight'));
    }
    getMaxPercentWidth() {
        return Math.max(5, this.getSizePercentLimit('_maxWidth'));
    }
    getMaxPercentHeight() {
        return Math.max(5, this.getSizePercentLimit('_maxHeight'));
    }
    getCursorFromHtmlElement(x, y, element) {
        const tileElement = this.getTileElementFromHTMLElement(element);
        if (!tileElement) {
            return null;
        }
        const bounds = tileElement.getBoundingClientRect();
        if (x < bounds.left + 15) {
            if (y < bounds.top + 15) {
                return 'nw-resize';
            }
            else if (y > bounds.bottom - 15) {
                return 'sw-resize';
            }
            else {
                return 'w-resize';
            }
        }
        else if (x > bounds.right - 15) {
            if (y < bounds.top + 15) {
                return 'ne-resize';
            }
            else if (y > bounds.bottom - 15) {
                return 'se-resize';
            }
            else {
                return 'e-resize';
            }
        }
        else if (y < bounds.top + 15) {
            return 'n-resize';
        }
        else if (y > bounds.bottom - 15) {
            return 's-resize';
        }
        else {
            return 'move';
        }
    }
    extractValueAndUnit(prop, value) {
        const regexp = /(\d+)(.*)/i;
        const matches = regexp.exec(value);
        if (matches?.length >= 1) {
            const self = this;
            self[prop] = parseInt(matches[1], 10);
            if (matches.length >= 2) {
                self[`${prop}Unit`] = matches[2];
            }
            else {
                self[`${prop}Unit`] = 'px';
            }
        }
    }
    restoreLayout(layout) {
        this.tiles.forEach(tile => {
            const config = layout[tile.id];
            if (config) {
                tile.percentBounds = config.bounds.clone();
            }
        });
    }
    calcHorizontalOverflow(direction, tiles, offset, blackList) {
        let overflow = 0;
        blackList = blackList || new Set();
        tiles.forEach(t => {
            if (!blackList.has(t.id)) {
                blackList.add(t.id);
                const tryBounds = t.percentBounds.offset(direction * offset, 0);
                let rightOffset = 0;
                const maxWidth = this.getMaxPercentWidth();
                if (tryBounds.left < 0) {
                    rightOffset = -tryBounds.left;
                }
                else if (maxWidth && tryBounds.right > maxWidth) {
                    rightOffset = tryBounds.right - maxWidth;
                }
                const adjacentTiles = this.tiles.filter((tile) => !tile.isDragging && !tile.equalsTo(t) && tile.percentBounds.intersectWith(tryBounds));
                if (adjacentTiles.length) {
                    rightOffset += this.calcHorizontalOverflow(direction, adjacentTiles, offset, blackList);
                }
                if (rightOffset > overflow) {
                    overflow = rightOffset;
                }
            }
        });
        return overflow;
    }
    moveHorizontal(direction, tiles, offset, targetBounds) {
        tiles.forEach(t => {
            if (!targetBounds.has(t.id)) {
                // Offset tile
                const newBounds = t.percentBounds.offset(direction * offset, 0);
                targetBounds.set(t.id, newBounds);
                const adjacentTiles = this.tiles.filter((tile) => !tile.isDragging && !tile.equalsTo(t) && tile.percentBounds.intersectWith(newBounds));
                if (adjacentTiles.length) {
                    this.moveHorizontal(direction, adjacentTiles, offset, targetBounds);
                }
            }
        });
    }
    pushHorizontal(bounds, direction, tiles, offset) {
        let overflow = 0;
        const targetBounds = new Map();
        if (!offset) {
            offset = 0;
            tiles.forEach(t => {
                const ho = direction > 0 ? Math.max(0, bounds.right - t.percentBounds.left) : Math.max(0, t.percentBounds.right - bounds.left);
                if (ho > offset) {
                    offset = ho;
                }
            });
        }
        if (offset > 0) {
            // Calc overflow space if all specified tiles are moved
            overflow = this.calcHorizontalOverflow(direction, tiles, offset);
            offset -= overflow;
            if (offset > 0) {
                this.moveHorizontal(direction, tiles, offset, targetBounds);
                //  bounds array to tiles
                this.tiles.forEach(t => {
                    if (targetBounds.has(t.id)) {
                        t.percentBounds = targetBounds.get(t.id);
                    }
                });
            }
        }
        return overflow;
    }
    calcVerticalOverflow(direction, tiles, offset, blackList) {
        let overflow = 0;
        blackList = blackList || new Set();
        tiles.forEach(t => {
            if (!blackList.has(t.id)) {
                blackList.add(t.id);
                // Offset tile
                const tryBounds = t.percentBounds.offset(0, direction * offset);
                let rightOffset = 0;
                const maxHeight = this.getMaxPercentHeight();
                if (tryBounds.top < 0) {
                    rightOffset = -tryBounds.top;
                }
                else if (maxHeight && tryBounds.bottom > maxHeight) {
                    rightOffset = tryBounds.bottom - maxHeight;
                }
                const adjacentTiles = this.tiles.filter((tile) => !tile.isDragging && !tile.equalsTo(t) && tile.percentBounds.intersectWith(tryBounds));
                if (adjacentTiles.length) {
                    rightOffset += this.calcVerticalOverflow(direction, adjacentTiles, offset, blackList);
                }
                if (rightOffset > overflow) {
                    overflow = rightOffset;
                }
            }
        });
        return overflow;
    }
    moveVertical(direction, tiles, offset, targetBounds) {
        tiles.forEach(t => {
            if (!targetBounds.has(t.id)) {
                // Offset tile
                const newBounds = t.percentBounds.offset(0, direction * offset);
                targetBounds.set(t.id, newBounds);
                const adjacentTiles = this.tiles.filter((tile) => !tile.isDragging && !tile.equalsTo(t) && tile.percentBounds.intersectWith(newBounds));
                if (adjacentTiles.length) {
                    this.moveVertical(direction, adjacentTiles, offset, targetBounds);
                }
            }
        });
    }
    pushVertical(bounds, direction, tiles, offset) {
        let overflow = 0;
        const targetBounds = new Map();
        if (!offset) {
            offset = 0;
            tiles.forEach(t => {
                const vo = direction > 0 ? Math.max(0, bounds.bottom - t.percentBounds.top) : Math.max(0, t.percentBounds.bottom - bounds.top);
                if (vo > offset) {
                    offset = vo;
                }
            });
        }
        if (offset > 0) {
            // Calc overflow space if all specified tiles are moved
            overflow = this.calcVerticalOverflow(direction, tiles, offset);
            offset -= overflow;
            if (offset > 0) {
                this.moveVertical(direction, tiles, offset, targetBounds);
                //  bounds array to tiles
                this.tiles.forEach(t => targetBounds.has(t.id) && (t.percentBounds = targetBounds.get(t.id)));
            }
        }
        return overflow;
    }
    copyTiles(tiles, isCut) {
        if (!this.clipboardService) {
            if (!tiles) {
                return;
            }
            throw new Error('DejaClipboardService must be imported by your application to use the copyTiles methode of DejaTilesComponent.');
        }
        const tt = this.clipboardService.get('tiles');
        if (tt) {
            tt.forEach(tile => tile.isCutted = false);
        }
        this.clipboardService.set('tiles', tiles);
        if (isCut) {
            tiles.forEach(tile => tile.isCutted = true);
            this.clipboardService.set('tiles-provider', this.deleteTiles$);
        }
        else {
            this.clipboardService.set('tiles-provider', undefined);
        }
    }
    removeTemporaryTile() {
        let index = this.tiles.length;
        // eslint-disable-next-line no-loops/no-loops
        while (--index >= 0) {
            const tile = this.tiles[index];
            if (tile.isTemporary) {
                this.tilesDic.delete(tile.id);
                this.tiles.splice(index, 1);
            }
        }
    }
}
DejaTilesLayoutProvider.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTilesLayoutProvider, deps: [{ token: i1$1.DejaClipboardService, optional: true }], target: i0.ɵɵFactoryTarget.Injectable });
DejaTilesLayoutProvider.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTilesLayoutProvider });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTilesLayoutProvider, decorators: [{
            type: Injectable
        }], ctorParameters: function () { return [{ type: i1$1.DejaClipboardService, decorators: [{
                    type: Optional
                }] }]; } });

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */
class DejaTilesComponent extends Destroy {
    constructor(el, changeDetectorRef, layoutProvider, control) {
        super();
        this.changeDetectorRef = changeDetectorRef;
        this.layoutProvider = layoutProvider;
        this.control = control;
        /**
         * Raised when the selected items has changed
         */
        this.selectionChanged = new EventEmitter();
        /**
         * Raised when the layout has changed with a drag and drop
         */
        this.layoutChanged = new EventEmitter();
        /**
         * Raised when the layout is completed and all tiles are binded
         */
        this.layoutCompleted = new EventEmitter();
        /**
         * Raised before some tiles will be added to the data model with a paste
         */
        this.contentAdding = new EventEmitter();
        /**
         * Raised before some tiles will be removed from the data model with a delete
         */
        this.contentRemoving = new EventEmitter();
        /**
         * Raised when tiles are added
         */
        this.tilesAdded = new EventEmitter();
        /**
         * Raised when tiles are deleted
         */
        this.tilesDeleted = new EventEmitter();
        /**
         * Raised when some tiles are copied in the clipboard service. Can result from a copy or paste operation on the tiles.
         */
        this.contentCopied = new EventEmitter();
        /**
         * Tab index of the focusable element
         */
        this.tabIndex = 0;
        this._models = [];
        this.hasFocus = false;
        // NgModel implementation
        this.onTouchedCallback = () => undefined;
        this.onChangeCallback = (_a) => undefined;
        if (this.control) {
            this.control.valueAccessor = this;
        }
        const element = el.nativeElement;
        from(this.layoutProvider.selectionChanged).pipe(takeUntil(this.destroyed$)).subscribe(event => this.selectionChanged.emit(event));
        from(this.layoutProvider.contentAdding).pipe(takeUntil(this.destroyed$)).subscribe(event => this.contentAdding.emit(event));
        from(this.layoutProvider.contentRemoving).pipe(takeUntil(this.destroyed$)).subscribe(event => this.contentRemoving.emit(event));
        from(this.layoutProvider.tilesAdded).pipe(takeUntil(this.destroyed$)).subscribe(event => {
            this.tilesAdded.emit(event);
            this.onChangeCallback(event.tiles);
        });
        from(this.layoutProvider.tilesDeleted).pipe(takeUntil(this.destroyed$)).subscribe(event => {
            this.tilesDeleted.emit(event);
            this.onChangeCallback(event.tiles);
        });
        from(this.layoutProvider.layoutChanged).pipe(takeUntil(this.destroyed$)).subscribe(event => {
            this.layoutChanged.emit(event);
            this.onChangeCallback(event.tiles);
        });
        from(this.layoutProvider.layoutCompleted).pipe(takeUntil(this.destroyed$)).subscribe(event => this.layoutCompleted.emit(event));
        this.keyup$ = fromEvent(element.ownerDocument, 'keyup');
        fromEvent(window, 'resize').pipe(debounceTime(5), takeUntil(this.destroyed$)).subscribe(() => this.refresh({ resetWidth: true }));
    }
    get tiles() {
        return this.layoutProvider.tiles;
    }
    // provide a public access
    get selectionRect$() {
        return this.layoutProvider.selectionRect$;
    }
    set tileminwidth(value) {
        this.layoutProvider.tileMinWidth = value;
    }
    set tilemaxwidth(value) {
        this.layoutProvider.tileMaxWidth = value;
    }
    set tileminheight(value) {
        this.layoutProvider.tileMinHeight = value;
    }
    set tilemaxheight(value) {
        this.layoutProvider.tileMaxHeight = value;
    }
    set maxwidth(value) {
        this.layoutProvider.maxWidth = value;
    }
    set designMode(value) {
        this.layoutProvider.designMode = coerceBooleanProperty(value);
    }
    get designMode() {
        return this.layoutProvider.designMode;
    }
    set models(models) {
        this.writeValue(models);
    }
    set canDelete(value) {
        if (coerceBooleanProperty(value) && !this.delete$sub) {
            this.delete$sub = this.keyup$.pipe(filter(event => this.layoutProvider.designMode && event.code === KeyCodes.Delete && this.hasFocus), takeUntil(this.destroyed$)).subscribe(() => this.layoutProvider.deleteSelection());
        }
        else if (this.delete$sub) {
            this.delete$sub.unsubscribe();
            this.delete$sub = undefined;
        }
    }
    set canCopy(value) {
        if (coerceBooleanProperty(value) && !this.copy$sub) {
            this.copy$sub = this.keyup$.pipe(filter(event => event.code === KeyCodes.KeyC && event.ctrlKey && this.hasFocus), takeUntil(this.destroyed$)).subscribe(() => this.copySelection());
        }
        else if (this.copy$sub) {
            this.copy$sub.unsubscribe();
            this.copy$sub = undefined;
        }
    }
    set canCut(value) {
        if (coerceBooleanProperty(value) && !this.cut$sub) {
            this.cut$sub = this.keyup$.pipe(filter(event => this.layoutProvider.designMode && event.code === KeyCodes.KeyX && event.ctrlKey && this.hasFocus), takeUntil(this.destroyed$)).subscribe(() => this.cutSelection());
        }
        else if (this.cut$sub) {
            this.cut$sub.unsubscribe();
            this.cut$sub = undefined;
        }
    }
    set canPaste(value) {
        if (coerceBooleanProperty(value) && !this.paste$sub) {
            this.paste$sub = this.keyup$.pipe(filter(event => this.layoutProvider.designMode && event.code === KeyCodes.KeyV && event.ctrlKey && this.hasFocus), takeUntil(this.destroyed$)).subscribe(() => this.paste());
        }
        else if (this.paste$sub) {
            this.paste$sub.unsubscribe();
            this.paste$sub = undefined;
        }
    }
    set selectedTiles(selectedTiles) {
        this.layoutProvider.selectedTiles = selectedTiles.map(tile => typeof tile === 'string' ? tile : (tile).id);
    }
    // ************* ControlValueAccessor Implementation **************
    writeValue(models) {
        this._models = models || [];
        const tiles = this._models;
        this.layoutProvider.tiles = tiles;
        this.changeDetectorRef.markForCheck();
    }
    registerOnChange(fn) {
        this.onChangeCallback = fn;
    }
    registerOnTouched(fn) {
        this.onTouchedCallback = fn;
    }
    // ************* End of ControlValueAccessor Implementation **************
    ngAfterViewInit() {
        this.layoutProvider.container = this.tilesContainer.nativeElement;
        this.refresh({ resetWidth: true });
    }
    ngOnDestroy() {
        super.ngOnDestroy();
        this.canCopy = false;
        this.canCut = false;
        this.canDelete = false;
        this.canPaste = false;
    }
    copySelection() {
        const tiles = this.layoutProvider.copySelection();
        if (tiles?.length) {
            const event = new CustomEvent('DejaTilesCopied', { cancelable: true });
            event.tiles = tiles;
            this.contentCopied.emit(event);
        }
    }
    cutSelection() {
        const tiles = this.layoutProvider.cutSelection();
        if (tiles?.length) {
            const event = new CustomEvent('DejaTilesCutted', { cancelable: true });
            event.tiles = tiles;
            this.contentCopied.emit(event);
        }
    }
    deleteSelection() {
        const tiles = this.layoutProvider.deleteSelection();
        this.changeDetectorRef.markForCheck();
        return tiles;
    }
    paste() {
        const tiles = this.layoutProvider.paste();
        this.changeDetectorRef.markForCheck();
        return tiles;
    }
    ensureVisible(id) {
        this.layoutProvider.ensureVisible$.next(id);
    }
    refresh(params) {
        this.layoutProvider.refreshTiles$.next(params);
    }
    addTiles(tiles) {
        this.layoutProvider.addTiles(tiles);
    }
    removeTiles(tileIds) {
        this.layoutProvider.removeTiles(tileIds);
    }
    hitTest(pageX, pageY) {
        const containerElement = this.tilesContainer.nativeElement;
        const containerBounds = containerElement.getBoundingClientRect();
        const x = pageX - containerBounds.left;
        const y = pageY - containerBounds.top;
        return this.tiles.find(t => t.pixelBounds?.containsPoint(new Position(x, y)));
    }
    getFreePlace(pageX, pageY, width, height) {
        if (!this._models || this._models.length === 0) {
            return new Rect(0, 0, width, height);
        }
        // Check if we drag on a tile
        const containerElement = this.tilesContainer.nativeElement;
        const containerBounds = containerElement.getBoundingClientRect();
        const x = pageX ? (pageX - containerBounds.left) : 0;
        const y = pageY ? (pageY - containerBounds.top) : 0;
        const percentBounds = new Rect(this.layoutProvider.getPercentSize(x), this.layoutProvider.getPercentSize(y), width, height);
        return this.layoutProvider.getFreePlace(percentBounds);
    }
    moveTile(id, bounds) {
        this.layoutProvider.moveTile(id, bounds);
    }
    getDropContext() {
        let cursorInfo;
        return {
            dragEnter: (dragContext, dragCursor) => {
                cursorInfo = this.layoutProvider.dragEnter(dragContext, dragCursor) ? {
                    className: 'hidden' // Hide drag cursor
                } : null;
                return cursorInfo;
            },
            dragOver: (_dragContext, dragCursor) => {
                this.layoutProvider.dragover$.next(dragCursor);
                return cursorInfo;
            },
            dragLeave: _dragContext => {
                cursorInfo = null;
                this.layoutProvider.dragleave$.next();
            }
        };
    }
    onTileClosed(tile) {
        this.layoutProvider.removeTiles([tile.id]);
    }
    onFocus() {
        this.hasFocus = true;
    }
    onBlur() {
        this.hasFocus = false;
    }
}
DejaTilesComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTilesComponent, deps: [{ token: i0.ElementRef }, { token: i0.ChangeDetectorRef }, { token: DejaTilesLayoutProvider }, { token: i2$1.NgControl, optional: true, self: true }], target: i0.ɵɵFactoryTarget.Component });
DejaTilesComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "14.1.1", type: DejaTilesComponent, selector: "deja-tiles", inputs: { tabIndex: "tabIndex", tileminwidth: "tileminwidth", tilemaxwidth: "tilemaxwidth", tileminheight: "tileminheight", tilemaxheight: "tilemaxheight", maxwidth: "maxwidth", designMode: "designMode", models: "models", canDelete: "canDelete", canCopy: "canCopy", canCut: "canCut", canPaste: "canPaste", selectedTiles: "selectedTiles" }, outputs: { selectionChanged: "selectionChanged", layoutChanged: "layoutChanged", layoutCompleted: "layoutCompleted", contentAdding: "contentAdding", contentRemoving: "contentRemoving", tilesAdded: "tilesAdded", tilesDeleted: "tilesDeleted", contentCopied: "contentCopied" }, providers: [DejaTilesLayoutProvider], queries: [{ propertyName: "tileTemplate", first: true, predicate: ["tileTemplate"], descendants: true }], viewQueries: [{ propertyName: "tilesContainer", first: true, predicate: ["tilesContainer"], descendants: true, static: true }], usesInheritance: true, ngImport: i0, template: "<div #tilesContainer id=\"tiles\" [class.design]=\"designMode\" [mouse-droppable]=\"getDropContext()\" [attr.tabindex]=\"tabIndex\" (focus)=\"onFocus()\" (blur)=\"onBlur()\">\n    <deja-tile *ngFor=\"let tile of tiles; let index = index;\" [id]=\"tile.id\" [tile]=\"tile\" [attr.tile-index]=\"index\" [template]=\"tileTemplate\" (close)=\"onTileClosed(tile)\"></deja-tile>\n    <div deja-tile-position [bounds]=\"selectionRect$ | async\"></div>\n</div>\n", styles: [":host{position:relative;overflow:auto;margin:0!important}:host #tiles{display:block;left:0;top:0;min-width:100%;min-height:100%;position:absolute;padding:0!important;margin:0!important}:host #tiles.design deja-tile{background-color:#0000001a}:host #tiles #tile:hover{z-index:200}:host [deja-tile-position]{position:absolute;z-index:101;background-color:#00000080}:host[drag]{background-color:#00000080}:host[drag] [deja-tile-position]{transition-property:left,top,width,height;transition-duration:.35s;transition-timing-function:ease}\n"], dependencies: [{ kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i4.MouseDroppableDirective, selector: "[mouse-droppable]", inputs: ["mouse-droppable"] }, { kind: "component", type: DejaTileComponent, selector: "deja-tile", inputs: ["template", "tile"], outputs: ["close"] }, { kind: "directive", type: DejaTilePositionDirective, selector: "[deja-tile-position]", inputs: ["bounds"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTilesComponent, decorators: [{
            type: Component,
            args: [{ selector: 'deja-tiles', changeDetection: ChangeDetectionStrategy.OnPush, providers: [DejaTilesLayoutProvider], template: "<div #tilesContainer id=\"tiles\" [class.design]=\"designMode\" [mouse-droppable]=\"getDropContext()\" [attr.tabindex]=\"tabIndex\" (focus)=\"onFocus()\" (blur)=\"onBlur()\">\n    <deja-tile *ngFor=\"let tile of tiles; let index = index;\" [id]=\"tile.id\" [tile]=\"tile\" [attr.tile-index]=\"index\" [template]=\"tileTemplate\" (close)=\"onTileClosed(tile)\"></deja-tile>\n    <div deja-tile-position [bounds]=\"selectionRect$ | async\"></div>\n</div>\n", styles: [":host{position:relative;overflow:auto;margin:0!important}:host #tiles{display:block;left:0;top:0;min-width:100%;min-height:100%;position:absolute;padding:0!important;margin:0!important}:host #tiles.design deja-tile{background-color:#0000001a}:host #tiles #tile:hover{z-index:200}:host [deja-tile-position]{position:absolute;z-index:101;background-color:#00000080}:host[drag]{background-color:#00000080}:host[drag] [deja-tile-position]{transition-property:left,top,width,height;transition-duration:.35s;transition-timing-function:ease}\n"] }]
        }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: i0.ChangeDetectorRef }, { type: DejaTilesLayoutProvider }, { type: i2$1.NgControl, decorators: [{
                    type: Self
                }, {
                    type: Optional
                }] }]; }, propDecorators: { selectionChanged: [{
                type: Output
            }], layoutChanged: [{
                type: Output
            }], layoutCompleted: [{
                type: Output
            }], contentAdding: [{
                type: Output
            }], contentRemoving: [{
                type: Output
            }], tilesAdded: [{
                type: Output
            }], tilesDeleted: [{
                type: Output
            }], contentCopied: [{
                type: Output
            }], tabIndex: [{
                type: Input
            }], tileTemplate: [{
                type: ContentChild,
                args: ['tileTemplate']
            }], tilesContainer: [{
                type: ViewChild,
                args: ['tilesContainer', { static: true }]
            }], tileminwidth: [{
                type: Input
            }], tilemaxwidth: [{
                type: Input
            }], tileminheight: [{
                type: Input
            }], tilemaxheight: [{
                type: Input
            }], maxwidth: [{
                type: Input
            }], designMode: [{
                type: Input
            }], models: [{
                type: Input
            }], canDelete: [{
                type: Input
            }], canCopy: [{
                type: Input
            }], canCut: [{
                type: Input
            }], canPaste: [{
                type: Input
            }], selectedTiles: [{
                type: Input
            }] } });

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */
class DejaTile {
    constructor(id) {
        this.cutted$ = new BehaviorSubject(false);
        this.dragging$ = new BehaviorSubject(false);
        this.dropping$ = new BehaviorSubject(false);
        this.pressed$ = new BehaviorSubject(false);
        this.selected$ = new BehaviorSubject(false);
        this.hidden$ = new ReplaySubject(1);
        this.pixelBounds$ = new BehaviorSubject(null);
        this.deleted$ = new Subject();
        this.refresh$ = new Subject();
        this.isTemporary = false;
        this._isDragging = false;
        this._isDropping = false;
        this._isPressed = false;
        this._isHidden = false;
        this._id = id || `#${DejaTile.CURRENT_ID++}`;
    }
    get idealBounds() {
        return new Rect(0, 0, 15, 15);
    }
    set pixelBounds(value) {
        if (!Rect.equals(this._pixelBounds, value)) {
            this._pixelBounds = value;
            this.pixelBounds$.next(value);
        }
    }
    get pixelBounds() {
        return this._pixelBounds;
    }
    set percentBounds(value) {
        this._percentBounds = value;
    }
    get percentBounds() {
        return this._percentBounds;
    }
    set templateModel(value) {
        this._templateModel = value;
    }
    get templateModel() {
        return this._templateModel;
    }
    get id() {
        return this._id;
    }
    set isCutted(value) {
        if (this._cutted !== value) {
            this._cutted = value;
            this.cutted$.next(value);
        }
    }
    get isCutted() {
        return this._cutted;
    }
    set color(value) {
        this._color = value;
    }
    get color() {
        return this._color;
    }
    set isDragging(value) {
        if (this._isDragging !== value) {
            this._isDragging = value;
            this.dragging$.next(value);
        }
    }
    get isDragging() {
        return this._isDragging;
    }
    set isDropping(value) {
        if (this._isDropping !== value) {
            this._isDropping = value;
            this.dropping$.next(value);
        }
    }
    get isDropping() {
        return this._isDropping;
    }
    set isPressed(value) {
        if (this._isPressed !== value) {
            this._isPressed = value;
            this.pressed$.next(value);
        }
    }
    get isPressed() {
        return this._isPressed;
    }
    set isSelected(value) {
        if (this._selected !== value) {
            this._selected = value;
            this.selected$.next(value);
        }
    }
    get isSelected() {
        return this._selected;
    }
    set isHidden(value) {
        if (this._isHidden !== value) {
            this._isHidden = value;
            this.hidden$.next(value);
        }
    }
    get isHidden() {
        return this._isHidden;
    }
    set isPending(value) {
        this._pending = value;
    }
    get isPending() {
        return this._pending;
    }
    get fading() {
        return this._fading;
    }
    set fading(value) {
        this._fading = value;
    }
    makeId() {
        this._id = `#${DejaTile.CURRENT_ID++}`;
    }
    equalsTo(tile) {
        if (this._templateModel) {
            return this._templateModel === tile._templateModel;
        }
        else {
            return this._id === tile._id;
        }
    }
    clone(tile) {
        if (!tile) {
            tile = new DejaTile(this.id);
        }
        tile._percentBounds = this.percentBounds;
        tile._color = this.color;
        tile._templateModel = this.templateModel;
        tile._cutted = this._cutted;
        tile._selected = this._selected;
        tile._pending = this._pending;
        tile._fading = this._fading;
        return tile;
    }
    delete() {
        this.deleted$.next();
    }
    refresh() {
        this.refresh$.next();
    }
}
DejaTile.CURRENT_ID = 0;

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */

/*
 *  @license
 *  Copyright Hôpitaux Universitaires de Genève. All Rights Reserved.
 *
 *  Use of this source code is governed by an Apache-2.0 license that can be
 *  found in the LICENSE file at https://github.com/DSI-HUG/dejajs-components/blob/master/LICENSE
 */
class DejaTilesModule {
}
DejaTilesModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTilesModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
DejaTilesModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "14.1.1", ngImport: i0, type: DejaTilesModule, declarations: [DejaTileComponent, DejaTilePositionDirective, DejaTilesComponent], imports: [CommonModule,
        FormsModule,
        MatProgressSpinnerModule,
        MouseDragDropModule], exports: [DejaTileComponent, DejaTilesComponent] });
DejaTilesModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTilesModule, imports: [CommonModule,
        FormsModule,
        MatProgressSpinnerModule,
        MouseDragDropModule] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "14.1.1", ngImport: i0, type: DejaTilesModule, decorators: [{
            type: NgModule,
            args: [{
                    declarations: [DejaTileComponent, DejaTilePositionDirective, DejaTilesComponent],
                    exports: [DejaTileComponent, DejaTilesComponent],
                    imports: [
                        CommonModule,
                        FormsModule,
                        MatProgressSpinnerModule,
                        MouseDragDropModule
                    ]
                }]
        }] });

/**
 * Generated bundle index. Do not edit.
 */

export { DejaTile, DejaTileComponent, DejaTilePositionDirective, DejaTilesComponent, DejaTilesLayoutProvider, DejaTilesModule };
//# sourceMappingURL=deja-js-component-tiles.mjs.map
