UNPKG

angular-grid-layout-ngx13

Version:

Grid Layout with draggable and resizable items for Angular

1 lines 153 kB
{"version":3,"file":"angular-grid-layout-ngx13.mjs","sources":["../../../projects/angular-grid-layout/src/lib/utils/react-grid-layout.utils.ts","../../../projects/angular-grid-layout/src/lib/utils/passive-listeners.ts","../../../projects/angular-grid-layout/src/lib/utils/pointer.utils.ts","../../../projects/angular-grid-layout/src/lib/utils/grid.utils.ts","../../../projects/angular-grid-layout/src/lib/directives/drag-handle.ts","../../../projects/angular-grid-layout/src/lib/directives/resize-handle.ts","../../../projects/angular-grid-layout/src/lib/grid.definitions.ts","../../../projects/angular-grid-layout/src/lib/utils/operators.ts","../../../projects/angular-grid-layout/src/lib/coercion/boolean-property.ts","../../../projects/angular-grid-layout/src/lib/coercion/number-property.ts","../../../projects/angular-grid-layout/src/lib/grid.service.ts","../../../projects/angular-grid-layout/src/lib/grid-item/grid-item.component.ts","../../../projects/angular-grid-layout/src/lib/grid-item/grid-item.component.html","../../../projects/angular-grid-layout/src/lib/utils/client-rect.ts","../../../projects/angular-grid-layout/src/lib/utils/scroll.ts","../../../projects/angular-grid-layout/src/lib/grid.component.ts","../../../projects/angular-grid-layout/src/lib/grid.component.html","../../../projects/angular-grid-layout/src/lib/grid.module.ts","../../../projects/angular-grid-layout/src/public-api.ts","../../../projects/angular-grid-layout/src/angular-grid-layout-ngx13.ts"],"sourcesContent":["\n/**\n * IMPORTANT:\n * This utils are taken from the project: https://github.com/STRML/react-grid-layout.\n * The code should be as less modified as possible for easy maintenance.\n */\n\n// Disable lint since we don't want to modify this code\n// tslint:disable\nexport type LayoutItem = {\n w: number;\n h: number;\n x: number;\n y: number;\n id: string;\n minW?: number;\n minH?: number;\n maxW?: number;\n maxH?: number;\n moved?: boolean;\n static?: boolean;\n isDraggable?: boolean | null | undefined;\n isResizable?: boolean | null | undefined;\n};\nexport type Layout = Array<LayoutItem>;\nexport type Position = {\n left: number;\n top: number;\n width: number;\n height: number;\n};\nexport type ReactDraggableCallbackData = {\n node: HTMLElement;\n x?: number;\n y?: number;\n deltaX: number;\n deltaY: number;\n lastX?: number;\n lastY?: number;\n};\n\nexport type PartialPosition = { left: number; top: number };\nexport type DroppingPosition = { x: number; y: number; e: Event };\nexport type Size = { width: number; height: number };\nexport type GridDragEvent = {\n e: Event;\n node: HTMLElement;\n newPosition: PartialPosition;\n};\nexport type GridResizeEvent = { e: Event; node: HTMLElement; size: Size };\nexport type DragOverEvent = MouseEvent & {\n nativeEvent: {\n layerX: number;\n layerY: number;\n target: {\n className: String;\n };\n };\n};\n\n//type REl = ReactElement<any>;\n//export type ReactChildren = ReactChildrenArray<REl>;\n\n// All callbacks are of the signature (layout, oldItem, newItem, placeholder, e).\nexport type EventCallback = (\n arg0: Layout,\n oldItem: LayoutItem | null | undefined,\n newItem: LayoutItem | null | undefined,\n placeholder: LayoutItem | null | undefined,\n arg4: Event,\n arg5: HTMLElement | null | undefined,\n) => void;\nexport type CompactType = ('horizontal' | 'vertical') | null | undefined;\n\nconst DEBUG = false;\n\n/**\n * Return the bottom coordinate of the layout.\n *\n * @param {Array} layout Layout array.\n * @return {Number} Bottom coordinate.\n */\nexport function bottom(layout: Layout): number {\n let max = 0,\n bottomY;\n for (let i = 0, len = layout.length; i < len; i++) {\n bottomY = layout[i].y + layout[i].h;\n if (bottomY > max) {\n max = bottomY;\n }\n }\n return max;\n}\n\nexport function cloneLayout(layout: Layout): Layout {\n const newLayout = Array(layout.length);\n for (let i = 0, len = layout.length; i < len; i++) {\n newLayout[i] = cloneLayoutItem(layout[i]);\n }\n return newLayout;\n}\n\n// Fast path to cloning, since this is monomorphic\n/** NOTE: This code has been modified from the original source */\nexport function cloneLayoutItem(layoutItem: LayoutItem): LayoutItem {\n const clonedLayoutItem: LayoutItem = {\n w: layoutItem.w,\n h: layoutItem.h,\n x: layoutItem.x,\n y: layoutItem.y,\n id: layoutItem.id,\n moved: !!layoutItem.moved,\n static: !!layoutItem.static,\n };\n\n if (layoutItem.minW !== undefined) { clonedLayoutItem.minW = layoutItem.minW;}\n if (layoutItem.maxW !== undefined) { clonedLayoutItem.maxW = layoutItem.maxW;}\n if (layoutItem.minH !== undefined) { clonedLayoutItem.minH = layoutItem.minH;}\n if (layoutItem.maxH !== undefined) { clonedLayoutItem.maxH = layoutItem.maxH;}\n // These can be null\n if (layoutItem.isDraggable !== undefined) { clonedLayoutItem.isDraggable = layoutItem.isDraggable;}\n if (layoutItem.isResizable !== undefined) { clonedLayoutItem.isResizable = layoutItem.isResizable;}\n\n return clonedLayoutItem;\n}\n\n/**\n * Given two layoutitems, check if they collide.\n */\nexport function collides(l1: LayoutItem, l2: LayoutItem): boolean {\n if (l1.id === l2.id) {\n return false;\n } // same element\n if (l1.x + l1.w <= l2.x) {\n return false;\n } // l1 is left of l2\n if (l1.x >= l2.x + l2.w) {\n return false;\n } // l1 is right of l2\n if (l1.y + l1.h <= l2.y) {\n return false;\n } // l1 is above l2\n if (l1.y >= l2.y + l2.h) {\n return false;\n } // l1 is below l2\n return true; // boxes overlap\n}\n\n/**\n * Given a layout, compact it. This involves going down each y coordinate and removing gaps\n * between items.\n *\n * @param {Array} layout Layout.\n * @param {Boolean} verticalCompact Whether or not to compact the layout\n * vertically.\n * @return {Array} Compacted Layout.\n */\nexport function compact(\n layout: Layout,\n compactType: CompactType,\n cols: number,\n): Layout {\n // Statics go in the compareWith array right away so items flow around them.\n const compareWith = getStatics(layout);\n // We go through the items by row and column.\n const sorted = sortLayoutItems(layout, compactType);\n // Holding for new items.\n const out = Array(layout.length);\n\n for (let i = 0, len = sorted.length; i < len; i++) {\n let l = cloneLayoutItem(sorted[i]);\n\n // Don't move static elements\n if (!l.static) {\n l = compactItem(compareWith, l, compactType, cols, sorted);\n\n // Add to comparison array. We only collide with items before this one.\n // Statics are already in this array.\n compareWith.push(l);\n }\n\n // Add to output array to make sure they still come out in the right order.\n out[layout.indexOf(sorted[i])] = l;\n\n // Clear moved flag, if it exists.\n l.moved = false;\n }\n\n return out;\n}\n\nconst heightWidth = {x: 'w', y: 'h'};\n\n/**\n * Before moving item down, it will check if the movement will cause collisions and move those items down before.\n */\nfunction resolveCompactionCollision(\n layout: Layout,\n item: LayoutItem,\n moveToCoord: number,\n axis: 'x' | 'y',\n) {\n const sizeProp = heightWidth[axis];\n item[axis] += 1;\n const itemIndex = layout\n .map(layoutItem => {\n return layoutItem.id;\n })\n .indexOf(item.id);\n\n // Go through each item we collide with.\n for (let i = itemIndex + 1; i < layout.length; i++) {\n const otherItem = layout[i];\n // Ignore static items\n if (otherItem.static) {\n continue;\n }\n\n // Optimization: we can break early if we know we're past this el\n // We can do this b/c it's a sorted layout\n if (otherItem.y > item.y + item.h) {\n break;\n }\n\n if (collides(item, otherItem)) {\n resolveCompactionCollision(\n layout,\n otherItem,\n moveToCoord + item[sizeProp],\n axis,\n );\n }\n }\n\n item[axis] = moveToCoord;\n}\n\n/**\n * Compact an item in the layout.\n */\nexport function compactItem(\n compareWith: Layout,\n l: LayoutItem,\n compactType: CompactType,\n cols: number,\n fullLayout: Layout,\n): LayoutItem {\n const compactV = compactType === 'vertical';\n const compactH = compactType === 'horizontal';\n if (compactV) {\n // Bottom 'y' possible is the bottom of the layout.\n // This allows you to do nice stuff like specify {y: Infinity}\n // This is here because the layout must be sorted in order to get the correct bottom `y`.\n l.y = Math.min(bottom(compareWith), l.y);\n // Move the element up as far as it can go without colliding.\n while (l.y > 0 && !getFirstCollision(compareWith, l)) {\n l.y--;\n }\n } else if (compactH) {\n l.y = Math.min(bottom(compareWith), l.y);\n // Move the element left as far as it can go without colliding.\n while (l.x > 0 && !getFirstCollision(compareWith, l)) {\n l.x--;\n }\n }\n\n // Move it down, and keep moving it down if it's colliding.\n let collides;\n while ((collides = getFirstCollision(compareWith, l))) {\n if (compactH) {\n resolveCompactionCollision(\n fullLayout,\n l,\n collides.x + collides.w,\n 'x',\n );\n } else {\n resolveCompactionCollision(\n fullLayout,\n l,\n collides.y + collides.h,\n 'y',\n );\n }\n // Since we can't grow without bounds horizontally, if we've overflown, let's move it down and try again.\n if (compactH && l.x + l.w > cols) {\n l.x = cols - l.w;\n l.y++;\n }\n }\n return l;\n}\n\n/**\n * Given a layout, make sure all elements fit within its bounds.\n *\n * @param {Array} layout Layout array.\n * @param {Number} bounds Number of columns.\n */\nexport function correctBounds(layout: Layout, bounds: { cols: number }): Layout {\n const collidesWith = getStatics(layout);\n for (let i = 0, len = layout.length; i < len; i++) {\n const l = layout[i];\n // Overflows right\n if (l.x + l.w > bounds.cols) {\n l.x = bounds.cols - l.w;\n }\n // Overflows left\n if (l.x < 0) {\n l.x = 0;\n l.w = bounds.cols;\n }\n if (!l.static) {\n collidesWith.push(l);\n } else {\n // If this is static and collides with other statics, we must move it down.\n // We have to do something nicer than just letting them overlap.\n while (getFirstCollision(collidesWith, l)) {\n l.y++;\n }\n }\n }\n return layout;\n}\n\n/**\n * Get a layout item by ID. Used so we can override later on if necessary.\n *\n * @param {Array} layout Layout array.\n * @param {String} id ID\n * @return {LayoutItem} Item at ID.\n */\nexport function getLayoutItem(\n layout: Layout,\n id: string,\n): LayoutItem | null | undefined {\n for (let i = 0, len = layout.length; i < len; i++) {\n if (layout[i].id === id) {\n return layout[i];\n }\n }\n return null;\n}\n\n/**\n * Returns the first item this layout collides with.\n * It doesn't appear to matter which order we approach this from, although\n * perhaps that is the wrong thing to do.\n *\n * @param {Object} layoutItem Layout item.\n * @return {Object|undefined} A colliding layout item, or undefined.\n */\nexport function getFirstCollision(\n layout: Layout,\n layoutItem: LayoutItem,\n): LayoutItem | null | undefined {\n for (let i = 0, len = layout.length; i < len; i++) {\n if (collides(layout[i], layoutItem)) {\n return layout[i];\n }\n }\n return null;\n}\n\nexport function getAllCollisions(\n layout: Layout,\n layoutItem: LayoutItem,\n): Array<LayoutItem> {\n return layout.filter(l => collides(l, layoutItem));\n}\n\n/**\n * Get all static elements.\n * @param {Array} layout Array of layout objects.\n * @return {Array} Array of static layout items..\n */\nexport function getStatics(layout: Layout): Array<LayoutItem> {\n return layout.filter(l => l.static);\n}\n\n/**\n * Move an element. Responsible for doing cascading movements of other elements.\n *\n * @param {Array} layout Full layout to modify.\n * @param {LayoutItem} l element to move.\n * @param {Number} [x] X position in grid units.\n * @param {Number} [y] Y position in grid units.\n */\nexport function moveElement(\n layout: Layout,\n l: LayoutItem,\n x: number | null | undefined,\n y: number | null | undefined,\n isUserAction: boolean | null | undefined,\n preventCollision: boolean | null | undefined,\n compactType: CompactType,\n cols: number,\n): Layout {\n // If this is static and not explicitly enabled as draggable,\n // no move is possible, so we can short-circuit this immediately.\n if (l.static && l.isDraggable !== true) {\n return layout;\n }\n\n // Short-circuit if nothing to do.\n if (l.y === y && l.x === x) {\n return layout;\n }\n\n log(\n `Moving element ${l.id} to [${String(x)},${String(y)}] from [${l.x},${\n l.y\n }]`,\n );\n const oldX = l.x;\n const oldY = l.y;\n\n // This is quite a bit faster than extending the object\n if (typeof x === 'number') {\n l.x = x;\n }\n if (typeof y === 'number') {\n l.y = y;\n }\n l.moved = true;\n\n // If this collides with anything, move it.\n // When doing this comparison, we have to sort the items we compare with\n // to ensure, in the case of multiple collisions, that we're getting the\n // nearest collision.\n let sorted = sortLayoutItems(layout, compactType);\n const movingUp =\n compactType === 'vertical' && typeof y === 'number'\n ? oldY >= y\n : compactType === 'horizontal' && typeof x === 'number'\n ? oldX >= x\n : false;\n if (movingUp) {\n sorted = sorted.reverse();\n }\n const collisions = getAllCollisions(sorted, l);\n\n // There was a collision; abort\n if (preventCollision && collisions.length) {\n log(`Collision prevented on ${l.id}, reverting.`);\n l.x = oldX;\n l.y = oldY;\n l.moved = false;\n return layout;\n }\n\n // Move each item that collides away from this element.\n for (let i = 0, len = collisions.length; i < len; i++) {\n const collision = collisions[i];\n log(\n `Resolving collision between ${l.id} at [${l.x},${l.y}] and ${\n collision.id\n } at [${collision.x},${collision.y}]`,\n );\n\n // Short circuit so we can't infinite loop\n if (collision.moved) {\n continue;\n }\n\n // Don't move static items - we have to move *this* element away\n if (collision.static) {\n layout = moveElementAwayFromCollision(\n layout,\n collision,\n l,\n isUserAction,\n compactType,\n cols,\n );\n } else {\n layout = moveElementAwayFromCollision(\n layout,\n l,\n collision,\n isUserAction,\n compactType,\n cols,\n );\n }\n }\n\n return layout;\n}\n\n/**\n * This is where the magic needs to happen - given a collision, move an element away from the collision.\n * We attempt to move it up if there's room, otherwise it goes below.\n *\n * @param {Array} layout Full layout to modify.\n * @param {LayoutItem} collidesWith Layout item we're colliding with.\n * @param {LayoutItem} itemToMove Layout item we're moving.\n */\nexport function moveElementAwayFromCollision(\n layout: Layout,\n collidesWith: LayoutItem,\n itemToMove: LayoutItem,\n isUserAction: boolean | null | undefined,\n compactType: CompactType,\n cols: number,\n): Layout {\n const compactH = compactType === 'horizontal';\n // Compact vertically if not set to horizontal\n const compactV = compactType !== 'horizontal';\n const preventCollision = collidesWith.static; // we're already colliding (not for static items)\n\n // If there is enough space above the collision to put this element, move it there.\n // We only do this on the main collision as this can get funky in cascades and cause\n // unwanted swapping behavior.\n if (isUserAction) {\n // Reset isUserAction flag because we're not in the main collision anymore.\n isUserAction = false;\n\n // Make a mock item so we don't modify the item here, only modify in moveElement.\n const fakeItem: LayoutItem = {\n x: compactH\n ? Math.max(collidesWith.x - itemToMove.w, 0)\n : itemToMove.x,\n y: compactV\n ? Math.max(collidesWith.y - itemToMove.h, 0)\n : itemToMove.y,\n w: itemToMove.w,\n h: itemToMove.h,\n id: '-1',\n };\n\n // No collision? If so, we can go up there; otherwise, we'll end up moving down as normal\n if (!getFirstCollision(layout, fakeItem)) {\n log(\n `Doing reverse collision on ${itemToMove.id} up to [${\n fakeItem.x\n },${fakeItem.y}].`,\n );\n return moveElement(\n layout,\n itemToMove,\n compactH ? fakeItem.x : undefined,\n compactV ? fakeItem.y : undefined,\n isUserAction,\n preventCollision,\n compactType,\n cols,\n );\n }\n }\n\n return moveElement(\n layout,\n itemToMove,\n compactH ? itemToMove.x + 1 : undefined,\n compactV ? itemToMove.y + 1 : undefined,\n isUserAction,\n preventCollision,\n compactType,\n cols,\n );\n}\n\n/**\n * Helper to convert a number to a percentage string.\n *\n * @param {Number} num Any number\n * @return {String} That number as a percentage.\n */\nexport function perc(num: number): string {\n return num * 100 + '%';\n}\n\nexport function setTransform({top, left, width, height}: Position): Object {\n // Replace unitless items with px\n const translate = `translate(${left}px,${top}px)`;\n return {\n transform: translate,\n WebkitTransform: translate,\n MozTransform: translate,\n msTransform: translate,\n OTransform: translate,\n width: `${width}px`,\n height: `${height}px`,\n position: 'absolute',\n };\n}\n\nexport function setTopLeft({top, left, width, height}: Position): Object {\n return {\n top: `${top}px`,\n left: `${left}px`,\n width: `${width}px`,\n height: `${height}px`,\n position: 'absolute',\n };\n}\n\n/**\n * Get layout items sorted from top left to right and down.\n *\n * @return {Array} Array of layout objects.\n * @return {Array} Layout, sorted static items first.\n */\nexport function sortLayoutItems(\n layout: Layout,\n compactType: CompactType,\n): Layout {\n if (compactType === 'horizontal') {\n return sortLayoutItemsByColRow(layout);\n } else {\n return sortLayoutItemsByRowCol(layout);\n }\n}\n\nexport function sortLayoutItemsByRowCol(layout: Layout): Layout {\n return ([] as any[]).concat(layout).sort(function (a, b) {\n if (a.y > b.y || (a.y === b.y && a.x > b.x)) {\n return 1;\n } else if (a.y === b.y && a.x === b.x) {\n // Without this, we can get different sort results in IE vs. Chrome/FF\n return 0;\n }\n return -1;\n });\n}\n\nexport function sortLayoutItemsByColRow(layout: Layout): Layout {\n return ([] as any[]).concat(layout).sort(function (a, b) {\n if (a.x > b.x || (a.x === b.x && a.y > b.y)) {\n return 1;\n }\n return -1;\n });\n}\n\n/**\n * Validate a layout. Throws errors.\n *\n * @param {Array} layout Array of layout items.\n * @param {String} [contextName] Context name for errors.\n * @throw {Error} Validation error.\n */\nexport function validateLayout(\n layout: Layout,\n contextName: string = 'Layout',\n): void {\n const subProps = ['x', 'y', 'w', 'h'];\n if (!Array.isArray(layout)) {\n throw new Error(contextName + ' must be an array!');\n }\n for (let i = 0, len = layout.length; i < len; i++) {\n const item = layout[i];\n for (let j = 0; j < subProps.length; j++) {\n if (typeof item[subProps[j]] !== 'number') {\n throw new Error(\n 'ReactGridLayout: ' +\n contextName +\n '[' +\n i +\n '].' +\n subProps[j] +\n ' must be a number!',\n );\n }\n }\n if (item.id && typeof item.id !== 'string') {\n throw new Error(\n 'ReactGridLayout: ' +\n contextName +\n '[' +\n i +\n '].i must be a string!',\n );\n }\n if (item.static !== undefined && typeof item.static !== 'boolean') {\n throw new Error(\n 'ReactGridLayout: ' +\n contextName +\n '[' +\n i +\n '].static must be a boolean!',\n );\n }\n }\n}\n\n// Flow can't really figure this out, so we just use Object\nexport function autoBindHandlers(el: Object, fns: Array<string>): void {\n fns.forEach(key => (el[key] = el[key].bind(el)));\n}\n\nfunction log(...args) {\n if (!DEBUG) {\n return;\n }\n // eslint-disable-next-line no-console\n console.log(...args);\n}\n\nexport const noop = () => {};\n","/** Cached result of whether the user's browser supports passive event listeners. */\nlet supportsPassiveEvents: boolean;\n\n/**\n * Checks whether the user's browser supports passive event listeners.\n * See: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md\n */\nexport function ktdSupportsPassiveEventListeners(): boolean {\n if (supportsPassiveEvents == null && typeof window !== 'undefined') {\n try {\n window.addEventListener('test', null!, Object.defineProperty({}, 'passive', {\n get: () => supportsPassiveEvents = true\n }));\n } finally {\n supportsPassiveEvents = supportsPassiveEvents || false;\n }\n }\n\n return supportsPassiveEvents;\n}\n\n/**\n * Normalizes an `AddEventListener` object to something that can be passed\n * to `addEventListener` on any browser, no matter whether it supports the\n * `options` parameter.\n * @param options Object to be normalized.\n */\nexport function ktdNormalizePassiveListenerOptions(options: AddEventListenerOptions):\n AddEventListenerOptions | boolean {\n return ktdSupportsPassiveEventListeners() ? options : !!options.capture;\n}\n","import { fromEvent, iif, merge, Observable } from 'rxjs';\nimport { filter } from 'rxjs/operators';\nimport { ktdNormalizePassiveListenerOptions } from './passive-listeners';\n\n/** Options that can be used to bind a passive event listener. */\nconst passiveEventListenerOptions = ktdNormalizePassiveListenerOptions({passive: true});\n\n/** Options that can be used to bind an active event listener. */\nconst activeEventListenerOptions = ktdNormalizePassiveListenerOptions({passive: false});\n\nlet isMobile: boolean | null = null;\n\nexport function ktdIsMobileOrTablet(): boolean {\n\n if (isMobile != null) {\n return isMobile;\n }\n\n // Generic match pattern to identify mobile or tablet devices\n const isMobileDevice = /Android|webOS|BlackBerry|Windows Phone|iPad|iPhone|iPod/i.test(navigator.userAgent);\n\n // Since IOS 13 is not safe to just check for the generic solution. See: https://stackoverflow.com/questions/58019463/how-to-detect-device-name-in-safari-on-ios-13-while-it-doesnt-show-the-correct\n const isIOSMobileDevice = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);\n\n isMobile = isMobileDevice || isIOSMobileDevice;\n\n return isMobile;\n}\n\nexport function ktdIsMouseEvent(event: any): event is MouseEvent {\n return (event as MouseEvent).clientX != null;\n}\n\nexport function ktdIsTouchEvent(event: any): event is TouchEvent {\n return (event as TouchEvent).touches != null && (event as TouchEvent).touches.length != null;\n}\n\nexport function ktdPointerClientX(event: MouseEvent | TouchEvent): number {\n return ktdIsMouseEvent(event) ? event.clientX : event.touches[0].clientX;\n}\n\nexport function ktdPointerClientY(event: MouseEvent | TouchEvent): number {\n return ktdIsMouseEvent(event) ? event.clientY : event.touches[0].clientY;\n}\n\nexport function ktdPointerClient(event: MouseEvent | TouchEvent): {clientX: number, clientY: number} {\n return {\n clientX: ktdIsMouseEvent(event) ? event.clientX : event.touches[0].clientX,\n clientY: ktdIsMouseEvent(event) ? event.clientY : event.touches[0].clientY\n };\n}\n\n/**\n * Emits when a mousedown or touchstart emits. Avoids conflicts between both events.\n * @param element, html element where to listen the events.\n * @param touchNumber number of the touch to track the event, default to the first one.\n */\nexport function ktdMouseOrTouchDown(element, touchNumber = 1): Observable<MouseEvent | TouchEvent> {\n return iif(\n () => ktdIsMobileOrTablet(),\n fromEvent<TouchEvent>(element, 'touchstart', passiveEventListenerOptions as AddEventListenerOptions).pipe(\n filter((touchEvent) => touchEvent.touches.length === touchNumber)\n ),\n fromEvent<MouseEvent>(element, 'mousedown', activeEventListenerOptions as AddEventListenerOptions).pipe(\n filter((mouseEvent: MouseEvent) => {\n /**\n * 0 : Left mouse button\n * 1 : Wheel button or middle button (if present)\n * 2 : Right mouse button\n */\n return mouseEvent.button === 0; // Mouse down to be only fired if is left click\n })\n )\n );\n}\n\n/**\n * Emits when a 'mousemove' or a 'touchmove' event gets fired.\n * @param element, html element where to listen the events.\n * @param touchNumber number of the touch to track the event, default to the first one.\n */\nexport function ktdMouseOrTouchMove(element, touchNumber = 1): Observable<MouseEvent | TouchEvent> {\n return iif(\n () => ktdIsMobileOrTablet(),\n fromEvent<TouchEvent>(element, 'touchmove', activeEventListenerOptions as AddEventListenerOptions).pipe(\n filter((touchEvent) => touchEvent.touches.length === touchNumber),\n ),\n fromEvent<MouseEvent>(element, 'mousemove', activeEventListenerOptions as AddEventListenerOptions)\n );\n}\n\nexport function ktdTouchEnd(element, touchNumber = 1): Observable<TouchEvent> {\n return merge(\n fromEvent<TouchEvent>(element, 'touchend').pipe(\n filter((touchEvent) => touchEvent.touches.length === touchNumber - 1)\n ),\n fromEvent<TouchEvent>(element, 'touchcancel').pipe(\n filter((touchEvent) => touchEvent.touches.length === touchNumber - 1)\n )\n );\n}\n\n/**\n * Emits when a there is a 'mouseup' or the touch ends.\n * @param element, html element where to listen the events.\n * @param touchNumber number of the touch to track the event, default to the first one.\n */\nexport function ktdMouseOrTouchEnd(element, touchNumber = 1): Observable<MouseEvent | TouchEvent> {\n return iif(\n () => ktdIsMobileOrTablet(),\n ktdTouchEnd(element, touchNumber),\n fromEvent<MouseEvent>(element, 'mouseup'),\n );\n}\n","import { compact, CompactType, getFirstCollision, Layout, LayoutItem, moveElement } from './react-grid-layout.utils';\nimport { KtdDraggingData, KtdGridCfg, KtdGridCompactType, KtdGridItemRect, KtdGridLayout, KtdGridLayoutItem } from '../grid.definitions';\nimport { ktdPointerClientX, ktdPointerClientY } from './pointer.utils';\nimport { KtdDictionary } from '../../types';\nimport { KtdGridItemComponent } from '../grid-item/grid-item.component';\n\n/** Tracks items by id. This function is mean to be used in conjunction with the ngFor that renders the 'ktd-grid-items' */\nexport function ktdTrackById(index: number, item: {id: string}) {\n return item.id;\n}\n\n/**\n * Call react-grid-layout utils 'compact()' function and return the compacted layout.\n * @param layout to be compacted.\n * @param compactType, type of compaction.\n * @param cols, number of columns of the grid.\n */\nexport function ktdGridCompact(layout: KtdGridLayout, compactType: KtdGridCompactType, cols: number): KtdGridLayout {\n return compact(layout, compactType, cols)\n // Prune react-grid-layout compact extra properties.\n .map(item => ({ id: item.id, x: item.x, y: item.y, w: item.w, h: item.h, minW: item.minW, minH: item.minH, maxW: item.maxW, maxH: item.maxH }));\n}\n\nfunction screenXPosToGridValue(screenXPos: number, cols: number, width: number): number {\n return Math.round((screenXPos * cols) / width);\n}\n\nfunction screenYPosToGridValue(screenYPos: number, rowHeight: number, height: number): number {\n return Math.round(screenYPos / rowHeight);\n}\n\n/** Returns a Dictionary where the key is the id and the value is the change applied to that item. If no changes Dictionary is empty. */\nexport function ktdGetGridLayoutDiff(gridLayoutA: KtdGridLayoutItem[], gridLayoutB: KtdGridLayoutItem[]): KtdDictionary<{ change: 'move' | 'resize' | 'moveresize' }> {\n const diff: KtdDictionary<{ change: 'move' | 'resize' | 'moveresize' }> = {};\n\n gridLayoutA.forEach(itemA => {\n const itemB = gridLayoutB.find(_itemB => _itemB.id === itemA.id);\n if (itemB != null) {\n const posChanged = itemA.x !== itemB.x || itemA.y !== itemB.y;\n const sizeChanged = itemA.w !== itemB.w || itemA.h !== itemB.h;\n const change: 'move' | 'resize' | 'moveresize' | null = posChanged && sizeChanged ? 'moveresize' : posChanged ? 'move' : sizeChanged ? 'resize' : null;\n if (change) {\n diff[itemB.id] = {change};\n }\n }\n });\n return diff;\n}\n\n/**\n * Given the grid config & layout data and the current drag position & information, returns the corresponding layout and drag item position\n * @param gridItem grid item that is been dragged\n * @param config current grid configuration\n * @param compactionType type of compaction that will be performed\n * @param draggingData contains all the information about the drag\n */\nexport function ktdGridItemDragging(gridItem: KtdGridItemComponent, config: KtdGridCfg, compactionType: CompactType, draggingData: KtdDraggingData): { layout: KtdGridLayoutItem[]; draggedItemPos: KtdGridItemRect } {\n const {pointerDownEvent, pointerDragEvent, gridElemClientRect, dragElemClientRect, scrollDifference} = draggingData;\n\n const gridItemId = gridItem.id;\n\n const draggingElemPrevItem = config.layout.find(item => item.id === gridItemId)!;\n\n const clientStartX = ktdPointerClientX(pointerDownEvent);\n const clientStartY = ktdPointerClientY(pointerDownEvent);\n const clientX = ktdPointerClientX(pointerDragEvent);\n const clientY = ktdPointerClientY(pointerDragEvent);\n\n const offsetX = clientStartX - dragElemClientRect.left;\n const offsetY = clientStartY - dragElemClientRect.top;\n\n // Grid element positions taking into account the possible scroll total difference from the beginning.\n const gridElementLeftPosition = gridElemClientRect.left + scrollDifference.left;\n const gridElementTopPosition = gridElemClientRect.top + scrollDifference.top;\n\n // Calculate position relative to the grid element.\n const gridRelXPos = clientX - gridElementLeftPosition - offsetX;\n const gridRelYPos = clientY - gridElementTopPosition - offsetY;\n\n // Get layout item position\n const layoutItem: KtdGridLayoutItem = {\n ...draggingElemPrevItem,\n x: screenXPosToGridValue(gridRelXPos, config.cols, gridElemClientRect.width),\n y: screenYPosToGridValue(gridRelYPos, config.rowHeight, gridElemClientRect.height)\n };\n\n // Correct the values if they overflow, since 'moveElement' function doesn't do it\n layoutItem.x = Math.max(0, layoutItem.x);\n layoutItem.y = Math.max(0, layoutItem.y);\n if (layoutItem.x + layoutItem.w > config.cols) {\n layoutItem.x = Math.max(0, config.cols - layoutItem.w);\n }\n\n // Parse to LayoutItem array data in order to use 'react.grid-layout' utils\n const layoutItems: LayoutItem[] = config.layout;\n const draggedLayoutItem: LayoutItem = layoutItems.find(item => item.id === gridItemId)!;\n\n let newLayoutItems: LayoutItem[] = moveElement(\n layoutItems,\n draggedLayoutItem,\n layoutItem.x,\n layoutItem.y,\n true,\n config.preventCollision,\n compactionType,\n config.cols\n );\n\n newLayoutItems = compact(newLayoutItems, compactionType, config.cols);\n\n return {\n layout: newLayoutItems,\n draggedItemPos: {\n top: gridRelYPos,\n left: gridRelXPos,\n width: dragElemClientRect.width,\n height: dragElemClientRect.height,\n }\n };\n}\n\n/**\n * Given the grid config & layout data and the current drag position & information, returns the corresponding layout and drag item position\n * @param gridItem grid item that is been dragged\n * @param config current grid configuration\n * @param compactionType type of compaction that will be performed\n * @param draggingData contains all the information about the drag\n */\nexport function ktdGridItemResizing(gridItem: KtdGridItemComponent, config: KtdGridCfg, compactionType: CompactType, draggingData: KtdDraggingData): { layout: KtdGridLayoutItem[]; draggedItemPos: KtdGridItemRect } {\n const {pointerDownEvent, pointerDragEvent, gridElemClientRect, dragElemClientRect, scrollDifference} = draggingData;\n const gridItemId = gridItem.id;\n\n const clientStartX = ktdPointerClientX(pointerDownEvent);\n const clientStartY = ktdPointerClientY(pointerDownEvent);\n const clientX = ktdPointerClientX(pointerDragEvent);\n const clientY = ktdPointerClientY(pointerDragEvent);\n\n // Get the difference between the mouseDown and the position 'right' of the resize element.\n const resizeElemOffsetX = dragElemClientRect.width - (clientStartX - dragElemClientRect.left);\n const resizeElemOffsetY = dragElemClientRect.height - (clientStartY - dragElemClientRect.top);\n\n const draggingElemPrevItem = config.layout.find(item => item.id === gridItemId)!;\n const width = clientX + resizeElemOffsetX - (dragElemClientRect.left + scrollDifference.left);\n const height = clientY + resizeElemOffsetY - (dragElemClientRect.top + scrollDifference.top);\n\n\n // Get layout item grid position\n const layoutItem: KtdGridLayoutItem = {\n ...draggingElemPrevItem,\n w: screenXPosToGridValue(width, config.cols, gridElemClientRect.width),\n h: screenYPosToGridValue(height, config.rowHeight, gridElemClientRect.height)\n };\n\n layoutItem.w = limitNumberWithinRange(layoutItem.w, gridItem.minW ?? layoutItem.minW, gridItem.maxW ?? layoutItem.maxW);\n layoutItem.h = limitNumberWithinRange(layoutItem.h, gridItem.minH ?? layoutItem.minH, gridItem.maxH ?? layoutItem.maxH);\n\n if (layoutItem.x + layoutItem.w > config.cols) {\n layoutItem.w = Math.max(1, config.cols - layoutItem.x);\n }\n\n if (config.preventCollision) {\n const maxW = layoutItem.w;\n const maxH = layoutItem.h;\n\n let colliding = hasCollision(config.layout, layoutItem);\n let shrunkDimension: 'w' | 'h' | undefined;\n\n while (colliding) {\n shrunkDimension = getDimensionToShrink(layoutItem, shrunkDimension);\n layoutItem[shrunkDimension]--;\n colliding = hasCollision(config.layout, layoutItem);\n }\n\n if (shrunkDimension === 'w') {\n layoutItem.h = maxH;\n\n colliding = hasCollision(config.layout, layoutItem);\n while (colliding) {\n layoutItem.h--;\n colliding = hasCollision(config.layout, layoutItem);\n }\n }\n if (shrunkDimension === 'h') {\n layoutItem.w = maxW;\n\n colliding = hasCollision(config.layout, layoutItem);\n while (colliding) {\n layoutItem.w--;\n colliding = hasCollision(config.layout, layoutItem);\n }\n }\n\n }\n\n const newLayoutItems: LayoutItem[] = config.layout.map((item) => {\n return item.id === gridItemId ? layoutItem : item;\n });\n\n return {\n layout: compact(newLayoutItems, compactionType, config.cols),\n draggedItemPos: {\n top: dragElemClientRect.top - gridElemClientRect.top,\n left: dragElemClientRect.left - gridElemClientRect.left,\n width,\n height,\n }\n };\n}\n\nfunction hasCollision(layout: Layout, layoutItem: LayoutItem): boolean {\n return !!getFirstCollision(layout, layoutItem);\n}\n\nfunction getDimensionToShrink(layoutItem, lastShrunk): 'w' | 'h' {\n if (layoutItem.h <= 1) {\n return 'w';\n }\n if (layoutItem.w <= 1) {\n return 'h';\n }\n\n return lastShrunk === 'w' ? 'h' : 'w';\n}\n\n/**\n * Given the current number and min/max values, returns the number within the range\n * @param number can be any numeric value\n * @param min minimum value of range\n * @param max maximum value of range\n */\nfunction limitNumberWithinRange(num: number, min: number = 1, max: number = Infinity) {\n return Math.min(Math.max(num, min < 1 ? 1 : min), max);\n}\n","import { Directive, ElementRef, InjectionToken } from '@angular/core';\n\n/**\n * Injection token that can be used to reference instances of `KtdGridDragHandle`. It serves as\n * alternative token to the actual `KtdGridDragHandle` class which could cause unnecessary\n * retention of the class and its directive metadata.\n */\nexport const KTD_GRID_DRAG_HANDLE = new InjectionToken<KtdGridDragHandle>('KtdGridDragHandle');\n\n/** Handle that can be used to drag a KtdGridItem instance. */\n@Directive({\n selector: '[ktdGridDragHandle]',\n // eslint-disable-next-line @angular-eslint/no-host-metadata-property\n host: {\n class: 'ktd-grid-drag-handle'\n },\n providers: [{provide: KTD_GRID_DRAG_HANDLE, useExisting: KtdGridDragHandle}],\n})\n// eslint-disable-next-line @angular-eslint/directive-class-suffix\nexport class KtdGridDragHandle {\n constructor(\n public element: ElementRef<HTMLElement>) {\n }\n}\n","import { Directive, ElementRef, InjectionToken, } from '@angular/core';\n\n\n/**\n * Injection token that can be used to reference instances of `KtdGridResizeHandle`. It serves as\n * alternative token to the actual `KtdGridResizeHandle` class which could cause unnecessary\n * retention of the class and its directive metadata.\n */\nexport const KTD_GRID_RESIZE_HANDLE = new InjectionToken<KtdGridResizeHandle>('KtdGridResizeHandle');\n\n/** Handle that can be used to drag a KtdGridItem instance. */\n@Directive({\n selector: '[ktdGridResizeHandle]',\n // eslint-disable-next-line @angular-eslint/no-host-metadata-property\n host: {\n class: 'ktd-grid-resize-handle'\n },\n providers: [{provide: KTD_GRID_RESIZE_HANDLE, useExisting: KtdGridResizeHandle}],\n})\n// eslint-disable-next-line @angular-eslint/directive-class-suffix\nexport class KtdGridResizeHandle {\n\n constructor(\n public element: ElementRef<HTMLElement>) {\n }\n}\n","import { InjectionToken } from '@angular/core';\nimport { CompactType } from './utils/react-grid-layout.utils';\n\nexport interface KtdGridLayoutItem {\n id: string;\n x: number;\n y: number;\n w: number;\n h: number;\n minW?: number;\n minH?: number;\n maxW?: number;\n maxH?: number;\n}\n\nexport type KtdGridCompactType = CompactType;\n\nexport interface KtdGridCfg {\n cols: number;\n rowHeight: number; // row height in pixels\n layout: KtdGridLayoutItem[];\n preventCollision: boolean;\n}\n\nexport type KtdGridLayout = KtdGridLayoutItem[];\n\n// TODO: Remove this interface. If can't remove, move and rename this interface in the core module or similar.\nexport interface KtdGridItemRect {\n top: number;\n left: number;\n width: number;\n height: number;\n}\n\nexport interface KtdGridItemRenderData<T = number | string> {\n id: string;\n top: T;\n left: T;\n width: T;\n height: T;\n}\n\n/**\n * We inject a token because of the 'circular dependency issue warning'. In case we don't had this issue with the circular dependency, we could just\n * import KtdGridComponent on KtdGridItem and execute the needed function to get the rendering data.\n */\nexport type KtdGridItemRenderDataTokenType = (id: string) => KtdGridItemRenderData<string>;\nexport const GRID_ITEM_GET_RENDER_DATA_TOKEN: InjectionToken<KtdGridItemRenderDataTokenType> = new InjectionToken('GRID_ITEM_GET_RENDER_DATA_TOKEN');\n\nexport interface KtdDraggingData {\n pointerDownEvent: MouseEvent | TouchEvent;\n pointerDragEvent: MouseEvent | TouchEvent;\n gridElemClientRect: ClientRect;\n dragElemClientRect: ClientRect;\n scrollDifference: { top: number, left: number };\n}\n","import { NgZone } from '@angular/core';\nimport { Observable, Subscription } from 'rxjs';\nimport { filter } from 'rxjs/operators';\n\n/** Runs source observable outside the zone */\nexport function ktdOutsideZone<T>(zone: NgZone) {\n return (source: Observable<T>) => {\n return new Observable<T>(observer => {\n return zone.runOutsideAngular<Subscription>(() => source.subscribe(observer));\n });\n };\n}\n\n\n/** Rxjs operator that makes source observable to no emit any data */\nexport function ktdNoEmit() {\n return (source$: Observable<any>): Observable<any> => {\n return source$.pipe(filter(() => false));\n };\n}\n","// tslint:disable\n/**\n * Type describing the allowed values for a boolean input.\n * @docs-private\n */\nexport type BooleanInput = string | boolean | null | undefined;\n\n/** Coerces a data-bound value (typically a string) to a boolean. */\nexport function coerceBooleanProperty(value: any): boolean {\n return value != null && `${value}` !== 'false';\n}\n","// tslint:disable\nexport type NumberInput = string | number | null | undefined;\n\n/** Coerces a data-bound value (typically a string) to a number. */\nexport function coerceNumberProperty(value: any): number;\nexport function coerceNumberProperty<D>(value: any, fallback: D): number | D;\nexport function coerceNumberProperty(value: any, fallbackValue = 0) {\n return _isNumberValue(value) ? Number(value) : fallbackValue;\n}\n\n/**\n * Whether the provided value is considered a number.\n * @docs-private\n */\nexport function _isNumberValue(value: any): boolean {\n // parseFloat(value) handles most of the cases we're interested in (it treats null, empty string,\n // and other non-number values as NaN, where Number just uses 0) but it considers the string\n // '123hello' to be a valid number. Therefore we also check if Number(value) is NaN.\n return !isNaN(parseFloat(value as any)) && !isNaN(Number(value));\n}\n","import { Injectable, NgZone, OnDestroy } from '@angular/core';\nimport { ktdNormalizePassiveListenerOptions } from './utils/passive-listeners';\nimport { fromEvent, iif, Observable, Subject, Subscription } from 'rxjs';\nimport { filter } from 'rxjs/operators';\nimport { ktdIsMobileOrTablet } from './utils/pointer.utils';\n\n/** Event options that can be used to bind an active, capturing event. */\nconst activeCapturingEventOptions = ktdNormalizePassiveListenerOptions({\n passive: false,\n capture: true\n});\n\n@Injectable({providedIn: 'root'})\nexport class KtdGridService implements OnDestroy {\n\n touchMove$: Observable<TouchEvent>;\n private touchMoveSubject: Subject<TouchEvent> = new Subject<TouchEvent>();\n private touchMoveSubscription: Subscription;\n\n constructor(private ngZone: NgZone) {\n this.touchMove$ = this.touchMoveSubject.asObservable();\n this.registerTouchMoveSubscription();\n }\n\n ngOnDestroy() {\n this.touchMoveSubscription.unsubscribe();\n }\n\n mouseOrTouchMove$(element): Observable<MouseEvent | TouchEvent> {\n return iif(\n () => ktdIsMobileOrTablet(),\n this.touchMove$,\n fromEvent<MouseEvent>(element, 'mousemove', activeCapturingEventOptions as AddEventListenerOptions) // TODO: Fix rxjs typings, boolean should be a good param too.\n );\n }\n\n private registerTouchMoveSubscription() {\n // The `touchmove` event gets bound once, ahead of time, because WebKit\n // won't preventDefault on a dynamically-added `touchmove` listener.\n // See https://bugs.webkit.org/show_bug.cgi?id=184250.\n this.touchMoveSubscription = this.ngZone.runOutsideAngular(() =>\n // The event handler has to be explicitly active,\n // because newer browsers make it passive by default.\n fromEvent(document, 'touchmove', activeCapturingEventOptions as AddEventListenerOptions) // TODO: Fix rxjs typings, boolean should be a good param too.\n .pipe(filter((touchEvent: TouchEvent) => touchEvent.touches.length === 1))\n .subscribe((touchEvent: TouchEvent) => this.touchMoveSubject.next(touchEvent))\n );\n }\n}\n","import {\n AfterContentInit, ChangeDetectionStrategy, Component, ContentChildren, ElementRef, Inject, Input, NgZone, OnDestroy, OnInit, QueryList, Renderer2,\n ViewChild\n} from '@angular/core';\nimport { BehaviorSubject, iif, merge, NEVER, Observable, Subject, Subscription } from 'rxjs';\nimport { exhaustMap, filter, map, startWith, switchMap, take, takeUntil } from 'rxjs/operators';\nimport { ktdMouseOrTouchDown, ktdMouseOrTouchEnd, ktdPointerClient } from '../utils/pointer.utils';\nimport { GRID_ITEM_GET_RENDER_DATA_TOKEN, KtdGridItemRenderDataTokenType } from '../grid.definitions';\nimport { KTD_GRID_DRAG_HANDLE, KtdGridDragHandle } from '../directives/drag-handle';\nimport { KTD_GRID_RESIZE_HANDLE, KtdGridResizeHandle } from '../directives/resize-handle';\nimport { KtdGridService } from '../grid.service';\nimport { ktdOutsideZone } from '../utils/operators';\nimport { BooleanInput, coerceBooleanProperty } from '../coercion/boolean-property';\nimport { coerceNumberProperty, NumberInput } from '../coercion/number-property';\n\n@Component({\n selector: 'ktd-grid-item',\n templateUrl: './grid-item.component.html',\n styleUrls: ['./grid-item.component.scss'],\n changeDetection: ChangeDetectionStrategy.OnPush\n})\nexport class KtdGridItemComponent implements OnInit, OnDestroy, AfterContentInit {\n /** Elements that can be used to drag the grid item. */\n @ContentChildren(KTD_GRID_DRAG_HANDLE, {descendants: true}) _dragHandles: QueryList<KtdGridDragHandle>;\n @ContentChildren(KTD_GRID_RESIZE_HANDLE, {descendants: true}) _resizeHandles: QueryList<KtdGridResizeHandle>;\n @ViewChild('resizeElem', {static: true, read: ElementRef}) resizeElem: ElementRef;\n\n /** Min and max size input properties. Any of these would 'override' the min/max values specified in the layout. */\n @Input() minW?: number;\n @Input() minH?: number;\n @Input() maxW?: number;\n @Input() maxH?: number;\n\n /** CSS transition style. Note that for more performance is preferable only make transition on transform property. */\n @Input() transition: string = 'transform 500ms ease, width 500ms ease, height 500ms ease';\n\n dragStart$: Observable<MouseEvent | TouchEvent>;\n resizeStart$: Observable<MouseEvent | TouchEvent>;\n\n /** Id of the grid item. This property is strictly compulsory. */\n @Input()\n get id(): string {\n return this._id;\n }\n\n set id(val: string) {\n this._id = val;\n }\n\n private _id: string;\n\n /** Minimum amount of pixels that the user should move before it starts the drag sequence. */\n @Input()\n get dragStartThreshold(): number { return this._dragStartThreshold; }\n\n set dragStartThreshold(val: number) {\n this._dragStartThreshold = coerceNumberProperty(val);\n }\n\n private _dragStartThreshold: number = 0;\n\n\n /** Whether the item is draggable or not. Defaults to true. */\n @Input()\n get draggable(): boolean {\n