@katoid/angular-grid-layout
Version:
Grid Layout with draggable and resizable items for Angular
1 lines • 193 kB
Source Map (JSON)
{"version":3,"file":"katoid-angular-grid-layout.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/directives/placeholder.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.definitions.ts","../../../projects/angular-grid-layout/src/lib/utils/operators.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/utils/transition-duration.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/katoid-angular-grid-layout.ts"],"sourcesContent":["/**\r\n * IMPORTANT:\r\n * This utils are taken from the project: https://github.com/STRML/react-grid-layout.\r\n * The code should be as less modified as possible for easy maintenance.\r\n */\r\n\r\n// Disable lint since we don't want to modify this code\r\n/* eslint-disable */\r\nexport type LayoutItem = {\r\n w: number;\r\n h: number;\r\n x: number;\r\n y: number;\r\n id: string;\r\n minW?: number;\r\n minH?: number;\r\n maxW?: number;\r\n maxH?: number;\r\n moved?: boolean;\r\n static?: boolean;\r\n isDraggable?: boolean | null | undefined;\r\n isResizable?: boolean | null | undefined;\r\n};\r\nexport type Layout = Array<LayoutItem>;\r\nexport type Position = {\r\n left: number;\r\n top: number;\r\n width: number;\r\n height: number;\r\n};\r\nexport type ReactDraggableCallbackData = {\r\n node: HTMLElement;\r\n x?: number;\r\n y?: number;\r\n deltaX: number;\r\n deltaY: number;\r\n lastX?: number;\r\n lastY?: number;\r\n};\r\n\r\nexport type PartialPosition = { left: number; top: number };\r\nexport type DroppingPosition = { x: number; y: number; e: Event };\r\nexport type Size = { width: number; height: number };\r\nexport type GridDragEvent = {\r\n e: Event;\r\n node: HTMLElement;\r\n newPosition: PartialPosition;\r\n};\r\nexport type GridResizeEvent = { e: Event; node: HTMLElement; size: Size };\r\nexport type DragOverEvent = MouseEvent & {\r\n nativeEvent: {\r\n layerX: number;\r\n layerY: number;\r\n target: {\r\n className: String;\r\n };\r\n };\r\n};\r\n\r\n//type REl = ReactElement<any>;\r\n//export type ReactChildren = ReactChildrenArray<REl>;\r\n\r\n// All callbacks are of the signature (layout, oldItem, newItem, placeholder, e).\r\nexport type EventCallback = (\r\n arg0: Layout,\r\n oldItem: LayoutItem | null | undefined,\r\n newItem: LayoutItem | null | undefined,\r\n placeholder: LayoutItem | null | undefined,\r\n arg4: Event,\r\n arg5: HTMLElement | null | undefined,\r\n) => void;\r\nexport type CompactType = ('horizontal' | 'vertical') | null | undefined;\r\n\r\nconst DEBUG = false;\r\n\r\n/**\r\n * Return the bottom coordinate of the layout.\r\n *\r\n * @param {Array} layout Layout array.\r\n * @return {Number} Bottom coordinate.\r\n */\r\nexport function bottom(layout: Layout): number {\r\n let max = 0,\r\n bottomY;\r\n for (let i = 0, len = layout.length; i < len; i++) {\r\n bottomY = layout[i].y + layout[i].h;\r\n if (bottomY > max) {\r\n max = bottomY;\r\n }\r\n }\r\n return max;\r\n}\r\n\r\nexport function cloneLayout(layout: Layout): Layout {\r\n const newLayout = Array(layout.length);\r\n for (let i = 0, len = layout.length; i < len; i++) {\r\n newLayout[i] = cloneLayoutItem(layout[i]);\r\n }\r\n return newLayout;\r\n}\r\n\r\n// Fast path to cloning, since this is monomorphic\r\n/** NOTE: This code has been modified from the original source */\r\nexport function cloneLayoutItem(layoutItem: LayoutItem): LayoutItem {\r\n const clonedLayoutItem: LayoutItem = {\r\n w: layoutItem.w,\r\n h: layoutItem.h,\r\n x: layoutItem.x,\r\n y: layoutItem.y,\r\n id: layoutItem.id,\r\n moved: !!layoutItem.moved,\r\n static: !!layoutItem.static,\r\n };\r\n\r\n if (layoutItem.minW !== undefined) { clonedLayoutItem.minW = layoutItem.minW;}\r\n if (layoutItem.maxW !== undefined) { clonedLayoutItem.maxW = layoutItem.maxW;}\r\n if (layoutItem.minH !== undefined) { clonedLayoutItem.minH = layoutItem.minH;}\r\n if (layoutItem.maxH !== undefined) { clonedLayoutItem.maxH = layoutItem.maxH;}\r\n // These can be null\r\n if (layoutItem.isDraggable !== undefined) { clonedLayoutItem.isDraggable = layoutItem.isDraggable;}\r\n if (layoutItem.isResizable !== undefined) { clonedLayoutItem.isResizable = layoutItem.isResizable;}\r\n\r\n return clonedLayoutItem;\r\n}\r\n\r\n/**\r\n * Given two layoutitems, check if they collide.\r\n */\r\nexport function collides(l1: LayoutItem, l2: LayoutItem): boolean {\r\n if (l1.id === l2.id) {\r\n return false;\r\n } // same element\r\n if (l1.x + l1.w <= l2.x) {\r\n return false;\r\n } // l1 is left of l2\r\n if (l1.x >= l2.x + l2.w) {\r\n return false;\r\n } // l1 is right of l2\r\n if (l1.y + l1.h <= l2.y) {\r\n return false;\r\n } // l1 is above l2\r\n if (l1.y >= l2.y + l2.h) {\r\n return false;\r\n } // l1 is below l2\r\n return true; // boxes overlap\r\n}\r\n\r\n/**\r\n * Given a layout, compact it. This involves going down each y coordinate and removing gaps\r\n * between items.\r\n *\r\n * @param {Array} layout Layout.\r\n * @param {Boolean} verticalCompact Whether or not to compact the layout\r\n * vertically.\r\n * @return {Array} Compacted Layout.\r\n */\r\nexport function compact(\r\n layout: Layout,\r\n compactType: CompactType,\r\n cols: number,\r\n): Layout {\r\n // Statics go in the compareWith array right away so items flow around them.\r\n const compareWith = getStatics(layout);\r\n // We go through the items by row and column.\r\n const sorted = sortLayoutItems(layout, compactType);\r\n // Holding for new items.\r\n const out = Array(layout.length);\r\n\r\n for (let i = 0, len = sorted.length; i < len; i++) {\r\n let l = cloneLayoutItem(sorted[i]);\r\n\r\n // Don't move static elements\r\n if (!l.static) {\r\n l = compactItem(compareWith, l, compactType, cols, sorted);\r\n\r\n // Add to comparison array. We only collide with items before this one.\r\n // Statics are already in this array.\r\n compareWith.push(l);\r\n }\r\n\r\n // Add to output array to make sure they still come out in the right order.\r\n out[layout.indexOf(sorted[i])] = l;\r\n\r\n // Clear moved flag, if it exists.\r\n l.moved = false;\r\n }\r\n\r\n return out;\r\n}\r\n\r\nconst heightWidth = {x: 'w', y: 'h'};\r\n\r\n/**\r\n * Before moving item down, it will check if the movement will cause collisions and move those items down before.\r\n */\r\nfunction resolveCompactionCollision(\r\n layout: Layout,\r\n item: LayoutItem,\r\n moveToCoord: number,\r\n axis: 'x' | 'y',\r\n) {\r\n const sizeProp = heightWidth[axis];\r\n item[axis] += 1;\r\n const itemIndex = layout\r\n .map(layoutItem => {\r\n return layoutItem.id;\r\n })\r\n .indexOf(item.id);\r\n\r\n // Go through each item we collide with.\r\n for (let i = itemIndex + 1; i < layout.length; i++) {\r\n const otherItem = layout[i];\r\n // Ignore static items\r\n if (otherItem.static) {\r\n continue;\r\n }\r\n\r\n // Optimization: we can break early if we know we're past this el\r\n // We can do this b/c it's a sorted layout\r\n if (otherItem.y > item.y + item.h) {\r\n break;\r\n }\r\n\r\n if (collides(item, otherItem)) {\r\n resolveCompactionCollision(\r\n layout,\r\n otherItem,\r\n moveToCoord + item[sizeProp],\r\n axis,\r\n );\r\n }\r\n }\r\n\r\n item[axis] = moveToCoord;\r\n}\r\n\r\n/**\r\n * Compact an item in the layout.\r\n */\r\nexport function compactItem(\r\n compareWith: Layout,\r\n l: LayoutItem,\r\n compactType: CompactType,\r\n cols: number,\r\n fullLayout: Layout,\r\n): LayoutItem {\r\n const compactV = compactType === 'vertical';\r\n const compactH = compactType === 'horizontal';\r\n if (compactV) {\r\n // Bottom 'y' possible is the bottom of the layout.\r\n // This allows you to do nice stuff like specify {y: Infinity}\r\n // This is here because the layout must be sorted in order to get the correct bottom `y`.\r\n l.y = Math.min(bottom(compareWith), l.y);\r\n // Move the element up as far as it can go without colliding.\r\n while (l.y > 0 && !getFirstCollision(compareWith, l)) {\r\n l.y--;\r\n }\r\n } else if (compactH) {\r\n // Move the element left as far as it can go without colliding.\r\n while (l.x > 0 && !getFirstCollision(compareWith, l)) {\r\n l.x--;\r\n }\r\n }\r\n\r\n // Move it down, and keep moving it down if it's colliding.\r\n let collides;\r\n while ((collides = getFirstCollision(compareWith, l))) {\r\n if (compactH) {\r\n resolveCompactionCollision(fullLayout, l, collides.x + collides.w, 'x');\r\n } else {\r\n resolveCompactionCollision(fullLayout, l, collides.y + collides.h, 'y',);\r\n }\r\n // Since we can't grow without bounds horizontally, if we've overflown, let's move it down and try again.\r\n if (compactH && l.x + l.w > cols) {\r\n l.x = cols - l.w;\r\n l.y++;\r\n\r\n // ALso move element as left as much as we can (ktd-custom-change)\r\n while (l.x > 0 && !getFirstCollision(compareWith, l)) {\r\n l.x--;\r\n }\r\n }\r\n }\r\n\r\n // Ensure that there are no negative positions\r\n l.y = Math.max(l.y, 0);\r\n l.x = Math.max(l.x, 0);\r\n\r\n return l;\r\n}\r\n\r\n/**\r\n * Given a layout, make sure all elements fit within its bounds.\r\n *\r\n * @param {Array} layout Layout array.\r\n * @param {Number} bounds Number of columns.\r\n */\r\nexport function correctBounds(layout: Layout, bounds: { cols: number }): Layout {\r\n const collidesWith = getStatics(layout);\r\n for (let i = 0, len = layout.length; i < len; i++) {\r\n const l = layout[i];\r\n // Overflows right\r\n if (l.x + l.w > bounds.cols) {\r\n l.x = bounds.cols - l.w;\r\n }\r\n // Overflows left\r\n if (l.x < 0) {\r\n l.x = 0;\r\n l.w = bounds.cols;\r\n }\r\n if (!l.static) {\r\n collidesWith.push(l);\r\n } else {\r\n // If this is static and collides with other statics, we must move it down.\r\n // We have to do something nicer than just letting them overlap.\r\n while (getFirstCollision(collidesWith, l)) {\r\n l.y++;\r\n }\r\n }\r\n }\r\n return layout;\r\n}\r\n\r\n/**\r\n * Get a layout item by ID. Used so we can override later on if necessary.\r\n *\r\n * @param {Array} layout Layout array.\r\n * @param {String} id ID\r\n * @return {LayoutItem} Item at ID.\r\n */\r\nexport function getLayoutItem(\r\n layout: Layout,\r\n id: string,\r\n): LayoutItem | null | undefined {\r\n for (let i = 0, len = layout.length; i < len; i++) {\r\n if (layout[i].id === id) {\r\n return layout[i];\r\n }\r\n }\r\n return null;\r\n}\r\n\r\n/**\r\n * Returns the first item this layout collides with.\r\n * It doesn't appear to matter which order we approach this from, although\r\n * perhaps that is the wrong thing to do.\r\n *\r\n * @param {Object} layoutItem Layout item.\r\n * @return {Object|undefined} A colliding layout item, or undefined.\r\n */\r\nexport function getFirstCollision(\r\n layout: Layout,\r\n layoutItem: LayoutItem,\r\n): LayoutItem | null | undefined {\r\n for (let i = 0, len = layout.length; i < len; i++) {\r\n if (collides(layout[i], layoutItem)) {\r\n return layout[i];\r\n }\r\n }\r\n return null;\r\n}\r\n\r\nexport function getAllCollisions(\r\n layout: Layout,\r\n layoutItem: LayoutItem,\r\n): Array<LayoutItem> {\r\n return layout.filter(l => collides(l, layoutItem));\r\n}\r\n\r\n/**\r\n * Get all static elements.\r\n * @param {Array} layout Array of layout objects.\r\n * @return {Array} Array of static layout items..\r\n */\r\nexport function getStatics(layout: Layout): Array<LayoutItem> {\r\n return layout.filter(l => l.static);\r\n}\r\n\r\n/**\r\n * Move an element. Responsible for doing cascading movements of other elements.\r\n *\r\n * @param {Array} layout Full layout to modify.\r\n * @param {LayoutItem} l element to move.\r\n * @param {Number} [x] X position in grid units.\r\n * @param {Number} [y] Y position in grid units.\r\n */\r\nexport function moveElement(\r\n layout: Layout,\r\n l: LayoutItem,\r\n x: number | null | undefined,\r\n y: number | null | undefined,\r\n isUserAction: boolean | null | undefined,\r\n preventCollision: boolean | null | undefined,\r\n compactType: CompactType,\r\n cols: number,\r\n): Layout {\r\n // If this is static and not explicitly enabled as draggable,\r\n // no move is possible, so we can short-circuit this immediately.\r\n if (l.static && l.isDraggable !== true) {\r\n return layout;\r\n }\r\n\r\n // Short-circuit if nothing to do.\r\n if (l.y === y && l.x === x) {\r\n return layout;\r\n }\r\n\r\n log(\r\n `Moving element ${l.id} to [${String(x)},${String(y)}] from [${l.x},${\r\n l.y\r\n }]`,\r\n );\r\n const oldX = l.x;\r\n const oldY = l.y;\r\n\r\n // This is quite a bit faster than extending the object\r\n if (typeof x === 'number') {\r\n l.x = x;\r\n }\r\n if (typeof y === 'number') {\r\n l.y = y;\r\n }\r\n l.moved = true;\r\n\r\n // If this collides with anything, move it.\r\n // When doing this comparison, we have to sort the items we compare with\r\n // to ensure, in the case of multiple collisions, that we're getting the\r\n // nearest collision.\r\n let sorted = sortLayoutItems(layout, compactType);\r\n const movingUp =\r\n compactType === 'vertical' && typeof y === 'number'\r\n ? oldY >= y\r\n : compactType === 'horizontal' && typeof x === 'number'\r\n ? oldX >= x\r\n : false;\r\n if (movingUp) {\r\n sorted = sorted.reverse();\r\n }\r\n const collisions = getAllCollisions(sorted, l);\r\n\r\n // There was a collision; abort\r\n if (preventCollision && collisions.length) {\r\n log(`Collision prevented on ${l.id}, reverting.`);\r\n l.x = oldX;\r\n l.y = oldY;\r\n l.moved = false;\r\n return layout;\r\n }\r\n\r\n // Move each item that collides away from this element.\r\n for (let i = 0, len = collisions.length; i < len; i++) {\r\n const collision = collisions[i];\r\n log(\r\n `Resolving collision between ${l.id} at [${l.x},${l.y}] and ${\r\n collision.id\r\n } at [${collision.x},${collision.y}]`,\r\n );\r\n\r\n // Short circuit so we can't infinite loop\r\n if (collision.moved) {\r\n continue;\r\n }\r\n\r\n // Don't move static items - we have to move *this* element away\r\n if (collision.static) {\r\n layout = moveElementAwayFromCollision(\r\n layout,\r\n collision,\r\n l,\r\n isUserAction,\r\n compactType,\r\n cols,\r\n );\r\n } else {\r\n layout = moveElementAwayFromCollision(\r\n layout,\r\n l,\r\n collision,\r\n isUserAction,\r\n compactType,\r\n cols,\r\n );\r\n }\r\n }\r\n\r\n return layout;\r\n}\r\n\r\n/**\r\n * This is where the magic needs to happen - given a collision, move an element away from the collision.\r\n * We attempt to move it up if there's room, otherwise it goes below.\r\n *\r\n * @param {Array} layout Full layout to modify.\r\n * @param {LayoutItem} collidesWith Layout item we're colliding with.\r\n * @param {LayoutItem} itemToMove Layout item we're moving.\r\n */\r\nexport function moveElementAwayFromCollision(\r\n layout: Layout,\r\n collidesWith: LayoutItem,\r\n itemToMove: LayoutItem,\r\n isUserAction: boolean | null | undefined,\r\n compactType: CompactType,\r\n cols: number,\r\n): Layout {\r\n const compactH = compactType === 'horizontal';\r\n // Compact vertically if not set to horizontal\r\n const compactV = compactType !== 'horizontal';\r\n const preventCollision = collidesWith.static; // we're already colliding (not for static items)\r\n\r\n // If there is enough space above the collision to put this element, move it there.\r\n // We only do this on the main collision as this can get funky in cascades and cause\r\n // unwanted swapping behavior.\r\n if (isUserAction) {\r\n // Reset isUserAction flag because we're not in the main collision anymore.\r\n isUserAction = false;\r\n\r\n // Make a mock item so we don't modify the item here, only modify in moveElement.\r\n const fakeItem: LayoutItem = {\r\n x: compactH\r\n ? Math.max(collidesWith.x - itemToMove.w, 0)\r\n : itemToMove.x,\r\n y: compactV\r\n ? Math.max(collidesWith.y - itemToMove.h, 0)\r\n : itemToMove.y,\r\n w: itemToMove.w,\r\n h: itemToMove.h,\r\n id: '-1',\r\n };\r\n\r\n // No collision? If so, we can go up there; otherwise, we'll end up moving down as normal\r\n if (!getFirstCollision(layout, fakeItem)) {\r\n log(\r\n `Doing reverse collision on ${itemToMove.id} up to [${\r\n fakeItem.x\r\n },${fakeItem.y}].`,\r\n );\r\n return moveElement(\r\n layout,\r\n itemToMove,\r\n compactH ? fakeItem.x : undefined,\r\n compactV ? fakeItem.y : undefined,\r\n isUserAction,\r\n preventCollision,\r\n compactType,\r\n cols,\r\n );\r\n }\r\n }\r\n\r\n return moveElement(\r\n layout,\r\n itemToMove,\r\n compactH ? itemToMove.x + 1 : undefined,\r\n compactV ? itemToMove.y + 1 : undefined,\r\n isUserAction,\r\n preventCollision,\r\n compactType,\r\n cols,\r\n );\r\n}\r\n\r\n/**\r\n * Helper to convert a number to a percentage string.\r\n *\r\n * @param {Number} num Any number\r\n * @return {String} That number as a percentage.\r\n */\r\nexport function perc(num: number): string {\r\n return num * 100 + '%';\r\n}\r\n\r\nexport function setTransform({top, left, width, height}: Position): Object {\r\n // Replace unitless items with px\r\n const translate = `translate(${left}px,${top}px)`;\r\n return {\r\n transform: translate,\r\n WebkitTransform: translate,\r\n MozTransform: translate,\r\n msTransform: translate,\r\n OTransform: translate,\r\n width: `${width}px`,\r\n height: `${height}px`,\r\n position: 'absolute',\r\n };\r\n}\r\n\r\nexport function setTopLeft({top, left, width, height}: Position): Object {\r\n return {\r\n top: `${top}px`,\r\n left: `${left}px`,\r\n width: `${width}px`,\r\n height: `${height}px`,\r\n position: 'absolute',\r\n };\r\n}\r\n\r\n/**\r\n * Get layout items sorted from top left to right and down.\r\n *\r\n * @return {Array} Array of layout objects.\r\n * @return {Array} Layout, sorted static items first.\r\n */\r\nexport function sortLayoutItems(\r\n layout: Layout,\r\n compactType: CompactType,\r\n): Layout {\r\n if (compactType === 'horizontal') {\r\n return sortLayoutItemsByColRow(layout);\r\n } else {\r\n return sortLayoutItemsByRowCol(layout);\r\n }\r\n}\r\n\r\nexport function sortLayoutItemsByRowCol(layout: Layout): Layout {\r\n return ([] as any[]).concat(layout).sort(function(a, b) {\r\n if (a.y > b.y || (a.y === b.y && a.x > b.x)) {\r\n return 1;\r\n } else if (a.y === b.y && a.x === b.x) {\r\n // Without this, we can get different sort results in IE vs. Chrome/FF\r\n return 0;\r\n }\r\n return -1;\r\n });\r\n}\r\n\r\nexport function sortLayoutItemsByColRow(layout: Layout): Layout {\r\n return ([] as any[]).concat(layout).sort(function(a, b) {\r\n if (a.x > b.x || (a.x === b.x && a.y > b.y)) {\r\n return 1;\r\n }\r\n return -1;\r\n });\r\n}\r\n\r\n/**\r\n * Validate a layout. Throws errors.\r\n *\r\n * @param {Array} layout Array of layout items.\r\n * @param {String} [contextName] Context name for errors.\r\n * @throw {Error} Validation error.\r\n */\r\nexport function validateLayout(\r\n layout: Layout,\r\n contextName: string = 'Layout',\r\n): void {\r\n const subProps = ['x', 'y', 'w', 'h'];\r\n if (!Array.isArray(layout)) {\r\n throw new Error(contextName + ' must be an array!');\r\n }\r\n for (let i = 0, len = layout.length; i < len; i++) {\r\n const item = layout[i];\r\n for (let j = 0; j < subProps.length; j++) {\r\n if (typeof item[subProps[j]] !== 'number') {\r\n throw new Error(\r\n 'ReactGridLayout: ' +\r\n contextName +\r\n '[' +\r\n i +\r\n '].' +\r\n subProps[j] +\r\n ' must be a number!',\r\n );\r\n }\r\n }\r\n if (item.id && typeof item.id !== 'string') {\r\n throw new Error(\r\n 'ReactGridLayout: ' +\r\n contextName +\r\n '[' +\r\n i +\r\n '].i must be a string!',\r\n );\r\n }\r\n if (item.static !== undefined && typeof item.static !== 'boolean') {\r\n throw new Error(\r\n 'ReactGridLayout: ' +\r\n contextName +\r\n '[' +\r\n i +\r\n '].static must be a boolean!',\r\n );\r\n }\r\n }\r\n}\r\n\r\n// Flow can't really figure this out, so we just use Object\r\nexport function autoBindHandlers(el: Object, fns: Array<string>): void {\r\n fns.forEach(key => (el[key] = el[key].bind(el)));\r\n}\r\n\r\nfunction log(...args) {\r\n if (!DEBUG) {\r\n return;\r\n }\r\n // eslint-disable-next-line no-console\r\n console.log(...args);\r\n}\r\n\r\nexport const noop = () => {};\r\n","/** Cached result of whether the user's browser supports passive event listeners. */\r\nlet supportsPassiveEvents: boolean;\r\n\r\n/**\r\n * Checks whether the user's browser supports passive event listeners.\r\n * See: https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md\r\n */\r\nexport function ktdSupportsPassiveEventListeners(): boolean {\r\n if (supportsPassiveEvents == null && typeof window !== 'undefined') {\r\n try {\r\n window.addEventListener('test', null!, Object.defineProperty({}, 'passive', {\r\n get: () => supportsPassiveEvents = true\r\n }));\r\n } finally {\r\n supportsPassiveEvents = supportsPassiveEvents || false;\r\n }\r\n }\r\n\r\n return supportsPassiveEvents;\r\n}\r\n\r\n/**\r\n * Normalizes an `AddEventListener` object to something that can be passed\r\n * to `addEventListener` on any browser, no matter whether it supports the\r\n * `options` parameter.\r\n * @param options Object to be normalized.\r\n */\r\nexport function ktdNormalizePassiveListenerOptions(options: AddEventListenerOptions):\r\n AddEventListenerOptions | boolean {\r\n return ktdSupportsPassiveEventListeners() ? options : !!options.capture;\r\n}\r\n","import { fromEvent, iif, merge, Observable } from 'rxjs';\r\nimport { filter } from 'rxjs/operators';\r\nimport { ktdNormalizePassiveListenerOptions } from './passive-listeners';\r\n\r\n/** Options that can be used to bind a passive event listener. */\r\nconst passiveEventListenerOptions = ktdNormalizePassiveListenerOptions({passive: true});\r\n\r\n/** Options that can be used to bind an active event listener. */\r\nconst activeEventListenerOptions = ktdNormalizePassiveListenerOptions({passive: false});\r\n\r\nlet isMobile: boolean | null = null;\r\n\r\nexport function ktdIsMobileOrTablet(): boolean {\r\n\r\n if (isMobile != null) {\r\n return isMobile;\r\n }\r\n\r\n // Generic match pattern to identify mobile or tablet devices\r\n const isMobileDevice = /Android|webOS|BlackBerry|Windows Phone|iPad|iPhone|iPod/i.test(navigator.userAgent);\r\n\r\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\r\n const isIOSMobileDevice = /iPad|iPhone|iPod/.test(navigator.platform) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);\r\n\r\n isMobile = isMobileDevice || isIOSMobileDevice;\r\n\r\n return isMobile;\r\n}\r\n\r\nexport function ktdIsMouseEvent(event: any): event is MouseEvent {\r\n return (event as MouseEvent).clientX != null;\r\n}\r\n\r\nexport function ktdIsTouchEvent(event: any): event is TouchEvent {\r\n return (event as TouchEvent).touches != null && (event as TouchEvent).touches.length != null;\r\n}\r\n\r\nexport function ktdPointerClientX(event: MouseEvent | TouchEvent): number {\r\n return ktdIsMouseEvent(event) ? event.clientX : event.touches[0].clientX;\r\n}\r\n\r\nexport function ktdPointerClientY(event: MouseEvent | TouchEvent): number {\r\n return ktdIsMouseEvent(event) ? event.clientY : event.touches[0].clientY;\r\n}\r\n\r\nexport function ktdPointerClient(event: MouseEvent | TouchEvent): { clientX: number, clientY: number } {\r\n return {\r\n clientX: ktdIsMouseEvent(event) ? event.clientX : event.touches[0].clientX,\r\n clientY: ktdIsMouseEvent(event) ? event.clientY : event.touches[0].clientY\r\n };\r\n}\r\n\r\nexport function ktdIsMouseEventOrMousePointerEvent(event: MouseEvent | TouchEvent | PointerEvent): boolean {\r\n return event.type === 'mousedown'\r\n || (event.type === 'pointerdown' && (event as PointerEvent).pointerType === 'mouse');\r\n}\r\n\r\n/** Returns true if browser supports pointer events */\r\nexport function ktdSupportsPointerEvents(): boolean {\r\n return !!window.PointerEvent;\r\n}\r\n\r\n/**\r\n * Emits when a mousedown or touchstart emits. Avoids conflicts between both events.\r\n * @param element, html element where to listen the events.\r\n * @param touchNumber number of the touch to track the event, default to the first one.\r\n */\r\nfunction ktdMouseOrTouchDown(element, touchNumber = 1): Observable<MouseEvent | TouchEvent> {\r\n return iif(\r\n () => ktdIsMobileOrTablet(),\r\n fromEvent<TouchEvent>(element, 'touchstart', passiveEventListenerOptions as AddEventListenerOptions).pipe(\r\n filter((touchEvent) => touchEvent.touches.length === touchNumber)\r\n ),\r\n fromEvent<MouseEvent>(element, 'mousedown', activeEventListenerOptions as AddEventListenerOptions).pipe(\r\n filter((mouseEvent: MouseEvent) => {\r\n /**\r\n * 0 : Left mouse button\r\n * 1 : Wheel button or middle button (if present)\r\n * 2 : Right mouse button\r\n */\r\n return mouseEvent.button === 0; // Mouse down to be only fired if is left click\r\n })\r\n )\r\n );\r\n}\r\n\r\n/**\r\n * Emits when a 'mousemove' or a 'touchmove' event gets fired.\r\n * @param element, html element where to listen the events.\r\n * @param touchNumber number of the touch to track the event, default to the first one.\r\n */\r\nfunction ktdMouseOrTouchMove(element: HTMLElement, touchNumber = 1): Observable<MouseEvent | TouchEvent> {\r\n return iif(\r\n () => ktdIsMobileOrTablet(),\r\n fromEvent<TouchEvent>(element, 'touchmove', activeEventListenerOptions as AddEventListenerOptions).pipe(\r\n filter((touchEvent) => touchEvent.touches.length === touchNumber),\r\n ),\r\n fromEvent<MouseEvent>(element, 'mousemove', activeEventListenerOptions as AddEventListenerOptions)\r\n );\r\n}\r\n\r\nexport function ktdTouchEnd(element, touchNumber = 1): Observable<TouchEvent> {\r\n return merge(\r\n fromEvent<TouchEvent>(element, 'touchend').pipe(\r\n filter((touchEvent) => touchEvent.touches.length === touchNumber - 1)\r\n ),\r\n fromEvent<TouchEvent>(element, 'touchcancel').pipe(\r\n filter((touchEvent) => touchEvent.touches.length === touchNumber - 1)\r\n )\r\n );\r\n}\r\n\r\n/**\r\n * Emits when a there is a 'mouseup' or the touch ends.\r\n * @param element, html element where to listen the events.\r\n * @param touchNumber number of the touch to track the event, default to the first one.\r\n */\r\nfunction ktdMouserOrTouchEnd(element: HTMLElement, touchNumber = 1): Observable<MouseEvent | TouchEvent> {\r\n return iif(\r\n () => ktdIsMobileOrTablet(),\r\n ktdTouchEnd(element, touchNumber),\r\n fromEvent<MouseEvent>(element, 'mouseup'),\r\n );\r\n}\r\n\r\n\r\n/**\r\n * Emits when a 'pointerdown' event occurs (only for the primary pointer). Fallbacks to 'mousemove' or a 'touchmove' if pointer events are not supported.\r\n * @param element, html element where to listen the events.\r\n */\r\nexport function ktdPointerDown(element): Observable<MouseEvent | TouchEvent | PointerEvent> {\r\n if (!ktdSupportsPointerEvents()) {\r\n return ktdMouseOrTouchDown(element);\r\n }\r\n\r\n return fromEvent<PointerEvent>(element, 'pointerdown', activeEventListenerOptions as AddEventListenerOptions).pipe(\r\n filter((pointerEvent) => pointerEvent.isPrimary)\r\n )\r\n}\r\n\r\n/**\r\n * Emits when a 'pointermove' event occurs (only for the primary pointer). Fallbacks to 'mousemove' or a 'touchmove' if pointer events are not supported.\r\n * @param element, html element where to listen the events.\r\n */\r\nexport function ktdPointerMove(element): Observable<MouseEvent | TouchEvent | PointerEvent> {\r\n if (!ktdSupportsPointerEvents()) {\r\n return ktdMouseOrTouchMove(element);\r\n }\r\n return fromEvent<PointerEvent>(element, 'pointermove', activeEventListenerOptions as AddEventListenerOptions).pipe(\r\n filter((pointerEvent) => pointerEvent.isPrimary),\r\n );\r\n}\r\n\r\n/**\r\n * Emits when a 'pointerup' event occurs (only for the primary pointer). Fallbacks to 'mousemove' or a 'touchmove' if pointer events are not supported.\r\n * @param element, html element where to listen the events.\r\n */\r\nexport function ktdPointerUp(element): Observable<MouseEvent | TouchEvent | PointerEvent> {\r\n if (!ktdSupportsPointerEvents()) {\r\n return ktdMouserOrTouchEnd(element);\r\n }\r\n return fromEvent<PointerEvent>(element, 'pointerup').pipe(filter(pointerEvent => pointerEvent.isPrimary));\r\n}\r\n","import { compact, CompactType, getFirstCollision, Layout, LayoutItem, moveElement } from './react-grid-layout.utils';\r\nimport {\r\n KtdDraggingData, KtdGridCfg, KtdGridCompactType, KtdGridItemRect, KtdGridItemRenderData, KtdGridLayout, KtdGridLayoutItem\r\n} from '../grid.definitions';\r\nimport { ktdPointerClientX, ktdPointerClientY } from './pointer.utils';\r\nimport { KtdDictionary } from '../../types';\r\nimport { KtdGridItemComponent } from '../grid-item/grid-item.component';\r\n\r\n/** Tracks items by id. This function is mean to be used in conjunction with the ngFor that renders the 'ktd-grid-items' */\r\nexport function ktdTrackById(index: number, item: {id: string}) {\r\n return item.id;\r\n}\r\n\r\n/** Given a layout, the gridHeight and the gap return the resulting rowHeight */\r\nexport function ktdGetGridItemRowHeight(layout: KtdGridLayout, gridHeight: number, gap: number): number {\r\n const numberOfRows = layout.reduce((acc, cur) => Math.max(acc, Math.max(cur.y + cur.h, 0)), 0);\r\n const gapTotalHeight = (numberOfRows - 1) * gap;\r\n const gridHeightMinusGap = gridHeight - gapTotalHeight;\r\n return gridHeightMinusGap / numberOfRows;\r\n}\r\n\r\n/**\r\n * Call react-grid-layout utils 'compact()' function and return the compacted layout.\r\n * @param layout to be compacted.\r\n * @param compactType, type of compaction.\r\n * @param cols, number of columns of the grid.\r\n */\r\nexport function ktdGridCompact(layout: KtdGridLayout, compactType: KtdGridCompactType, cols: number): KtdGridLayout {\r\n return compact(layout, compactType, cols)\r\n // Prune react-grid-layout compact extra properties.\r\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 }));\r\n}\r\n\r\nfunction screenXToGridX(screenXPos: number, cols: number, width: number, gap: number): number {\r\n if (cols <= 1) {\r\n return 0;\r\n }\r\n\r\n const totalGapsWidth = gap * (cols - 1);\r\n const totalItemsWidth = width - totalGapsWidth;\r\n const itemPlusGapWidth = totalItemsWidth / cols + gap;\r\n return Math.round(screenXPos / itemPlusGapWidth);\r\n}\r\n\r\nfunction screenYToGridY(screenYPos: number, rowHeight: number, height: number, gap: number): number {\r\n return Math.round(screenYPos / (rowHeight + gap));\r\n}\r\n\r\nfunction screenWidthToGridWidth(gridScreenWidth: number, cols: number, width: number, gap: number): number {\r\n const widthMinusGaps = width - (gap * (cols - 1));\r\n const itemWidth = widthMinusGaps / cols;\r\n const gridScreenWidthMinusFirst = gridScreenWidth - itemWidth;\r\n return Math.round(gridScreenWidthMinusFirst / (itemWidth + gap)) + 1;\r\n}\r\n\r\nfunction screenHeightToGridHeight(gridScreenHeight: number, rowHeight: number, height: number, gap: number): number {\r\n const gridScreenHeightMinusFirst = gridScreenHeight - rowHeight;\r\n return Math.round(gridScreenHeightMinusFirst / (rowHeight + gap)) + 1;\r\n}\r\n\r\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. */\r\nexport function ktdGetGridLayoutDiff(gridLayoutA: KtdGridLayoutItem[], gridLayoutB: KtdGridLayoutItem[]): KtdDictionary<{ change: 'move' | 'resize' | 'moveresize' }> {\r\n const diff: KtdDictionary<{ change: 'move' | 'resize' | 'moveresize' }> = {};\r\n\r\n gridLayoutA.forEach(itemA => {\r\n const itemB = gridLayoutB.find(_itemB => _itemB.id === itemA.id);\r\n if (itemB != null) {\r\n const posChanged = itemA.x !== itemB.x || itemA.y !== itemB.y;\r\n const sizeChanged = itemA.w !== itemB.w || itemA.h !== itemB.h;\r\n const change: 'move' | 'resize' | 'moveresize' | null = posChanged && sizeChanged ? 'moveresize' : posChanged ? 'move' : sizeChanged ? 'resize' : null;\r\n if (change) {\r\n diff[itemB.id] = {change};\r\n }\r\n }\r\n });\r\n return diff;\r\n}\r\n\r\n/**\r\n * Given the grid config & layout data and the current drag position & information, returns the corresponding layout and drag item position\r\n * @param gridItem grid item that is been dragged\r\n * @param config current grid configuration\r\n * @param compactionType type of compaction that will be performed\r\n * @param draggingData contains all the information about the drag\r\n */\r\nexport function ktdGridItemDragging(gridItem: KtdGridItemComponent, config: KtdGridCfg, compactionType: CompactType, draggingData: KtdDraggingData): { layout: KtdGridLayoutItem[]; draggedItemPos: KtdGridItemRect } {\r\n const {pointerDownEvent, pointerDragEvent, gridElemClientRect, dragElemClientRect, scrollDifference} = draggingData;\r\n\r\n const gridItemId = gridItem.id;\r\n\r\n const draggingElemPrevItem = config.layout.find(item => item.id === gridItemId)!;\r\n\r\n const clientStartX = ktdPointerClientX(pointerDownEvent);\r\n const clientStartY = ktdPointerClientY(pointerDownEvent);\r\n const clientX = ktdPointerClientX(pointerDragEvent);\r\n const clientY = ktdPointerClientY(pointerDragEvent);\r\n\r\n const offsetX = clientStartX - dragElemClientRect.left;\r\n const offsetY = clientStartY - dragElemClientRect.top;\r\n\r\n // Grid element positions taking into account the possible scroll total difference from the beginning.\r\n const gridElementLeftPosition = gridElemClientRect.left + scrollDifference.left;\r\n const gridElementTopPosition = gridElemClientRect.top + scrollDifference.top;\r\n\r\n // Calculate position relative to the grid element.\r\n const gridRelXPos = clientX - gridElementLeftPosition - offsetX;\r\n const gridRelYPos = clientY - gridElementTopPosition - offsetY;\r\n\r\n const rowHeightInPixels = config.rowHeight === 'fit'\r\n ? ktdGetGridItemRowHeight(config.layout, config.height ?? gridElemClientRect.height, config.gap)\r\n : config.rowHeight;\r\n\r\n // Get layout item position\r\n const layoutItem: KtdGridLayoutItem = {\r\n ...draggingElemPrevItem,\r\n x: screenXToGridX(gridRelXPos , config.cols, gridElemClientRect.width, config.gap),\r\n y: screenYToGridY(gridRelYPos, rowHeightInPixels, gridElemClientRect.height, config.gap)\r\n };\r\n\r\n // Correct the values if they overflow, since 'moveElement' function doesn't do it\r\n layoutItem.x = Math.max(0, layoutItem.x);\r\n layoutItem.y = Math.max(0, layoutItem.y);\r\n if (layoutItem.x + layoutItem.w > config.cols) {\r\n layoutItem.x = Math.max(0, config.cols - layoutItem.w);\r\n }\r\n\r\n // Parse to LayoutItem array data in order to use 'react.grid-layout' utils\r\n const layoutItems: LayoutItem[] = config.layout;\r\n const draggedLayoutItem: LayoutItem = layoutItems.find(item => item.id === gridItemId)!;\r\n\r\n let newLayoutItems: LayoutItem[] = moveElement(\r\n layoutItems,\r\n draggedLayoutItem,\r\n layoutItem.x,\r\n layoutItem.y,\r\n true,\r\n config.preventCollision,\r\n compactionType,\r\n config.cols\r\n );\r\n\r\n newLayoutItems = compact(newLayoutItems, compactionType, config.cols);\r\n\r\n return {\r\n layout: newLayoutItems,\r\n draggedItemPos: {\r\n top: gridRelYPos,\r\n left: gridRelXPos,\r\n width: dragElemClientRect.width,\r\n height: dragElemClientRect.height,\r\n }\r\n };\r\n}\r\n\r\n/**\r\n * Given the grid config & layout data and the current drag position & information, returns the corresponding layout and drag item position\r\n * @param gridItem grid item that is been dragged\r\n * @param config current grid configuration\r\n * @param compactionType type of compaction that will be performed\r\n * @param draggingData contains all the information about the drag\r\n */\r\nexport function ktdGridItemResizing(gridItem: KtdGridItemComponent, config: KtdGridCfg, compactionType: CompactType, draggingData: KtdDraggingData): { layout: KtdGridLayoutItem[]; draggedItemPos: KtdGridItemRect } {\r\n const {pointerDownEvent, pointerDragEvent, gridElemClientRect, dragElemClientRect, scrollDifference} = draggingData;\r\n const gridItemId = gridItem.id;\r\n\r\n const clientStartX = ktdPointerClientX(pointerDownEvent);\r\n const clientStartY = ktdPointerClientY(pointerDownEvent);\r\n const clientX = ktdPointerClientX(pointerDragEvent);\r\n const clientY = ktdPointerClientY(pointerDragEvent);\r\n\r\n // Get the difference between the mouseDown and the position 'right' of the resize element.\r\n const resizeElemOffsetX = dragElemClientRect.width - (clientStartX - dragElemClientRect.left);\r\n const resizeElemOffsetY = dragElemClientRect.height - (clientStartY - dragElemClientRect.top);\r\n\r\n const draggingElemPrevItem = config.layout.find(item => item.id === gridItemId)!;\r\n const width = clientX + resizeElemOffsetX - (dragElemClientRect.left + scrollDifference.left);\r\n const height = clientY + resizeElemOffsetY - (dragElemClientRect.top + scrollDifference.top);\r\n\r\n const rowHeightInPixels = config.rowHeight === 'fit'\r\n ? ktdGetGridItemRowHeight(config.layout, config.height ?? gridElemClientRect.height, config.gap)\r\n : config.rowHeight;\r\n\r\n // Get layout item grid position\r\n const layoutItem: KtdGridLayoutItem = {\r\n ...draggingElemPrevItem,\r\n w: screenWidthToGridWidth(width, config.cols, gridElemClientRect.width, config.gap),\r\n h: screenHeightToGridHeight(height, rowHeightInPixels, gridElemClientRect.height, config.gap)\r\n };\r\n\r\n layoutItem.w = limitNumberWithinRange(layoutItem.w, gridItem.minW ?? layoutItem.minW, gridItem.maxW ?? layoutItem.maxW);\r\n layoutItem.h = limitNumberWithinRange(layoutItem.h, gridItem.minH ?? layoutItem.minH, gridItem.maxH ?? layoutItem.maxH);\r\n\r\n if (layoutItem.x + layoutItem.w > config.cols) {\r\n layoutItem.w = Math.max(1, config.cols - layoutItem.x);\r\n }\r\n\r\n if (config.preventCollision) {\r\n const maxW = layoutItem.w;\r\n const maxH = layoutItem.h;\r\n\r\n let colliding = hasCollision(config.layout, layoutItem);\r\n let shrunkDimension: 'w' | 'h' | undefined;\r\n\r\n while (colliding) {\r\n shrunkDimension = getDimensionToShrink(layoutItem, shrunkDimension);\r\n layoutItem[shrunkDimension]--;\r\n colliding = hasCollision(config.layout, layoutItem);\r\n }\r\n\r\n if (shrunkDimension === 'w') {\r\n layoutItem.h = maxH;\r\n\r\n colliding = hasCollision(config.layout, layoutItem);\r\n while (colliding) {\r\n layoutItem.h--;\r\n colliding = hasCollision(config.layout, layoutItem);\r\n }\r\n }\r\n if (shrunkDimension === 'h') {\r\n layoutItem.w = maxW;\r\n\r\n colliding = hasCollision(config.layout, layoutItem);\r\n while (colliding) {\r\n layoutItem.w--;\r\n colliding = hasCollision(config.layout, layoutItem);\r\n }\r\n }\r\n\r\n }\r\n\r\n const newLayoutItems: LayoutItem[] = config.layout.map((item) => {\r\n return item.id === gridItemId ? layoutItem : item;\r\n });\r\n\r\n return {\r\n layout: compact(newLayoutItems, compactionType, config.cols),\r\n draggedItemPos: {\r\n top: dragElemClientRect.top - gridElemClientRect.top,\r\n left: dragElemClientRect.left - gridElemClientRect.left,\r\n width,\r\n height,\r\n }\r\n };\r\n}\r\n\r\nfunction hasCollision(layout: Layout, layoutItem: LayoutItem): boolean {\r\n return !!getFirstCollision(layout, layoutItem);\r\n}\r\n\r\nfunction getDimensionToShrink(layoutItem, lastShrunk): 'w' | 'h' {\r\n if (layoutItem.h <= 1) {\r\n return 'w';\r\n }\r\n if (layoutItem.w <= 1) {\r\n return 'h';\r\n }\r\n\r\n return lastShrunk === 'w' ? 'h' : 'w';\r\n}\r\n\r\n/**\r\n * Given the current number and min/max values, returns the number within the range\r\n * @param number can be any numeric value\r\n * @param min minimum value of range\r\n * @param max maximum value of range\r\n */\r\nfunction limitNumberWithinRange(num: number, min: number = 1, max: number = Infinity) {\r\n return Math.min(Math.max(num, min < 1 ? 1 : min), max);\r\n}\r\n\r\n/** Returns true if both item1 and item2 KtdGridLayoutItems are equivalent. */\r\nexport function ktdGridItemLayoutItemAreEqual(item1: KtdGridLayoutItem, item2: KtdGridLayoutItem): boolean {\r\n return item1.id === item2.id\r\n && item1.x === item2.x\r\n && item1.y === item2.y\r\n && item1.w === item2.w\r\n && item1.h === item2.h\r\n}\r\n","import { Directive, ElementRef, InjectionToken } from '@angular/core';\r\n\r\n/**\r\n * Injection token that can be used to reference instances of `KtdGridDragHandle`. It serves as\r\n * alternative token to the actual `KtdGridDragHandle` class which could cause unnecessary\r\n * retention of the class and its directive metadata.\r\n */\r\nexport const KTD_GRID_DRAG_HANDLE = new InjectionToken<KtdGridDragHandle>('KtdGridDragHandle');\r\n\r\n/** Handle that can be used to drag a KtdGridItem instance. */\r\n@Directive({\r\n standalone: true,\r\n selector: '[ktdGridDragHandle]',\r\n // eslint-disable-next-line @angular-eslint/no-host-metadata-property\r\n host: {\r\n class: 'ktd-grid-drag-handle'\r\n },\r\n providers: [{provide: KTD_GRID_DRAG_HANDLE, useExisting: KtdGridDragHandle}],\r\n})\r\n// eslint-disable-next-line @angular-eslint/directive-class-suffix\r\nexport class KtdGridDragHandle {\r\n constructor(\r\n public element: ElementRef<HTMLElement>) {\r\n }\r\n}\r\n","import { Directive, ElementRef, InjectionToken, } from '@angular/core';\r\n\r\n\r\n/**\r\n * Injection token that can be used to reference instances of `KtdGridResizeHandle`. It serves as\r\n * alternative token to the actual `KtdGridResizeHandle` class which could cause unnecessary\r\n * retention of the class and its directive metadata.\r\n */\r\nexport const KTD_GRID_RESIZE_HANDLE = new InjectionToken<KtdGridResizeHandle>('KtdGridResizeHandle');\r\n\r\n/** Handle that can be used to drag a KtdGridItem instance. */\r\n@Directive({\r\n standalone: true,\r\n selector: '[ktdGridResizeHandle]',\r\n // eslint-disable-next-line @angular-eslint/no-host-metadata-property\r\n host: {\r\n class: 'ktd-grid-resize-handle'\r\n },\r\n providers: [{provide: KTD_GRID_RESIZE_HANDLE, useExisting: KtdGridResizeHandle}],\r\n})\r\n// eslint-disable-next-line @angular-eslint/directive-class-suffix\r\nexport class KtdGridResizeHandle {\r\n\r\n constructor(\r\n public element: ElementRef<HTMLElement>) {\r\n }\r\n}\r\n","import { Directive, InjectionToken, Input, TemplateRef } from '@angular/core';\r\n\r\n/**\r\n * Injection token that can be used to reference instances of `KtdGridItemPlaceholder`. It serves as\r\n * alternative token to the actual `KtdGridItemPlaceholder` class which could cause unnecessary\r\n * retention of the class and its directive metadata.\r\n */\r\nexport const KTD_GRID_ITEM_PLACEHOLDER = new InjectionToken<KtdGridItemPlaceholder>('KtdGridItemPlaceholder');\r\n\r\n/** Directive that can be used to create a custom placeholder for a KtdGridItem instance. */\r\n@Directive({\r\n standalone: true,\r\n selector: 'ng-template[ktdGridItemPlaceholder]',\r\n // eslint-disable-next-line @angular-eslint/no-host-metadata-property\r\n host: {\r\n class: 'ktd-grid-item-placeholder-content'\r\n },\r\n providers: [{provide: KTD_GRID_ITEM_PLACEHOLDER, useExisting: KtdGridItemPlaceholder}],\r\n})\r\n// eslint-disable-next-line @angular-eslint/directive-class-suffix\r\nexport class KtdGridItemPlaceholder<T = any> {\r\n /** Context data to be added to the placeholder template instance. */\r\n @Input() data: T;\r\n constructor(public templateRef: TemplateRef<T>) {}\r\n}\r\n","/* eslint-disable */\r\n/**\r\n * Type describing the allowed values for a boolean input.\r\n * @docs-private\r\n */\r\nexport type BooleanInput = string | boolean | null | undefined;\r\n\r\n/** Coerces a data-bound value (typically a string) to a boolean. */\r\nexport function coerceBooleanProperty(value: any): boolean {\r\n return value != null && `${value}` !== 'false';\r\n}\r\n","/* eslint-disable */\r\nexport type NumberInput = string | number | null | undefined;\r\n\r\n/** Coerces a data-bound value (typically a string) to a number. */\r\nexport function coerceNumberProperty(value: any): number;\r\nexport function coerceNumberProperty<D>(value: any, fallback: D): number | D;\r\nexport functi