angular-gridster2
Version:
Angular gridster 2
1,113 lines (1,105 loc) • 160 kB
JavaScript
import { NgStyle } from '@angular/common';
import * as i0 from '@angular/core';
import { inject, ElementRef, Renderer2, ViewEncapsulation, ChangeDetectionStrategy, Component, ChangeDetectorRef, NgZone, viewChild, input, computed, effect, signal, output, untracked } 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["CompactGrid"] = "compactGrid";
})(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() {
const $options = this.gridster.$options();
if ($options.compactType !== CompactType.None) {
if ($options.compactType === CompactType.CompactUp) {
this.checkCompactMovement('y', -1);
}
else if ($options.compactType === CompactType.CompactLeft) {
this.checkCompactMovement('x', -1);
}
else if ($options.compactType === CompactType.CompactUpAndLeft) {
this.checkCompactMovement('y', -1);
this.checkCompactMovement('x', -1);
}
else if ($options.compactType === CompactType.CompactLeftAndUp) {
this.checkCompactMovement('x', -1);
this.checkCompactMovement('y', -1);
}
else if ($options.compactType === CompactType.CompactRight) {
this.checkCompactMovement('x', 1);
}
else if ($options.compactType === CompactType.CompactUpAndRight) {
this.checkCompactMovement('y', -1);
this.checkCompactMovement('x', 1);
}
else if ($options.compactType === CompactType.CompactRightAndUp) {
this.checkCompactMovement('x', 1);
this.checkCompactMovement('y', -1);
}
else if ($options.compactType === CompactType.CompactDown) {
this.checkCompactMovement('y', 1);
}
else if ($options.compactType === CompactType.CompactDownAndLeft) {
this.checkCompactMovement('y', 1);
this.checkCompactMovement('x', -1);
}
else if ($options.compactType === CompactType.CompactDownAndRight) {
this.checkCompactMovement('y', 1);
this.checkCompactMovement('x', 1);
}
else if ($options.compactType === CompactType.CompactLeftAndDown) {
this.checkCompactMovement('x', -1);
this.checkCompactMovement('y', 1);
}
else if ($options.compactType === CompactType.CompactRightAndDown) {
this.checkCompactMovement('x', 1);
this.checkCompactMovement('y', 1);
}
else if ($options.compactType === CompactType.CompactGrid) {
this.checkCompactGrid();
}
}
}
checkCompactItem(item) {
const $options = this.gridster.$options();
if ($options.compactType !== CompactType.None) {
if ($options.compactType === CompactType.CompactUp) {
this.moveTillCollision(item, 'y', -1);
}
else if ($options.compactType === CompactType.CompactLeft) {
this.moveTillCollision(item, 'x', -1);
}
else if ($options.compactType === CompactType.CompactUpAndLeft) {
this.moveTillCollision(item, 'y', -1);
this.moveTillCollision(item, 'x', -1);
}
else if ($options.compactType === CompactType.CompactLeftAndUp) {
this.moveTillCollision(item, 'x', -1);
this.moveTillCollision(item, 'y', -1);
}
else if ($options.compactType === CompactType.CompactUpAndRight) {
this.moveTillCollision(item, 'y', -1);
this.moveTillCollision(item, 'x', 1);
}
else if ($options.compactType === CompactType.CompactDown) {
this.moveTillCollision(item, 'y', 1);
}
else if ($options.compactType === CompactType.CompactDownAndLeft) {
this.moveTillCollision(item, 'y', 1);
this.moveTillCollision(item, 'x', -1);
}
else if ($options.compactType === CompactType.CompactLeftAndDown) {
this.moveTillCollision(item, 'x', -1);
this.moveTillCollision(item, 'y', 1);
}
else if ($options.compactType === CompactType.CompactDownAndRight) {
this.moveTillCollision(item, 'y', 1);
this.moveTillCollision(item, 'x', 1);
}
else if ($options.compactType === CompactType.CompactRightAndDown) {
this.moveTillCollision(item, 'x', 1);
this.moveTillCollision(item, 'y', 1);
}
else if ($options.compactType === CompactType.CompactGrid) {
this.moveToGridPosition(item);
}
}
}
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;
}
}
checkCompactGrid() {
// Sort items by their current position (top to bottom, left to right)
const sortedItems = this.gridster.grid
.filter((widget) => widget.$item().compactEnabled !== false)
.sort((a, b) => {
if (a.$item().y !== b.$item().y) {
return a.$item().y - b.$item().y;
}
return a.$item().x - b.$item().x;
});
// Reposition all items in a grid-like manner
let currentY = 0;
let currentX = 0;
let maxYInRow = 0;
sortedItems.forEach((widget) => {
const item = widget.$item();
// Check if item fits in current row
if (currentX + item.cols > this.gridster.columns) {
// Move to next row
currentY = maxYInRow;
currentX = 0;
maxYInRow = currentY;
}
// Position item
const oldX = item.x;
const oldY = item.y;
item.x = currentX;
item.y = currentY;
// Update widget if position changed
if (oldX !== item.x || oldY !== item.y) {
widget.item().x = item.x;
widget.item().y = item.y;
widget.itemChanged();
}
// Update position for next item
currentX += item.cols;
maxYInRow = Math.max(maxYInRow, currentY + item.rows);
});
}
moveToGridPosition(item) {
// Find the next available position in grid layout
let currentY = 0;
let currentX = 0;
let maxYInRow = 0;
// Sort existing items to find occupied positions
const sortedItems = this.gridster.grid
.filter((widget) => widget.$item() !== item)
.sort((a, b) => {
if (a.$item().y !== b.$item().y) {
return a.$item().y - b.$item().y;
}
return a.$item().x - b.$item().x;
});
// Find the next available position
for (const widget of sortedItems) {
const existingItem = widget.$item();
// Check if we need to move to next row
if (currentX + existingItem.cols > this.gridster.columns) {
currentY = maxYInRow;
currentX = 0;
maxYInRow = currentY;
}
// Check if current item overlaps with the position we want to place our item
if (currentY < existingItem.y + existingItem.rows &&
currentY + item.rows > existingItem.y &&
currentX < existingItem.x + existingItem.cols &&
currentX + item.cols > existingItem.x) {
// Move to position after this item
currentX = existingItem.x + existingItem.cols;
currentY = existingItem.y;
maxYInRow = Math.max(maxYInRow, currentY + existingItem.rows);
}
else {
// Update position for next iteration
currentX += existingItem.cols;
maxYInRow = Math.max(maxYInRow, currentY + existingItem.rows);
}
}
// Check if item fits in current row
if (currentX + item.cols > this.gridster.columns) {
currentY = maxYInRow;
currentX = 0;
}
// Set the position
item.x = currentX;
item.y = currentY;
}
}
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
itemAspectRatio: null, // set a fixed aspect ratio for an item to have in cols/rows e.g. 1/1 or 4/3 or 16/9
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) {
const $options = gridster.$options();
if ($options.draggable.ignoreContent) {
if (!GridsterUtils.checkDragHandleClass(e.target, e.currentTarget, $options.draggable.dragHandleClass, $options.draggable.ignoreContentClass)) {
return true;
}
}
else {
if (GridsterUtils.checkContentClass(e.target, e.currentTarget, $options.draggable.ignoreContentClass)) {
return true;
}
}
return false;
}
static checkContentClassForEmptyCellClickEvent(gridster, e) {
const $options = gridster.$options();
return (GridsterUtils.checkContentClass(e.target, e.currentTarget, $options.draggable.ignoreContentClass) ||
GridsterUtils.checkContentClass(e.target, e.currentTarget, $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() {
const options = this.gridster.options();
const $options = this.gridster.$options();
if ($options.enableEmptyCellClick && !this.removeEmptyCellClickListenerFn && 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 (!$options.enableEmptyCellClick && this.removeEmptyCellClickListenerFn && this.removeEmptyCellTouchendListenerFn) {
this.removeEmptyCellClickListenerFn();
this.removeEmptyCellTouchendListenerFn();
this.removeEmptyCellClickListenerFn = null;
this.removeEmptyCellTouchendListenerFn = null;
}
if ($options.enableEmptyCellContextMenu && !this.removeEmptyCellContextMenuListenerFn && options.emptyCellContextMenuCallback) {
this.removeEmptyCellContextMenuListenerFn = this.gridster.renderer.listen(this.gridster.el, 'contextmenu', this.emptyCellContextMenuCb);
}
else if (!$options.enableEmptyCellContextMenu && this.removeEmptyCellContextMenuListenerFn) {
this.removeEmptyCellContextMenuListenerFn();
this.removeEmptyCellContextMenuListenerFn = null;
}
if ($options.enableEmptyCellDrop && !this.removeEmptyCellDropListenerFn && 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 (!$options.enableEmptyCellDrop &&
this.removeEmptyCellDropListenerFn &&
this.removeEmptyCellDragoverListenerFn &&
this.removeDocumentDragendListenerFn) {
this.removeEmptyCellDropListenerFn();
this.removeEmptyCellDragoverListenerFn();
this.removeDocumentDragendListenerFn();
this.removeEmptyCellDragoverListenerFn = null;
this.removeEmptyCellDropListenerFn = null;
this.removeDocumentDragendListenerFn = null;
}
if ($options.enableEmptyCellDrag && !this.removeEmptyCellMousedownListenerFn && 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 (!$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;
}
const options = this.gridster.options();
if (options.emptyCellClickCallback) {
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;
}
const options = this.gridster.options();
if (options.emptyCellContextMenuCallback) {
options.emptyCellContextMenuCallback(e, item);
}
this.gridster.cdRef.markForCheck();
};
emptyCellDragDrop = (e) => {
const item = this.getValidItemFromEvent(e);
if (!item) {
return;
}
const options = this.gridster.options();
if (options.emptyCellDropCallback) {
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;
}
const options = this.gridster.options();
if (options.emptyCellDragCallback && this.gridster.movingItem) {
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 $options = this.gridster.$options();
const item = {
x: this.gridster.pixelsToPositionX(x, Math.floor, true),
y: this.gridster.pixelsToPositionY(y, Math.floor, true),
cols: $options.defaultItemCols,
rows: $options.defaultItemRows
};
if (oldItem) {
item.cols = Math.min(Math.abs(oldItem.x - item.x) + 1, $options.emptyCellDragMaxCols);
item.rows = Math.min(Math.abs(oldItem.y - item.y) + 1, $options.emptyCellDragMaxRows);
if (oldItem.x < item.x) {
item.x = oldItem.x;
}
else if (oldItem.x - item.x > $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 > $options.emptyCellDragMaxRows - 1) {
item.y = this.gridster.movingItem ? this.gridster.movingItem.y : 0;
}
}
if (!$options.enableOccupiedCellDrop && this.gridster.checkCollision(item)) {
return;
}
return item;
}
}
class GridsterPreview {
el = inject(ElementRef).nativeElement;
gridster = inject(Gridster);
renderer = inject(Renderer2);
previewStyle(item) {
if (item) {
this.renderer.setStyle(this.el, 'display', 'block');
this.gridster.gridRenderer.updateItem(this.el, item, this.renderer);
}
else {
this.renderer.setStyle(this.el, 'display', '');
}
}
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: GridsterPreview, deps: [], target: i0.ɵɵFactoryTarget.Component });
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.0.1", type: GridsterPreview, isStandalone: true, selector: "gridster-preview", ngImport: i0, template: '', isInline: true, styles: ["gridster-preview{position:absolute;display:none;background:#00000026}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush, encapsulation: i0.ViewEncapsulation.None });
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.0.1", ngImport: i0, type: GridsterPreview, decorators: [{
type: Component,
args: [{ selector: 'gridster-preview', template: '', changeDetection: ChangeDetectionStrategy.OnPush, encapsulation: ViewEncapsulation.None, styles: ["gridster-preview{position:absolute;display:none;background:#00000026}\n"] }]
}] });
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;
}
updateItem(el, item, renderer) {
const $options = this.gridster.$options();
if (this.gridster.mobile) {
this.clearCellPosition(renderer, el);
if ($options.keepFixedHeightInMobile) {
renderer.setStyle(el, 'height', (item.rows - 1) * $options.margin + item.rows * $options.fixedRowHeight + 'px');
}
else {
renderer.setStyle(el, 'height', (item.rows * this.gridster.curWidth) / item.cols + 'px');
}
if ($options.keepFixedWidthInMobile) {
renderer.setStyle(el, 'width', $options.fixedColWidth + 'px');
}
else {
renderer.setStyle(el, 'width', '');
}
renderer.setStyle(el, 'margin-bottom', $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 - $options.margin;
const height = this.gridster.curRowHeight * item.rows - $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 ($options.outerMargin) {
if (this.gridster.rows === item.rows + item.y) {
if ($options.outerMarginBottom !== null) {
marginBottom = $options.outerMarginBottom + 'px';
}
else {
marginBottom = $options.margin + 'px';
}
}
if (this.gridster.columns === item.cols + item.x) {
if ($options.outerMarginBottom !== null) {
marginRight = $options.outerMarginRight + 'px';
}
else {
marginRight = $options.margin + 'px';
}
}
}
renderer.setStyle(el, 'margin-bottom', marginBottom);
renderer.setStyle(el, $options.dirType === DirTypes.LTR ? 'margin-right' : 'margin-left', marginRight);
}
}
updateGridster() {
const $options = this.gridster.$options();
let addClass = '';
let removeClass1 = '';
let removeClass2 = '';
let removeClass3 = '';
if ($options.gridType === GridType.Fit) {
addClass = GridType.Fit;
removeClass1 = GridType.ScrollVertical;
removeClass2 = GridType.ScrollHorizontal;
removeClass3 = GridType.Fixed;
}
else if ($options.gridType === GridType.ScrollVertical) {
this.gridster.curRowHeight = this.gridster.curColWidth * $options.rowHeightRatio;
addClass = GridType.ScrollVertical;
removeClass1 = GridType.Fit;
removeClass2 = GridType.ScrollHorizontal;
removeClass3 = GridType.Fixed;
}
else if ($options.gridType === GridType.ScrollHorizontal) {
const widthRatio = $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 ($options.gridType === GridType.Fixed) {
this.gridster.curColWidth = $options.fixedColWidth + ($options.ignoreMarginInRow ? 0 : $options.margin);
this.gridster.curRowHeight = $options.fixedRowHeight + ($options.ignoreMarginInRow ? 0 : $options.margin);
addClass = GridType.Fixed;
removeClass1 = GridType.Fit;
removeClass2 = GridType.ScrollVertical;
removeClass3 = GridType.ScrollHorizontal;
}
else if ($options.gridType === GridType.VerticalFixed) {
this.gridster.curRowHeight = $options.fixedRowHeight + ($options.ignoreMarginInRow ? 0 : $options.margin);
addClass = GridType.ScrollVertical;
removeClass1 = GridType.Fit;
removeClass2 = GridType.ScrollHorizontal;
removeClass3 = GridType.Fixed;
}
else if ($options.gridType === GridType.HorizontalFixed) {
this.gridster.curColWidth = $options.fixedColWidth + ($options.ignoreMarginInRow ? 0 : $options.margin);
addClass = GridType.ScrollHorizontal;
removeClass1 = GridType.Fit;
removeClass2 = GridType.ScrollVertical;
removeClass3 = GridType.Fixed;
}
if (this.gridster.mobile || ($options.setGridSize && $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) {
const margin = this.gridster.$options().margin;
// generates the new style
const newPos = {
left: this.gridster.curColWidth * i,
width: this.gridster.curColWidth - margin,
height: this.gridster.gridRows.length * this.gridster.curRowHeight - 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) {
const margin = this.gridster.$options().margin;
// generates the new style
const newPos = {
top: this.gridster.curRowHeight * i,
width: this.gridster.gridColumns.length * this.gridster.curColWidth + margin,
height: this.gridster.curRowHeight - 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 $options = this.gridster.$options();
const dPosition = $options.dirType === DirTypes.RTL ? -d : d;
if ($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 $options = this.gridster.$options();
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() {
const $options = this.gridster.$options();
if ($options.outerMargin) {
if ($options.outerMarginLeft !== null) {
return $options.outerMarginLeft;
}
else {
return $options.margin;
}
}
else {
return 0;
}
}
getTopMargin() {
const $options = this.gridster.$options();
if ($options.outerMargin) {
if ($options.outerMarginTop !== null) {
return $options.outerMarginTop;
}
else {
return $options.margin;
}
}
else {
return 0;
}
}
}
class Gridster {
renderer = inject(Renderer2);
cdRef = inject(ChangeDetectorRef);
zone = inject(NgZone);
elRef = inject(ElementRef);
api = {
calculateLayout: () => this.calculateLayout(),
resize: () => this.onResize(),
getNextPossiblePosition: (newItem, startingFrom) => this.getNextPossiblePosition(newItem, startingFrom),
getFirstPossiblePosition: (item) => this.getFirstPossiblePosition(item),
getLastPossiblePosition: (item) => this.getLastPossiblePosition(item),
getItemComponent: (item) => this.getItemComponent(item)
};
gridsterPreview = viewChild.required(GridsterPreview);
options = input.required({ ...(ngDevMode ? { debugName: "options" } : {}) });
$options = computed(() => GridsterUtils.merge(JSON.parse(JSON.stringify(GridsterConfigService)), this.options(), GridsterConfigService), { ...(ngDevMode ? { debugName: "$options" } : {}) });
movingItem;
el = this.elRef.nativeElement;
mobile = false;
curWidth = 0;
curHeight = 0;
grid = [];
columns = 0;
rows = 0;
curColWidth = 0;
curRowHeight = 0;
gridColumns = [];
gridRows = [];
windowResize;
dragInProgress = false;
emptyCell = new GridsterEmptyCell(this);
compact = new GridsterCompact(this);
gridRenderer = new GridsterRenderer(this);
calculateLayout$ = new Subject();
resize$ = new Subject();
destroy$ = new Subject();
constructor() {
effect(() => {
const $options = this.$options();
if (!$options.disableWindowResize && !this.windowResize) {
this.windowResize = this.renderer.listen('window', 'resize', this.onResize);
}
else if ($options.disableWindowResize && this.windowResize) {
this.windowResize();
this.windowResize = null;
}
this.emptyCell.updateOptions();
this.columns = $options.minCols;
this.rows = $options.minRows + $options.addEmptyRowsCount;
this.setGridSize();
this.calculateLayout();
});
}
// ------ 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 , doesn't 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() {
const options = this.options();
if (options.initCallback) {
options.initCallback(this, this.api);
}
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());
}
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();
}
}
ngOnDestroy() {
this.destroy$.next();
if (this.windowResize) {
this.windowResize();
}
const options = this.options();
if (options && options.destroyCallback) {
options.destroyCallback(this);
}
this.emptyCell.destroy();
this.emptyCell = null;
this.compact.destroy();
this.compact = null;
}
onResize = () => {
if (thi