UNPKG

angular-resizable-element

Version:

An angular 15.0+ directive that allows an element to be dragged and resized

773 lines (764 loc) 31.9 kB
import * as i0 from '@angular/core'; import { EventEmitter, PLATFORM_ID, Directive, Inject, Input, Output, Optional, NgModule } from '@angular/core'; import { isPlatformBrowser } from '@angular/common'; import { Subject, merge, Observable, fromEvent } from 'rxjs'; import { tap, share, mergeMap, take, map, pairwise, filter, takeUntil } from 'rxjs/operators'; /** * @hidden */ const IS_TOUCH_DEVICE = (() => { // In case we're in Node.js environment. if (typeof window === 'undefined') { return false; } else { return ('ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0); } })(); /** Creates a deep clone of an element. */ function deepCloneNode(node) { const clone = node.cloneNode(true); const descendantsWithId = clone.querySelectorAll('[id]'); const nodeName = node.nodeName.toLowerCase(); // Remove the `id` to avoid having multiple elements with the same id on the page. clone.removeAttribute('id'); descendantsWithId.forEach((descendant) => { descendant.removeAttribute('id'); }); if (nodeName === 'canvas') { transferCanvasData(node, clone); } else if (nodeName === 'input' || nodeName === 'select' || nodeName === 'textarea') { transferInputData(node, clone); } transferData('canvas', node, clone, transferCanvasData); transferData('input, textarea, select', node, clone, transferInputData); return clone; } /** Matches elements between an element and its clone and allows for their data to be cloned. */ function transferData(selector, node, clone, callback) { const descendantElements = node.querySelectorAll(selector); if (descendantElements.length) { const cloneElements = clone.querySelectorAll(selector); for (let i = 0; i < descendantElements.length; i++) { callback(descendantElements[i], cloneElements[i]); } } } // Counter for unique cloned radio button names. let cloneUniqueId = 0; /** Transfers the data of one input element to another. */ function transferInputData(source, clone) { // Browsers throw an error when assigning the value of a file input programmatically. if (clone.type !== 'file') { clone.value = source.value; } // Radio button `name` attributes must be unique for radio button groups // otherwise original radio buttons can lose their checked state // once the clone is inserted in the DOM. if (clone.type === 'radio' && clone.name) { clone.name = `mat-clone-${clone.name}-${cloneUniqueId++}`; } } /** Transfers the data of one canvas element to another. */ function transferCanvasData(source, clone) { const context = clone.getContext('2d'); if (context) { // In some cases `drawImage` can throw (e.g. if the canvas size is 0x0). // We can't do much about it so just ignore the error. try { context.drawImage(source, 0, 0); } catch { } } } function getNewBoundingRectangle(startingRect, edges, clientX, clientY) { const newBoundingRect = { top: startingRect.top, bottom: startingRect.bottom, left: startingRect.left, right: startingRect.right, }; if (edges.top) { newBoundingRect.top += clientY; } if (edges.bottom) { newBoundingRect.bottom += clientY; } if (edges.left) { newBoundingRect.left += clientX; } if (edges.right) { newBoundingRect.right += clientX; } newBoundingRect.height = newBoundingRect.bottom - newBoundingRect.top; newBoundingRect.width = newBoundingRect.right - newBoundingRect.left; return newBoundingRect; } function getElementRect(element, ghostElementPositioning) { let translateX = 0; let translateY = 0; const style = element.nativeElement.style; const transformProperties = [ 'transform', '-ms-transform', '-moz-transform', '-o-transform', ]; const transform = transformProperties .map((property) => style[property]) .find((value) => !!value); if (transform && transform.includes('translate')) { translateX = transform.replace(/.*translate3?d?\((-?[0-9]*)px, (-?[0-9]*)px.*/, '$1'); translateY = transform.replace(/.*translate3?d?\((-?[0-9]*)px, (-?[0-9]*)px.*/, '$2'); } if (ghostElementPositioning === 'absolute') { return { height: element.nativeElement.offsetHeight, width: element.nativeElement.offsetWidth, top: element.nativeElement.offsetTop - translateY, bottom: element.nativeElement.offsetHeight + element.nativeElement.offsetTop - translateY, left: element.nativeElement.offsetLeft - translateX, right: element.nativeElement.offsetWidth + element.nativeElement.offsetLeft - translateX, }; } else { const boundingRect = element.nativeElement.getBoundingClientRect(); return { height: boundingRect.height, width: boundingRect.width, top: boundingRect.top - translateY, bottom: boundingRect.bottom - translateY, left: boundingRect.left - translateX, right: boundingRect.right - translateX, scrollTop: element.nativeElement.scrollTop, scrollLeft: element.nativeElement.scrollLeft, }; } } const DEFAULT_RESIZE_CURSORS = Object.freeze({ topLeft: 'nw-resize', topRight: 'ne-resize', bottomLeft: 'sw-resize', bottomRight: 'se-resize', leftOrRight: 'col-resize', topOrBottom: 'row-resize', }); function getResizeCursor(edges, cursors) { if (edges.left && edges.top) { return cursors.topLeft; } else if (edges.right && edges.top) { return cursors.topRight; } else if (edges.left && edges.bottom) { return cursors.bottomLeft; } else if (edges.right && edges.bottom) { return cursors.bottomRight; } else if (edges.left || edges.right) { return cursors.leftOrRight; } else if (edges.top || edges.bottom) { return cursors.topOrBottom; } else { return ''; } } function getEdgesDiff({ edges, initialRectangle, newRectangle, }) { const edgesDiff = {}; Object.keys(edges).forEach((edge) => { edgesDiff[edge] = (newRectangle[edge] || 0) - (initialRectangle[edge] || 0); }); return edgesDiff; } const RESIZE_ACTIVE_CLASS = 'resize-active'; const RESIZE_GHOST_ELEMENT_CLASS = 'resize-ghost-element'; const MOUSE_MOVE_THROTTLE_MS = 50; /** * Place this on an element to make it resizable. For example: * * ```html * <div * mwlResizable * [resizeEdges]="{bottom: true, right: true, top: true, left: true}" * [enableGhostResize]="true"> * </div> * ``` * Or in case they are sibling elements: * ```html * <div mwlResizable #resizableElement="mwlResizable"></div> * <div mwlResizeHandle [resizableContainer]="resizableElement" [resizeEdges]="{bottom: true, right: true}"></div> * ``` */ class ResizableDirective { /** * @hidden */ constructor(platformId, renderer, elm, zone) { this.platformId = platformId; this.renderer = renderer; this.elm = elm; this.zone = zone; /** * Set to `true` to enable a temporary resizing effect of the element in between the `resizeStart` and `resizeEnd` events. */ this.enableGhostResize = false; /** * A snap grid that resize events will be locked to. * * e.g. to only allow the element to be resized every 10px set it to `{left: 10, right: 10}` */ this.resizeSnapGrid = {}; /** * The mouse cursors that will be set on the resize edges */ this.resizeCursors = DEFAULT_RESIZE_CURSORS; /** * Define the positioning of the ghost element (can be fixed or absolute) */ this.ghostElementPositioning = 'fixed'; /** * Allow elements to be resized to negative dimensions */ this.allowNegativeResizes = false; /** * The mouse move throttle in milliseconds, default: 50 ms */ this.mouseMoveThrottleMS = MOUSE_MOVE_THROTTLE_MS; /** * Called when the mouse is pressed and a resize event is about to begin. `$event` is a `ResizeEvent` object. */ this.resizeStart = new EventEmitter(); /** * Called as the mouse is dragged after a resize event has begun. `$event` is a `ResizeEvent` object. */ this.resizing = new EventEmitter(); /** * Called after the mouse is released after a resize event. `$event` is a `ResizeEvent` object. */ this.resizeEnd = new EventEmitter(); /** * @hidden */ this.mouseup = new Subject(); /** * @hidden */ this.mousedown = new Subject(); /** * @hidden */ this.mousemove = new Subject(); this.destroy$ = new Subject(); this.pointerEventListeners = PointerEventListeners.getInstance(renderer, zone); } /** * @hidden */ ngOnInit() { const mousedown$ = merge(this.pointerEventListeners.pointerDown, this.mousedown); const mousemove$ = merge(this.pointerEventListeners.pointerMove, this.mousemove).pipe(tap(({ event }) => { if (currentResize && event.cancelable) { event.preventDefault(); } }), share()); const mouseup$ = merge(this.pointerEventListeners.pointerUp, this.mouseup); let currentResize; const removeGhostElement = () => { if (currentResize && currentResize.clonedNode) { this.elm.nativeElement.parentElement.removeChild(currentResize.clonedNode); this.renderer.setStyle(this.elm.nativeElement, 'visibility', 'inherit'); } }; const getResizeCursors = () => { return { ...DEFAULT_RESIZE_CURSORS, ...this.resizeCursors, }; }; const mousedrag = mousedown$ .pipe(mergeMap((startCoords) => { function getDiff(moveCoords) { return { clientX: moveCoords.clientX - startCoords.clientX, clientY: moveCoords.clientY - startCoords.clientY, }; } const getSnapGrid = () => { const snapGrid = { x: 1, y: 1 }; if (currentResize) { if (this.resizeSnapGrid.left && currentResize.edges.left) { snapGrid.x = +this.resizeSnapGrid.left; } else if (this.resizeSnapGrid.right && currentResize.edges.right) { snapGrid.x = +this.resizeSnapGrid.right; } if (this.resizeSnapGrid.top && currentResize.edges.top) { snapGrid.y = +this.resizeSnapGrid.top; } else if (this.resizeSnapGrid.bottom && currentResize.edges.bottom) { snapGrid.y = +this.resizeSnapGrid.bottom; } } return snapGrid; }; function getGrid(coords, snapGrid) { return { x: Math.ceil(coords.clientX / snapGrid.x), y: Math.ceil(coords.clientY / snapGrid.y), }; } return merge(mousemove$.pipe(take(1)).pipe(map((coords) => [, coords])), mousemove$.pipe(pairwise())) .pipe(map(([previousCoords, newCoords]) => { return [ previousCoords ? getDiff(previousCoords) : previousCoords, getDiff(newCoords), ]; })) .pipe(filter(([previousCoords, newCoords]) => { if (!previousCoords) { return true; } const snapGrid = getSnapGrid(); const previousGrid = getGrid(previousCoords, snapGrid); const newGrid = getGrid(newCoords, snapGrid); return (previousGrid.x !== newGrid.x || previousGrid.y !== newGrid.y); })) .pipe(map(([, newCoords]) => { const snapGrid = getSnapGrid(); return { clientX: Math.round(newCoords.clientX / snapGrid.x) * snapGrid.x, clientY: Math.round(newCoords.clientY / snapGrid.y) * snapGrid.y, }; })) .pipe(takeUntil(merge(mouseup$, mousedown$))); })) .pipe(filter(() => !!currentResize)); mousedrag .pipe(map(({ clientX, clientY }) => { return getNewBoundingRectangle(currentResize.startingRect, currentResize.edges, clientX, clientY); })) .pipe(filter((newBoundingRect) => { return (this.allowNegativeResizes || !!(newBoundingRect.height && newBoundingRect.width && newBoundingRect.height > 0 && newBoundingRect.width > 0)); })) .pipe(filter((newBoundingRect) => { return this.validateResize ? this.validateResize({ rectangle: newBoundingRect, edges: getEdgesDiff({ edges: currentResize.edges, initialRectangle: currentResize.startingRect, newRectangle: newBoundingRect, }), }) : true; }), takeUntil(this.destroy$)) .subscribe((newBoundingRect) => { if (currentResize && currentResize.clonedNode) { this.renderer.setStyle(currentResize.clonedNode, 'height', `${newBoundingRect.height}px`); this.renderer.setStyle(currentResize.clonedNode, 'width', `${newBoundingRect.width}px`); this.renderer.setStyle(currentResize.clonedNode, 'top', `${newBoundingRect.top}px`); this.renderer.setStyle(currentResize.clonedNode, 'left', `${newBoundingRect.left}px`); } if (this.resizing.observers.length > 0) { this.zone.run(() => { this.resizing.emit({ edges: getEdgesDiff({ edges: currentResize.edges, initialRectangle: currentResize.startingRect, newRectangle: newBoundingRect, }), rectangle: newBoundingRect, }); }); } currentResize.currentRect = newBoundingRect; }); mousedown$ .pipe(map(({ edges }) => { return edges || {}; }), filter((edges) => { return Object.keys(edges).length > 0; }), takeUntil(this.destroy$)) .subscribe((edges) => { if (currentResize) { removeGhostElement(); } const startingRect = getElementRect(this.elm, this.ghostElementPositioning); currentResize = { edges, startingRect, currentRect: startingRect, }; const resizeCursors = getResizeCursors(); const cursor = getResizeCursor(currentResize.edges, resizeCursors); this.renderer.setStyle(document.body, 'cursor', cursor); this.setElementClass(this.elm, RESIZE_ACTIVE_CLASS, true); if (this.enableGhostResize) { currentResize.clonedNode = deepCloneNode(this.elm.nativeElement); this.elm.nativeElement.parentElement.appendChild(currentResize.clonedNode); this.renderer.setStyle(this.elm.nativeElement, 'visibility', 'hidden'); this.renderer.setStyle(currentResize.clonedNode, 'position', this.ghostElementPositioning); this.renderer.setStyle(currentResize.clonedNode, 'left', `${currentResize.startingRect.left}px`); this.renderer.setStyle(currentResize.clonedNode, 'top', `${currentResize.startingRect.top}px`); this.renderer.setStyle(currentResize.clonedNode, 'height', `${currentResize.startingRect.height}px`); this.renderer.setStyle(currentResize.clonedNode, 'width', `${currentResize.startingRect.width}px`); this.renderer.setStyle(currentResize.clonedNode, 'cursor', getResizeCursor(currentResize.edges, resizeCursors)); this.renderer.addClass(currentResize.clonedNode, RESIZE_GHOST_ELEMENT_CLASS); currentResize.clonedNode.scrollTop = currentResize.startingRect .scrollTop; currentResize.clonedNode.scrollLeft = currentResize.startingRect .scrollLeft; } if (this.resizeStart.observers.length > 0) { this.zone.run(() => { this.resizeStart.emit({ edges: getEdgesDiff({ edges, initialRectangle: startingRect, newRectangle: startingRect, }), rectangle: getNewBoundingRectangle(startingRect, {}, 0, 0), }); }); } }); mouseup$.pipe(takeUntil(this.destroy$)).subscribe(() => { if (currentResize) { this.renderer.removeClass(this.elm.nativeElement, RESIZE_ACTIVE_CLASS); this.renderer.setStyle(document.body, 'cursor', ''); this.renderer.setStyle(this.elm.nativeElement, 'cursor', ''); if (this.resizeEnd.observers.length > 0) { this.zone.run(() => { this.resizeEnd.emit({ edges: getEdgesDiff({ edges: currentResize.edges, initialRectangle: currentResize.startingRect, newRectangle: currentResize.currentRect, }), rectangle: currentResize.currentRect, }); }); } removeGhostElement(); currentResize = null; } }); } /** * @hidden */ ngOnDestroy() { // browser check for angular universal, because it doesn't know what document is if (isPlatformBrowser(this.platformId)) { this.renderer.setStyle(document.body, 'cursor', ''); } this.mousedown.complete(); this.mouseup.complete(); this.mousemove.complete(); this.destroy$.next(); } setElementClass(elm, name, add) { if (add) { this.renderer.addClass(elm.nativeElement, name); } else { this.renderer.removeClass(elm.nativeElement, name); } } } ResizableDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: ResizableDirective, deps: [{ token: PLATFORM_ID }, { token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive }); ResizableDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.0.3", type: ResizableDirective, selector: "[mwlResizable]", inputs: { validateResize: "validateResize", enableGhostResize: "enableGhostResize", resizeSnapGrid: "resizeSnapGrid", resizeCursors: "resizeCursors", ghostElementPositioning: "ghostElementPositioning", allowNegativeResizes: "allowNegativeResizes", mouseMoveThrottleMS: "mouseMoveThrottleMS" }, outputs: { resizeStart: "resizeStart", resizing: "resizing", resizeEnd: "resizeEnd" }, exportAs: ["mwlResizable"], ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: ResizableDirective, decorators: [{ type: Directive, args: [{ selector: '[mwlResizable]', exportAs: 'mwlResizable', }] }], ctorParameters: function () { return [{ type: undefined, decorators: [{ type: Inject, args: [PLATFORM_ID] }] }, { type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.NgZone }]; }, propDecorators: { validateResize: [{ type: Input }], enableGhostResize: [{ type: Input }], resizeSnapGrid: [{ type: Input }], resizeCursors: [{ type: Input }], ghostElementPositioning: [{ type: Input }], allowNegativeResizes: [{ type: Input }], mouseMoveThrottleMS: [{ type: Input }], resizeStart: [{ type: Output }], resizing: [{ type: Output }], resizeEnd: [{ type: Output }] } }); class PointerEventListeners { constructor(renderer, zone) { this.pointerDown = new Observable((observer) => { let unsubscribeMouseDown; let unsubscribeTouchStart; zone.runOutsideAngular(() => { unsubscribeMouseDown = renderer.listen('document', 'mousedown', (event) => { observer.next({ clientX: event.clientX, clientY: event.clientY, event, }); }); if (IS_TOUCH_DEVICE) { unsubscribeTouchStart = renderer.listen('document', 'touchstart', (event) => { observer.next({ clientX: event.touches[0].clientX, clientY: event.touches[0].clientY, event, }); }); } }); return () => { unsubscribeMouseDown(); if (IS_TOUCH_DEVICE) { unsubscribeTouchStart(); } }; }).pipe(share()); this.pointerMove = new Observable((observer) => { let unsubscribeMouseMove; let unsubscribeTouchMove; zone.runOutsideAngular(() => { unsubscribeMouseMove = renderer.listen('document', 'mousemove', (event) => { observer.next({ clientX: event.clientX, clientY: event.clientY, event, }); }); if (IS_TOUCH_DEVICE) { unsubscribeTouchMove = renderer.listen('document', 'touchmove', (event) => { observer.next({ clientX: event.targetTouches[0].clientX, clientY: event.targetTouches[0].clientY, event, }); }); } }); return () => { unsubscribeMouseMove(); if (IS_TOUCH_DEVICE) { unsubscribeTouchMove(); } }; }).pipe(share()); this.pointerUp = new Observable((observer) => { let unsubscribeMouseUp; let unsubscribeTouchEnd; let unsubscribeTouchCancel; zone.runOutsideAngular(() => { unsubscribeMouseUp = renderer.listen('document', 'mouseup', (event) => { observer.next({ clientX: event.clientX, clientY: event.clientY, event, }); }); if (IS_TOUCH_DEVICE) { unsubscribeTouchEnd = renderer.listen('document', 'touchend', (event) => { observer.next({ clientX: event.changedTouches[0].clientX, clientY: event.changedTouches[0].clientY, event, }); }); unsubscribeTouchCancel = renderer.listen('document', 'touchcancel', (event) => { observer.next({ clientX: event.changedTouches[0].clientX, clientY: event.changedTouches[0].clientY, event, }); }); } }); return () => { unsubscribeMouseUp(); if (IS_TOUCH_DEVICE) { unsubscribeTouchEnd(); unsubscribeTouchCancel(); } }; }).pipe(share()); } static getInstance(renderer, zone) { if (!PointerEventListeners.instance) { PointerEventListeners.instance = new PointerEventListeners(renderer, zone); } return PointerEventListeners.instance; } } /** * An element placed inside a `mwlResizable` directive to be used as a drag and resize handle * * For example * * ```html * <div mwlResizable> * <div mwlResizeHandle [resizeEdges]="{bottom: true, right: true}"></div> * </div> * ``` * Or in case they are sibling elements: * ```html * <div mwlResizable #resizableElement="mwlResizable"></div> * <div mwlResizeHandle [resizableContainer]="resizableElement" [resizeEdges]="{bottom: true, right: true}"></div> * ``` */ class ResizeHandleDirective { constructor(renderer, element, zone, resizableDirective) { this.renderer = renderer; this.element = element; this.zone = zone; this.resizableDirective = resizableDirective; /** * The `Edges` object that contains the edges of the parent element that dragging the handle will trigger a resize on */ this.resizeEdges = {}; this.eventListeners = {}; this.destroy$ = new Subject(); } ngOnInit() { this.zone.runOutsideAngular(() => { this.listenOnTheHost('mousedown').subscribe((event) => { this.onMousedown(event, event.clientX, event.clientY); }); this.listenOnTheHost('mouseup').subscribe((event) => { this.onMouseup(event.clientX, event.clientY); }); if (IS_TOUCH_DEVICE) { this.listenOnTheHost('touchstart').subscribe((event) => { this.onMousedown(event, event.touches[0].clientX, event.touches[0].clientY); }); merge(this.listenOnTheHost('touchend'), this.listenOnTheHost('touchcancel')).subscribe((event) => { this.onMouseup(event.changedTouches[0].clientX, event.changedTouches[0].clientY); }); } }); } ngOnDestroy() { this.destroy$.next(); this.unsubscribeEventListeners(); } /** * @hidden */ onMousedown(event, clientX, clientY) { if (event.cancelable) { event.preventDefault(); } if (!this.eventListeners.touchmove) { this.eventListeners.touchmove = this.renderer.listen(this.element.nativeElement, 'touchmove', (touchMoveEvent) => { this.onMousemove(touchMoveEvent, touchMoveEvent.targetTouches[0].clientX, touchMoveEvent.targetTouches[0].clientY); }); } if (!this.eventListeners.mousemove) { this.eventListeners.mousemove = this.renderer.listen(this.element.nativeElement, 'mousemove', (mouseMoveEvent) => { this.onMousemove(mouseMoveEvent, mouseMoveEvent.clientX, mouseMoveEvent.clientY); }); } this.resizable.mousedown.next({ clientX, clientY, edges: this.resizeEdges, }); } /** * @hidden */ onMouseup(clientX, clientY) { this.unsubscribeEventListeners(); this.resizable.mouseup.next({ clientX, clientY, edges: this.resizeEdges, }); } // directive might be passed from DI or as an input get resizable() { return this.resizableDirective || this.resizableContainer; } onMousemove(event, clientX, clientY) { this.resizable.mousemove.next({ clientX, clientY, edges: this.resizeEdges, event, }); } unsubscribeEventListeners() { Object.keys(this.eventListeners).forEach((type) => { this.eventListeners[type](); delete this.eventListeners[type]; }); } listenOnTheHost(eventName) { return fromEvent(this.element.nativeElement, eventName).pipe(takeUntil(this.destroy$)); } } ResizeHandleDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: ResizeHandleDirective, deps: [{ token: i0.Renderer2 }, { token: i0.ElementRef }, { token: i0.NgZone }, { token: ResizableDirective, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); ResizeHandleDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "15.0.3", type: ResizeHandleDirective, selector: "[mwlResizeHandle]", inputs: { resizeEdges: "resizeEdges", resizableContainer: "resizableContainer" }, ngImport: i0 }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: ResizeHandleDirective, decorators: [{ type: Directive, args: [{ selector: '[mwlResizeHandle]', }] }], ctorParameters: function () { return [{ type: i0.Renderer2 }, { type: i0.ElementRef }, { type: i0.NgZone }, { type: ResizableDirective, decorators: [{ type: Optional }] }]; }, propDecorators: { resizeEdges: [{ type: Input }], resizableContainer: [{ type: Input }] } }); class ResizableModule { } ResizableModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: ResizableModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); ResizableModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "15.0.3", ngImport: i0, type: ResizableModule, declarations: [ResizableDirective, ResizeHandleDirective], exports: [ResizableDirective, ResizeHandleDirective] }); ResizableModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: ResizableModule }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.0.3", ngImport: i0, type: ResizableModule, decorators: [{ type: NgModule, args: [{ declarations: [ResizableDirective, ResizeHandleDirective], exports: [ResizableDirective, ResizeHandleDirective], }] }] }); /* * Public API Surface of angular-resizable-element */ /** * Generated bundle index. Do not edit. */ export { ResizableDirective, ResizableModule, ResizeHandleDirective }; //# sourceMappingURL=angular-resizable-element.mjs.map //# sourceMappingURL=angular-resizable-element.mjs.map