UNPKG

angular-gridster2

Version:
1,143 lines (1,135 loc) 155 kB
import { NgStyle } from '@angular/common'; import * as i0 from '@angular/core'; import { Component, ViewEncapsulation, Input, EventEmitter, ElementRef, Renderer2, ChangeDetectorRef, NgZone, Inject, Output, HostBinding, NgModule } from '@angular/core'; import { Subject, debounceTime, takeUntil, switchMap, timer } from 'rxjs'; var GridType; (function (GridType) { GridType["Fit"] = "fit"; GridType["ScrollVertical"] = "scrollVertical"; GridType["ScrollHorizontal"] = "scrollHorizontal"; GridType["Fixed"] = "fixed"; GridType["VerticalFixed"] = "verticalFixed"; GridType["HorizontalFixed"] = "horizontalFixed"; })(GridType || (GridType = {})); var DisplayGrid; (function (DisplayGrid) { DisplayGrid["Always"] = "always"; DisplayGrid["OnDragAndResize"] = "onDrag&Resize"; DisplayGrid["None"] = "none"; })(DisplayGrid || (DisplayGrid = {})); var CompactType; (function (CompactType) { CompactType["None"] = "none"; CompactType["CompactUp"] = "compactUp"; CompactType["CompactLeft"] = "compactLeft"; CompactType["CompactUpAndLeft"] = "compactUp&Left"; CompactType["CompactLeftAndUp"] = "compactLeft&Up"; CompactType["CompactRight"] = "compactRight"; CompactType["CompactUpAndRight"] = "compactUp&Right"; CompactType["CompactRightAndUp"] = "compactRight&Up"; CompactType["CompactDown"] = "compactDown"; CompactType["CompactDownAndLeft"] = "compactDown&Left"; CompactType["CompactLeftAndDown"] = "compactLeft&Down"; CompactType["CompactDownAndRight"] = "compactDown&Right"; CompactType["CompactRightAndDown"] = "compactRight&Down"; })(CompactType || (CompactType = {})); var DirTypes; (function (DirTypes) { DirTypes["LTR"] = "ltr"; DirTypes["RTL"] = "rtl"; })(DirTypes || (DirTypes = {})); class GridsterCompact { gridster; constructor(gridster) { this.gridster = gridster; } destroy() { this.gridster = null; } checkCompact() { if (this.gridster.$options.compactType !== CompactType.None) { if (this.gridster.$options.compactType === CompactType.CompactUp) { this.checkCompactMovement('y', -1); } else if (this.gridster.$options.compactType === CompactType.CompactLeft) { this.checkCompactMovement('x', -1); } else if (this.gridster.$options.compactType === CompactType.CompactUpAndLeft) { this.checkCompactMovement('y', -1); this.checkCompactMovement('x', -1); } else if (this.gridster.$options.compactType === CompactType.CompactLeftAndUp) { this.checkCompactMovement('x', -1); this.checkCompactMovement('y', -1); } else if (this.gridster.$options.compactType === CompactType.CompactRight) { this.checkCompactMovement('x', 1); } else if (this.gridster.$options.compactType === CompactType.CompactUpAndRight) { this.checkCompactMovement('y', -1); this.checkCompactMovement('x', 1); } else if (this.gridster.$options.compactType === CompactType.CompactRightAndUp) { this.checkCompactMovement('x', 1); this.checkCompactMovement('y', -1); } else if (this.gridster.$options.compactType === CompactType.CompactDown) { this.checkCompactMovement('y', 1); } else if (this.gridster.$options.compactType === CompactType.CompactDownAndLeft) { this.checkCompactMovement('y', 1); this.checkCompactMovement('x', -1); } else if (this.gridster.$options.compactType === CompactType.CompactDownAndRight) { this.checkCompactMovement('y', 1); this.checkCompactMovement('x', 1); } else if (this.gridster.$options.compactType === CompactType.CompactLeftAndDown) { this.checkCompactMovement('x', -1); this.checkCompactMovement('y', 1); } else if (this.gridster.$options.compactType === CompactType.CompactRightAndDown) { this.checkCompactMovement('x', 1); this.checkCompactMovement('y', 1); } } } checkCompactItem(item) { if (this.gridster.$options.compactType !== CompactType.None) { if (this.gridster.$options.compactType === CompactType.CompactUp) { this.moveTillCollision(item, 'y', -1); } else if (this.gridster.$options.compactType === CompactType.CompactLeft) { this.moveTillCollision(item, 'x', -1); } else if (this.gridster.$options.compactType === CompactType.CompactUpAndLeft) { this.moveTillCollision(item, 'y', -1); this.moveTillCollision(item, 'x', -1); } else if (this.gridster.$options.compactType === CompactType.CompactLeftAndUp) { this.moveTillCollision(item, 'x', -1); this.moveTillCollision(item, 'y', -1); } else if (this.gridster.$options.compactType === CompactType.CompactUpAndRight) { this.moveTillCollision(item, 'y', -1); this.moveTillCollision(item, 'x', 1); } else if (this.gridster.$options.compactType === CompactType.CompactDown) { this.moveTillCollision(item, 'y', 1); } else if (this.gridster.$options.compactType === CompactType.CompactDownAndLeft) { this.moveTillCollision(item, 'y', 1); this.moveTillCollision(item, 'x', -1); } else if (this.gridster.$options.compactType === CompactType.CompactLeftAndDown) { this.moveTillCollision(item, 'x', -1); this.moveTillCollision(item, 'y', 1); } else if (this.gridster.$options.compactType === CompactType.CompactDownAndRight) { this.moveTillCollision(item, 'y', 1); this.moveTillCollision(item, 'x', 1); } else if (this.gridster.$options.compactType === CompactType.CompactRightAndDown) { this.moveTillCollision(item, 'x', 1); this.moveTillCollision(item, 'y', 1); } } } checkCompactMovement(direction, delta) { let widgetMoved = false; this.gridster.grid.forEach((widget) => { if (widget.$item.compactEnabled !== false) { const moved = this.moveTillCollision(widget.$item, direction, delta); if (moved) { widgetMoved = true; widget.item[direction] = widget.$item[direction]; widget.itemChanged(); } } }); if (widgetMoved) { this.checkCompact(); } } moveTillCollision(item, direction, delta) { item[direction] += delta; if (this.gridster.checkCollision(item)) { item[direction] -= delta; return false; } else { this.moveTillCollision(item, direction, delta); return true; } } } const GridsterConfigService = { gridType: GridType.Fit, // 'fit' will fit the items in the container without scroll; scale: 1, // scale param to zoom in/zoom out // 'scrollVertical' will fit on width and height of the items will be the same as the width // 'scrollHorizontal' will fit on height and width of the items will be the same as the height // 'fixed' will set the rows and columns dimensions based on fixedColWidth and fixedRowHeight options // 'verticalFixed' will set the rows to fixedRowHeight and columns width will fit the space available // 'horizontalFixed' will set the columns to fixedColWidth and rows height will fit the space available fixedColWidth: 250, // fixed col width for gridType: 'fixed' fixedRowHeight: 250, // fixed row height for gridType: 'fixed' keepFixedHeightInMobile: false, // keep the height from fixed gridType in mobile layout keepFixedWidthInMobile: false, // keep the width from fixed gridType in mobile layout setGridSize: false, // sets grid size depending on content compactType: CompactType.None, // compact items: 'none' | 'compactUp' | 'compactLeft' | 'compactUp&Left' | 'compactLeft&Up' mobileBreakpoint: 640, // if the screen is not wider that this, remove the grid layout and stack the items useBodyForBreakpoint: false, // whether to use the body width to determine the mobile breakpoint. Uses the element width when false. allowMultiLayer: false, defaultLayerIndex: 0, maxLayerIndex: 2, baseLayerIndex: 1, minCols: 1, // minimum amount of columns in the grid maxCols: 100, // maximum amount of columns in the grid minRows: 1, // minimum amount of rows in the grid maxRows: 100, // maximum amount of rows in the grid defaultItemCols: 1, // default width of an item in columns defaultItemRows: 1, // default height of an item in rows maxItemCols: 50, // max item number of cols maxItemRows: 50, // max item number of rows minItemCols: 1, // min item number of columns minItemRows: 1, // min item number of rows minItemArea: 1, // min item area: cols * rows maxItemArea: 2500, // max item area: cols * rows addEmptyRowsCount: 0, // add a number of extra empty rows at the end rowHeightRatio: 1, // row height ratio from column width margin: 10, // margin between grid items outerMargin: true, // if margins will apply to the sides of the container outerMarginTop: null, // override outer margin for grid outerMarginRight: null, // override outer margin for grid outerMarginBottom: null, // override outer margin for grid outerMarginLeft: null, // override outer margin for grid useTransformPositioning: true, // toggle between transform or top/left positioning of items scrollSensitivity: 10, // margin of the dashboard where to start scrolling scrollSpeed: 20, // how much to scroll each mouse move when in the scrollSensitivity zone initCallback: undefined, // callback to call after grid has initialized. Arguments: gridsterComponent destroyCallback: undefined, // callback to call after grid has destroyed. Arguments: gridsterComponent gridSizeChangedCallback: undefined, // callback to call after grid has changed size. Arguments: gridsterComponent itemChangeCallback: undefined, // callback to call for each item when is changes x, y, rows, cols. // Arguments: gridsterItem, gridsterItemComponent itemResizeCallback: undefined, // callback to call for each item when width/height changes. // Arguments: gridsterItem, gridsterItemComponent itemInitCallback: undefined, // callback to call for each item when is initialized. // Arguments: gridsterItem, gridsterItemComponent itemRemovedCallback: undefined, // callback to call for each item when is initialized. // Arguments: gridsterItem, gridsterItemComponent itemValidateCallback: undefined, // callback to call to validate item position/size. Return true if valid. // Arguments: gridsterItem enableEmptyCellClick: false, // enable empty cell click events enableEmptyCellContextMenu: false, // enable empty cell context menu (right click) events enableEmptyCellDrop: false, // enable empty cell drop events enableEmptyCellDrag: false, // enable empty cell drag events enableOccupiedCellDrop: false, // enable occupied cell drop events emptyCellClickCallback: undefined, // empty cell click callback emptyCellContextMenuCallback: undefined, // empty cell context menu (right click) callback emptyCellDropCallback: undefined, // empty cell drag drop callback. HTML5 Drag & Drop emptyCellDragCallback: undefined, // empty cell drag and create item like excel cell selection emptyCellDragMaxCols: 50, // limit empty cell drag max cols emptyCellDragMaxRows: 50, // limit empty cell drag max rows // Arguments: event, gridsterItem{x, y, rows: defaultItemRows, cols: defaultItemCols} ignoreMarginInRow: false, // ignore the gap between rows for items which span multiple rows (see #162, #224) draggable: { delayStart: 0, // milliseconds to delay the start of drag, useful for touch interaction enabled: false, // enable/disable draggable items ignoreContentClass: 'gridster-item-content', // default content class to ignore the drag event from ignoreContent: false, // if true drag will start only from elements from `dragHandleClass` dragHandleClass: 'drag-handler', // drag event only from this class. If `ignoreContent` is true. stop: undefined, // callback when dragging an item stops. Accepts Promise return to cancel/approve drag. start: undefined, // callback when dragging an item starts. // Arguments: item, gridsterItem, event dropOverItems: false, // enable drop items on top other item dropOverItemsCallback: undefined // callback on drop over another item // Arguments: source, target, gridComponent }, resizable: { delayStart: 0, // milliseconds to delay the start of resize, useful for touch interaction enabled: false, // enable/disable resizable items handles: { s: true, e: true, n: true, w: true, se: true, ne: true, sw: true, nw: true }, // resizable edges of an item stop: undefined, // callback when resizing an item stops. Accepts Promise return to cancel/approve resize. start: undefined // callback when resizing an item starts. // Arguments: item, gridsterItem, event }, swap: true, // allow items to switch position if drop on top of another swapWhileDragging: false, // allow items to switch position while dragging pushItems: false, // push items when resizing and dragging disablePushOnDrag: false, // disable push on drag disablePushOnResize: false, // disable push on resize pushDirections: { north: true, east: true, south: true, west: true }, // control the directions items are pushed pushResizeItems: false, // on resize of item will shrink adjacent items displayGrid: DisplayGrid.OnDragAndResize, // display background grid of rows and columns disableWindowResize: false, // disable the window on resize listener. This will stop grid to recalculate on window resize. disableWarnings: false, // disable console log warnings about misplacement of grid items scrollToNewItems: false, // scroll to new items placed in a scrollable view disableScrollHorizontal: false, // disable horizontal scrolling disableScrollVertical: false, // disable vertical scrolling enableBoundaryControl: false, // enable boundary control while dragging items disableAutoPositionOnConflict: false, // disable auto-position of items on conflict state, dirType: DirTypes.LTR // page direction, rtl=right to left ltr= left to right, if you use rtl language set dirType to rtl }; class GridsterUtils { // eslint-disable-next-line @typescript-eslint/no-explicit-any static merge(obj1, obj2, properties) { for (const p in obj2) { if (obj2[p] !== void 0 && properties.hasOwnProperty(p)) { if (typeof obj2[p] === 'object') { // create an empty object for the property if obj1 does not already have one. if (!(p in obj1)) { obj1[p] = {}; } obj1[p] = GridsterUtils.merge(obj1[p], obj2[p], properties[p]); } else { obj1[p] = obj2[p]; } } } return obj1; } // eslint-disable-next-line @typescript-eslint/no-explicit-any static checkTouchEvent(e) { if (e.clientX === undefined && e.touches) { if (e.touches && e.touches.length) { e.clientX = e.touches[0].clientX; e.clientY = e.touches[0].clientY; } else if (e.changedTouches && e.changedTouches.length) { e.clientX = e.changedTouches[0].clientX; e.clientY = e.changedTouches[0].clientY; } } } static checkContentClassForEvent(gridster, e) { if (gridster.$options.draggable.ignoreContent) { if (!GridsterUtils.checkDragHandleClass(e.target, e.currentTarget, gridster.$options.draggable.dragHandleClass, gridster.$options.draggable.ignoreContentClass)) { return true; } } else { if (GridsterUtils.checkContentClass(e.target, e.currentTarget, gridster.$options.draggable.ignoreContentClass)) { return true; } } return false; } static checkContentClassForEmptyCellClickEvent(gridster, e) { return (GridsterUtils.checkContentClass(e.target, e.currentTarget, gridster.$options.draggable.ignoreContentClass) || GridsterUtils.checkContentClass(e.target, e.currentTarget, gridster.$options.draggable.dragHandleClass)); } static checkDragHandleClass(target, current, dragHandleClass, ignoreContentClass) { if (!target || target === current) { return false; } if (target.hasAttribute('class')) { const classnames = target.getAttribute('class').split(' '); if (classnames.indexOf(dragHandleClass) > -1) { return true; } if (classnames.indexOf(ignoreContentClass) > -1) { return false; } } return GridsterUtils.checkDragHandleClass(target.parentNode, current, dragHandleClass, ignoreContentClass); } static checkContentClass(target, current, contentClass) { if (!target || target === current) { return false; } if (target.hasAttribute('class') && target.getAttribute('class').split(' ').indexOf(contentClass) > -1) { return true; } else { return GridsterUtils.checkContentClass(target.parentNode, current, contentClass); } } static compareItems(a, b) { if (a.y > b.y) { return -1; } else if (a.y < b.y) { return 1; } else if (a.x > b.x) { return -1; } else { return 1; } } } class GridsterEmptyCell { gridster; initialItem; removeEmptyCellClickListenerFn; removeEmptyCellTouchendListenerFn; removeEmptyCellContextMenuListenerFn; removeEmptyCellDropListenerFn; removeEmptyCellMousedownListenerFn; removeEmptyCellTouchstartListenerFn; removeWindowMousemoveListenerFn; removeWindowTouchmoveListenerFn; removeWindowMouseupListenerFn; removeWindowTouchendListenerFn; removeEmptyCellDragoverListenerFn; removeDocumentDragendListenerFn; constructor(gridster) { this.gridster = gridster; } destroy() { if (this.gridster.previewStyle) { this.gridster.previewStyle(); } this.gridster.movingItem = null; this.initialItem = this.gridster = null; if (this.removeDocumentDragendListenerFn) { this.removeDocumentDragendListenerFn(); this.removeDocumentDragendListenerFn = null; } } updateOptions() { if (this.gridster.$options.enableEmptyCellClick && !this.removeEmptyCellClickListenerFn && this.gridster.options.emptyCellClickCallback) { this.removeEmptyCellClickListenerFn = this.gridster.renderer.listen(this.gridster.el, 'click', this.emptyCellClickCb); this.removeEmptyCellTouchendListenerFn = this.gridster.renderer.listen(this.gridster.el, 'touchend', this.emptyCellClickCb); } else if (!this.gridster.$options.enableEmptyCellClick && this.removeEmptyCellClickListenerFn && this.removeEmptyCellTouchendListenerFn) { this.removeEmptyCellClickListenerFn(); this.removeEmptyCellTouchendListenerFn(); this.removeEmptyCellClickListenerFn = null; this.removeEmptyCellTouchendListenerFn = null; } if (this.gridster.$options.enableEmptyCellContextMenu && !this.removeEmptyCellContextMenuListenerFn && this.gridster.options.emptyCellContextMenuCallback) { this.removeEmptyCellContextMenuListenerFn = this.gridster.renderer.listen(this.gridster.el, 'contextmenu', this.emptyCellContextMenuCb); } else if (!this.gridster.$options.enableEmptyCellContextMenu && this.removeEmptyCellContextMenuListenerFn) { this.removeEmptyCellContextMenuListenerFn(); this.removeEmptyCellContextMenuListenerFn = null; } if (this.gridster.$options.enableEmptyCellDrop && !this.removeEmptyCellDropListenerFn && this.gridster.options.emptyCellDropCallback) { this.removeEmptyCellDropListenerFn = this.gridster.renderer.listen(this.gridster.el, 'drop', this.emptyCellDragDrop); this.gridster.zone.runOutsideAngular(() => { this.removeEmptyCellDragoverListenerFn = this.gridster.renderer.listen(this.gridster.el, 'dragover', this.emptyCellDragOver); }); this.removeDocumentDragendListenerFn = this.gridster.renderer.listen('document', 'dragend', () => { this.gridster.movingItem = null; this.gridster.previewStyle(); }); } else if (!this.gridster.$options.enableEmptyCellDrop && this.removeEmptyCellDropListenerFn && this.removeEmptyCellDragoverListenerFn && this.removeDocumentDragendListenerFn) { this.removeEmptyCellDropListenerFn(); this.removeEmptyCellDragoverListenerFn(); this.removeDocumentDragendListenerFn(); this.removeEmptyCellDragoverListenerFn = null; this.removeEmptyCellDropListenerFn = null; this.removeDocumentDragendListenerFn = null; } if (this.gridster.$options.enableEmptyCellDrag && !this.removeEmptyCellMousedownListenerFn && this.gridster.options.emptyCellDragCallback) { this.removeEmptyCellMousedownListenerFn = this.gridster.renderer.listen(this.gridster.el, 'mousedown', this.emptyCellMouseDown); this.removeEmptyCellTouchstartListenerFn = this.gridster.renderer.listen(this.gridster.el, 'touchstart', this.emptyCellMouseDown); } else if (!this.gridster.$options.enableEmptyCellDrag && this.removeEmptyCellMousedownListenerFn && this.removeEmptyCellTouchstartListenerFn) { this.removeEmptyCellMousedownListenerFn(); this.removeEmptyCellTouchstartListenerFn(); this.removeEmptyCellMousedownListenerFn = null; this.removeEmptyCellTouchstartListenerFn = null; } } emptyCellClickCb = (e) => { if (!this.gridster || this.gridster.movingItem || GridsterUtils.checkContentClassForEmptyCellClickEvent(this.gridster, e)) { return; } const item = this.getValidItemFromEvent(e); if (!item) { return; } if (this.gridster.options.emptyCellClickCallback) { this.gridster.options.emptyCellClickCallback(e, item); } this.gridster.cdRef.markForCheck(); }; emptyCellContextMenuCb = (e) => { if (this.gridster.movingItem || GridsterUtils.checkContentClassForEmptyCellClickEvent(this.gridster, e)) { return; } e.preventDefault(); e.stopPropagation(); const item = this.getValidItemFromEvent(e); if (!item) { return; } if (this.gridster.options.emptyCellContextMenuCallback) { this.gridster.options.emptyCellContextMenuCallback(e, item); } this.gridster.cdRef.markForCheck(); }; emptyCellDragDrop = (e) => { const item = this.getValidItemFromEvent(e); if (!item) { return; } if (this.gridster.options.emptyCellDropCallback) { this.gridster.options.emptyCellDropCallback(e, item); } this.gridster.cdRef.markForCheck(); }; emptyCellDragOver = (e) => { e.preventDefault(); e.stopPropagation(); const item = this.getValidItemFromEvent(e); if (item) { if (e.dataTransfer) { e.dataTransfer.dropEffect = 'move'; } this.gridster.movingItem = item; } else { if (e.dataTransfer) { e.dataTransfer.dropEffect = 'none'; } this.gridster.movingItem = null; } this.gridster.previewStyle(); }; emptyCellMouseDown = (e) => { if (GridsterUtils.checkContentClassForEmptyCellClickEvent(this.gridster, e)) { return; } e.preventDefault(); e.stopPropagation(); const item = this.getValidItemFromEvent(e); const leftMouseButtonCode = 1; if (!item || (e.buttons !== leftMouseButtonCode && !(e instanceof TouchEvent))) { return; } this.initialItem = item; this.gridster.movingItem = item; this.gridster.previewStyle(); this.gridster.zone.runOutsideAngular(() => { this.removeWindowMousemoveListenerFn = this.gridster.renderer.listen('window', 'mousemove', this.emptyCellMouseMove); this.removeWindowTouchmoveListenerFn = this.gridster.renderer.listen('window', 'touchmove', this.emptyCellMouseMove); }); this.removeWindowMouseupListenerFn = this.gridster.renderer.listen('window', 'mouseup', this.emptyCellMouseUp); this.removeWindowTouchendListenerFn = this.gridster.renderer.listen('window', 'touchend', this.emptyCellMouseUp); }; emptyCellMouseMove = (e) => { e.preventDefault(); e.stopPropagation(); const item = this.getValidItemFromEvent(e, this.initialItem); if (!item) { return; } this.gridster.movingItem = item; this.gridster.previewStyle(); }; emptyCellMouseUp = (e) => { this.removeWindowMousemoveListenerFn(); this.removeWindowTouchmoveListenerFn(); this.removeWindowMouseupListenerFn(); this.removeWindowTouchendListenerFn(); const item = this.getValidItemFromEvent(e, this.initialItem); if (item) { this.gridster.movingItem = item; } if (this.gridster.options.emptyCellDragCallback && this.gridster.movingItem) { this.gridster.options.emptyCellDragCallback(e, this.gridster.movingItem); } setTimeout(() => { this.initialItem = null; if (this.gridster) { this.gridster.movingItem = null; this.gridster.previewStyle(); } }); this.gridster.cdRef.markForCheck(); }; getPixelsX(e, rect) { const scale = this.gridster.options.scale; if (scale) { return ((e.clientX - rect.left) / scale + this.gridster.el.scrollLeft - this.gridster.gridRenderer.getLeftMargin()); } return (e.clientX + this.gridster.el.scrollLeft - rect.left - this.gridster.gridRenderer.getLeftMargin()); } getPixelsY(e, rect) { const scale = this.gridster.options.scale; if (scale) { return ((e.clientY - rect.top) / scale + this.gridster.el.scrollTop - this.gridster.gridRenderer.getTopMargin()); } return (e.clientY + this.gridster.el.scrollTop - rect.top - this.gridster.gridRenderer.getTopMargin()); } getValidItemFromEvent(e, oldItem) { e.preventDefault(); e.stopPropagation(); GridsterUtils.checkTouchEvent(e); const rect = this.gridster.el.getBoundingClientRect(); const x = this.getPixelsX(e, rect); const y = this.getPixelsY(e, rect); const item = { x: this.gridster.pixelsToPositionX(x, Math.floor, true), y: this.gridster.pixelsToPositionY(y, Math.floor, true), cols: this.gridster.$options.defaultItemCols, rows: this.gridster.$options.defaultItemRows }; if (oldItem) { item.cols = Math.min(Math.abs(oldItem.x - item.x) + 1, this.gridster.$options.emptyCellDragMaxCols); item.rows = Math.min(Math.abs(oldItem.y - item.y) + 1, this.gridster.$options.emptyCellDragMaxRows); if (oldItem.x < item.x) { item.x = oldItem.x; } else if (oldItem.x - item.x > this.gridster.$options.emptyCellDragMaxCols - 1) { item.x = this.gridster.movingItem ? this.gridster.movingItem.x : 0; } if (oldItem.y < item.y) { item.y = oldItem.y; } else if (oldItem.y - item.y > this.gridster.$options.emptyCellDragMaxRows - 1) { item.y = this.gridster.movingItem ? this.gridster.movingItem.y : 0; } } if (!this.gridster.$options.enableOccupiedCellDrop && this.gridster.checkCollision(item)) { return; } return item; } } class GridsterRenderer { gridster; /** * Caches the last grid column styles. * This improves the grid responsiveness by caching and reusing the last style object instead of creating a new one. */ lastGridColumnStyles = {}; /** * Caches the last grid column styles. * This improves the grid responsiveness by caching and reusing the last style object instead of creating a new one. */ lastGridRowStyles = {}; constructor(gridster) { this.gridster = gridster; } destroy() { this.gridster = null; } updateItem(el, item, renderer) { if (this.gridster.mobile) { this.clearCellPosition(renderer, el); if (this.gridster.$options.keepFixedHeightInMobile) { renderer.setStyle(el, 'height', (item.rows - 1) * this.gridster.$options.margin + item.rows * this.gridster.$options.fixedRowHeight + 'px'); } else { renderer.setStyle(el, 'height', (item.rows * this.gridster.curWidth) / item.cols + 'px'); } if (this.gridster.$options.keepFixedWidthInMobile) { renderer.setStyle(el, 'width', this.gridster.$options.fixedColWidth + 'px'); } else { renderer.setStyle(el, 'width', ''); } renderer.setStyle(el, 'margin-bottom', this.gridster.$options.margin + 'px'); renderer.setStyle(el, DirTypes.LTR ? 'margin-right' : 'margin-left', ''); } else { const x = Math.round(this.gridster.curColWidth * item.x); const y = Math.round(this.gridster.curRowHeight * item.y); const width = this.gridster.curColWidth * item.cols - this.gridster.$options.margin; const height = this.gridster.curRowHeight * item.rows - this.gridster.$options.margin; // set the cell style this.setCellPosition(renderer, el, x, y); renderer.setStyle(el, 'width', width + 'px'); renderer.setStyle(el, 'height', height + 'px'); let marginBottom = null; let marginRight = null; if (this.gridster.$options.outerMargin) { if (this.gridster.rows === item.rows + item.y) { if (this.gridster.$options.outerMarginBottom !== null) { marginBottom = this.gridster.$options.outerMarginBottom + 'px'; } else { marginBottom = this.gridster.$options.margin + 'px'; } } if (this.gridster.columns === item.cols + item.x) { if (this.gridster.$options.outerMarginBottom !== null) { marginRight = this.gridster.$options.outerMarginRight + 'px'; } else { marginRight = this.gridster.$options.margin + 'px'; } } } renderer.setStyle(el, 'margin-bottom', marginBottom); renderer.setStyle(el, DirTypes.LTR ? 'margin-right' : 'margin-left', marginRight); } } updateGridster() { let addClass = ''; let removeClass1 = ''; let removeClass2 = ''; let removeClass3 = ''; if (this.gridster.$options.gridType === GridType.Fit) { addClass = GridType.Fit; removeClass1 = GridType.ScrollVertical; removeClass2 = GridType.ScrollHorizontal; removeClass3 = GridType.Fixed; } else if (this.gridster.$options.gridType === GridType.ScrollVertical) { this.gridster.curRowHeight = this.gridster.curColWidth * this.gridster.$options.rowHeightRatio; addClass = GridType.ScrollVertical; removeClass1 = GridType.Fit; removeClass2 = GridType.ScrollHorizontal; removeClass3 = GridType.Fixed; } else if (this.gridster.$options.gridType === GridType.ScrollHorizontal) { const widthRatio = this.gridster.$options.rowHeightRatio; const calWidthRatio = widthRatio >= 1 ? widthRatio : widthRatio + 1; this.gridster.curColWidth = this.gridster.curRowHeight * calWidthRatio; addClass = GridType.ScrollHorizontal; removeClass1 = GridType.Fit; removeClass2 = GridType.ScrollVertical; removeClass3 = GridType.Fixed; } else if (this.gridster.$options.gridType === GridType.Fixed) { this.gridster.curColWidth = this.gridster.$options.fixedColWidth + (this.gridster.$options.ignoreMarginInRow ? 0 : this.gridster.$options.margin); this.gridster.curRowHeight = this.gridster.$options.fixedRowHeight + (this.gridster.$options.ignoreMarginInRow ? 0 : this.gridster.$options.margin); addClass = GridType.Fixed; removeClass1 = GridType.Fit; removeClass2 = GridType.ScrollVertical; removeClass3 = GridType.ScrollHorizontal; } else if (this.gridster.$options.gridType === GridType.VerticalFixed) { this.gridster.curRowHeight = this.gridster.$options.fixedRowHeight + (this.gridster.$options.ignoreMarginInRow ? 0 : this.gridster.$options.margin); addClass = GridType.ScrollVertical; removeClass1 = GridType.Fit; removeClass2 = GridType.ScrollHorizontal; removeClass3 = GridType.Fixed; } else if (this.gridster.$options.gridType === GridType.HorizontalFixed) { this.gridster.curColWidth = this.gridster.$options.fixedColWidth + (this.gridster.$options.ignoreMarginInRow ? 0 : this.gridster.$options.margin); addClass = GridType.ScrollHorizontal; removeClass1 = GridType.Fit; removeClass2 = GridType.ScrollVertical; removeClass3 = GridType.Fixed; } if (this.gridster.mobile || (this.gridster.$options.setGridSize && this.gridster.$options.gridType !== GridType.Fit)) { this.gridster.renderer.removeClass(this.gridster.el, addClass); } else { this.gridster.renderer.addClass(this.gridster.el, addClass); } this.gridster.renderer.removeClass(this.gridster.el, removeClass1); this.gridster.renderer.removeClass(this.gridster.el, removeClass2); this.gridster.renderer.removeClass(this.gridster.el, removeClass3); } getGridColumnStyle(i) { // generates the new style const newPos = { left: this.gridster.curColWidth * i, width: this.gridster.curColWidth - this.gridster.$options.margin, height: this.gridster.gridRows.length * this.gridster.curRowHeight - this.gridster.$options.margin, style: {} }; newPos.style = { ...this.getLeftPosition(newPos.left), width: newPos.width + 'px', height: newPos.height + 'px' }; // use the last cached style if it has same values as the generated one const last = this.lastGridColumnStyles[i]; if (last && last.left === newPos.left && last.width === newPos.width && last.height === newPos.height) { return last.style; } // cache and set new style this.lastGridColumnStyles[i] = newPos; return newPos.style; } getGridRowStyle(i) { // generates the new style const newPos = { top: this.gridster.curRowHeight * i, width: this.gridster.gridColumns.length * this.gridster.curColWidth + this.gridster.$options.margin, height: this.gridster.curRowHeight - this.gridster.$options.margin, style: {} }; newPos.style = { ...this.getTopPosition(newPos.top), width: newPos.width + 'px', height: newPos.height + 'px' }; // use the last cached style if it has same values as the generated one const last = this.lastGridRowStyles[i]; if (last && last.top === newPos.top && last.width === newPos.width && last.height === newPos.height) { return last.style; } // cache and set new style this.lastGridRowStyles[i] = newPos; return newPos.style; } getLeftPosition(d) { const dPosition = this.gridster.$options.dirType === DirTypes.RTL ? -d : d; if (this.gridster.$options.useTransformPositioning) { return { transform: 'translateX(' + dPosition + 'px)' }; } else { return { left: this.getLeftMargin() + dPosition + 'px' }; } } getTopPosition(d) { if (this.gridster.$options.useTransformPositioning) { return { transform: 'translateY(' + d + 'px)' }; } else { return { top: this.getTopMargin() + d + 'px' }; } } clearCellPosition(renderer, el) { if (this.gridster.$options.useTransformPositioning) { renderer.setStyle(el, 'transform', ''); } else { renderer.setStyle(el, 'top', ''); renderer.setStyle(el, 'left', ''); } } setCellPosition(renderer, el, x, y) { const xPosition = this.gridster.$options.dirType === DirTypes.RTL ? -x : x; if (this.gridster.$options.useTransformPositioning) { const transform = 'translate3d(' + xPosition + 'px, ' + y + 'px, 0)'; renderer.setStyle(el, 'transform', transform); } else { renderer.setStyle(el, 'left', this.getLeftMargin() + xPosition + 'px'); renderer.setStyle(el, 'top', this.getTopMargin() + y + 'px'); } } getLeftMargin() { if (this.gridster.$options.outerMargin) { if (this.gridster.$options.outerMarginLeft !== null) { return this.gridster.$options.outerMarginLeft; } else { return this.gridster.$options.margin; } } else { return 0; } } getTopMargin() { if (this.gridster.$options.outerMargin) { if (this.gridster.$options.outerMarginTop !== null) { return this.gridster.$options.outerMarginTop; } else { return this.gridster.$options.margin; } } else { return 0; } } } class GridsterPreviewComponent { renderer; previewStyle$; gridRenderer; el; sub; constructor(el, renderer) { this.renderer = renderer; this.el = el.nativeElement; } ngOnInit() { this.sub = this.previewStyle$.subscribe(options => this.previewStyle(options)); } ngOnDestroy() { if (this.sub) { this.sub.unsubscribe(); } } previewStyle(item) { if (item) { this.renderer.setStyle(this.el, 'display', 'block'); this.gridRenderer.updateItem(this.el, item, this.renderer); } else { this.renderer.setStyle(this.el, 'display', ''); } } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: GridsterPreviewComponent, deps: [{ token: i0.ElementRef }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.0.1", type: GridsterPreviewComponent, isStandalone: true, selector: "gridster-preview", inputs: { previewStyle$: "previewStyle$", gridRenderer: "gridRenderer" }, ngImport: i0, template: '', isInline: true, styles: ["gridster-preview{position:absolute;display:none;background:#00000026}\n"], encapsulation: i0.ViewEncapsulation.None }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.0.1", ngImport: i0, type: GridsterPreviewComponent, decorators: [{ type: Component, args: [{ selector: 'gridster-preview', template: '', encapsulation: ViewEncapsulation.None, standalone: true, styles: ["gridster-preview{position:absolute;display:none;background:#00000026}\n"] }] }], ctorParameters: () => [{ type: i0.ElementRef }, { type: i0.Renderer2 }], propDecorators: { previewStyle$: [{ type: Input }], gridRenderer: [{ type: Input }] } }); class GridsterComponent { renderer; cdRef; zone; options; movingItem; el; $options; mobile; curWidth; curHeight; grid; columns = 0; rows = 0; curColWidth; curRowHeight; gridColumns = []; gridRows = []; windowResize; dragInProgress; emptyCell; compact; gridRenderer; previewStyle$ = new EventEmitter(); calculateLayout$ = new Subject(); resize$ = new Subject(); destroy$ = new Subject(); constructor(el, renderer, cdRef, zone) { this.renderer = renderer; this.cdRef = cdRef; this.zone = zone; this.el = el.nativeElement; this.$options = JSON.parse(JSON.stringify(GridsterConfigService)); this.mobile = false; this.curWidth = 0; this.curHeight = 0; this.grid = []; this.curColWidth = 0; this.curRowHeight = 0; this.dragInProgress = false; this.emptyCell = new GridsterEmptyCell(this); this.compact = new GridsterCompact(this); this.gridRenderer = new GridsterRenderer(this); } // ------ Function for swapWhileDragging option // identical to checkCollision() except that here we add boundaries. static checkCollisionTwoItemsForSwaping(item, item2) { // if the cols or rows of the items are 1 , doesnt make any sense to set a boundary. Only if the item is bigger we set a boundary const horizontalBoundaryItem1 = item.cols === 1 ? 0 : 1; const horizontalBoundaryItem2 = item2.cols === 1 ? 0 : 1; const verticalBoundaryItem1 = item.rows === 1 ? 0 : 1; const verticalBoundaryItem2 = item2.rows === 1 ? 0 : 1; return (item.x + horizontalBoundaryItem1 < item2.x + item2.cols && item.x + item.cols > item2.x + horizontalBoundaryItem2 && item.y + verticalBoundaryItem1 < item2.y + item2.rows && item.y + item.rows > item2.y + verticalBoundaryItem2); } checkCollisionTwoItems(item, item2) { const collision = item.x < item2.x + item2.cols && item.x + item.cols > item2.x && item.y < item2.y + item2.rows && item.y + item.rows > item2.y; if (!collision) { return false; } if (!this.$options.allowMultiLayer) { return true; } const defaultLayerIndex = this.$options.defaultLayerIndex; const layerIndex = item.layerIndex === undefined ? defaultLayerIndex : item.layerIndex; const layerIndex2 = item2.layerIndex === undefined ? defaultLayerIndex : item2.layerIndex; return layerIndex === layerIndex2; } ngOnInit() { if (this.options.initCallback) { this.options.initCallback(this); } this.calculateLayout$ .pipe(debounceTime(0), takeUntil(this.destroy$)) .subscribe(() => this.calculateLayout()); this.resize$ .pipe( // Cancel previously scheduled DOM timer if `calculateLayout()` has been called // within this time range. switchMap(() => timer(100)), takeUntil(this.destroy$)) .subscribe(() => this.resize()); } ngOnChanges(changes) { if (changes.options) { this.setOptions(); this.options.api = { optionsChanged: this.optionsChanged, resize: this.onResize, getNextPossiblePosition: this.getNextPossiblePosition, getFirstPossiblePosition: this.getFirstPossiblePosition, getLastPossiblePosition: this.getLastPossiblePosition, getItemComponent: (item) => this.getItemComponent(item) }; this.columns = this.$options.minCols; this.rows = this.$options.minRows + this.$options.addEmptyRowsCount; this.setGridSize(); this.calculateLayout(); } } resize() { let height; let width; if (this.$options.gridType === 'fit' && !this.mobile) { width = this.el.offsetWidth; height = this.el.offsetHeight; } else { width = this.el.clientWidth; height = this.el.clientHeight; } if ((width !== this.curWidth || height !== this.curHeight) && this.checkIfToResize()) { this.onResize(); } } setOptions() { this.$options = GridsterUtils.merge(this.$options, this.options, this.$options); if (!this.$options.disableWindowResize && !this.windowResize) { this.windowResize = this.renderer.listen('window', 'resize', this.onResize); } else if (this.$options.disableWindowResize && this.windowResize) { this.windowResize(); this.windowResize = null; } this.emptyCell.updateOptions(); } optionsChanged = () => { this.setOptions(); let widgetsIndex = this.grid.length - 1; let widget; for (; widgetsIndex >= 0; widgetsIndex--) { widget = this.grid[widgetsIndex]; widget.updateOptions(); } this.calculateLayout(); }; ngOnDestroy() { this.destroy$.next(); this.previewStyle$.complete(); if (this.windowResize) { this.windowResize(); } if (this.options && this.options.destroyCallback) { this.options.destroyCallback(this); } if (this.options && this.options.api) { this.options.api.resize = undefined; this.options.api.optionsChanged = undefined; this.options.api.getNextPossiblePosition = undefined; this.options.api = undefined; } this.emptyCell.destroy(); this.emptyCell = null; this.compact.destroy(); this.compact = null; } onResize = () => { if (this.el.clientWidth) { if (this.options.setGridSize) { // reset width/height so the size is recalculated afterwards this.renderer.setStyle(this.el, 'width', ''); this.renderer.setStyle(this.el, 'height', ''); } this.setGridSize(); this.calculateLayout(); } }; checkIfToResize() { const clientWidth = this.el.clientWidth; const offsetWidth = this.el.offsetWidth; const scrollWidth = this.el.scrollWidth; const clientHeight = this.el.clientHeight; const offsetHeight = this.el.offsetHeight; const scrollHeight = this.el.scrollHeight; const verticalScrollPresent = clientWidth < offsetWidth && scrollHeight > offsetHeight && scrollHeight - offsetHeight < offsetWidth - clientWidth; const horizontalScrollPresent = clientHeight < offsetHeight && scrollWidth > offsetWidth && scrollWidth - offsetWidth < offsetHeight