@angular/cdk
Version:
Angular Material Component Development Kit
1 lines • 331 kB
Source Map (JSON)
{"version":3,"file":"drag-drop.mjs","sources":["../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/dom/clone-node.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/dom/dom-rect.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/dom/parent-position-tracker.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/dom/root-node.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/dom/styling.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/dom/transition-duration.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/preview-ref.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/drag-ref.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/drag-utils.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/sorting/single-axis-sort-strategy.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/sorting/mixed-sort-strategy.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/drop-list-ref.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/drag-drop-registry.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/drag-drop.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/drag-parent.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/directives/assertions.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/directives/drag-handle.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/directives/config.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/directives/drag.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/directives/drop-list-group.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/directives/drop-list.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/directives/drag-preview.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/directives/drag-placeholder.ts","../../../../../k8-fastbuild-ST-46c76129e412/bin/src/cdk/drag-drop/drag-drop-module.ts"],"sourcesContent":["/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/** Creates a deep clone of an element. */\nexport function deepCloneNode(node: HTMLElement): HTMLElement {\n const clone = node.cloneNode(true) as HTMLElement;\n const descendantsWithId = clone.querySelectorAll('[id]');\n const nodeName = node.nodeName.toLowerCase();\n\n // Remove the `id` to avoid having multiple elements with the same id on the page.\n clone.removeAttribute('id');\n\n for (let i = 0; i < descendantsWithId.length; i++) {\n descendantsWithId[i].removeAttribute('id');\n }\n\n if (nodeName === 'canvas') {\n transferCanvasData(node as HTMLCanvasElement, clone as HTMLCanvasElement);\n } else if (nodeName === 'input' || nodeName === 'select' || nodeName === 'textarea') {\n transferInputData(node as HTMLInputElement, clone as HTMLInputElement);\n }\n\n transferData('canvas', node, clone, transferCanvasData);\n transferData('input, textarea, select', node, clone, transferInputData);\n return clone;\n}\n\n/** Matches elements between an element and its clone and allows for their data to be cloned. */\nfunction transferData<T extends Element>(\n selector: string,\n node: HTMLElement,\n clone: HTMLElement,\n callback: (source: T, clone: T) => void,\n) {\n const descendantElements = node.querySelectorAll<T>(selector);\n\n if (descendantElements.length) {\n const cloneElements = clone.querySelectorAll<T>(selector);\n\n for (let i = 0; i < descendantElements.length; i++) {\n callback(descendantElements[i], cloneElements[i]);\n }\n }\n}\n\n// Counter for unique cloned radio button names.\nlet cloneUniqueId = 0;\n\n/** Transfers the data of one input element to another. */\nfunction transferInputData(\n source: Element & {value: string},\n clone: Element & {value: string; name: string; type: string},\n) {\n // Browsers throw an error when assigning the value of a file input programmatically.\n if (clone.type !== 'file') {\n clone.value = source.value;\n }\n\n // Radio button `name` attributes must be unique for radio button groups\n // otherwise original radio buttons can lose their checked state\n // once the clone is inserted in the DOM.\n if (clone.type === 'radio' && clone.name) {\n clone.name = `mat-clone-${clone.name}-${cloneUniqueId++}`;\n }\n}\n\n/** Transfers the data of one canvas element to another. */\nfunction transferCanvasData(source: HTMLCanvasElement, clone: HTMLCanvasElement) {\n const context = clone.getContext('2d');\n\n if (context) {\n // In some cases `drawImage` can throw (e.g. if the canvas size is 0x0).\n // We can't do much about it so just ignore the error.\n try {\n context.drawImage(source, 0, 0);\n } catch {}\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/** Gets a mutable version of an element's bounding `DOMRect`. */\nexport function getMutableClientRect(element: Element): DOMRect {\n const rect = element.getBoundingClientRect();\n\n // We need to clone the `clientRect` here, because all the values on it are readonly\n // and we need to be able to update them. Also we can't use a spread here, because\n // the values on a `DOMRect` aren't own properties. See:\n // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes\n return {\n top: rect.top,\n right: rect.right,\n bottom: rect.bottom,\n left: rect.left,\n width: rect.width,\n height: rect.height,\n x: rect.x,\n y: rect.y,\n } as DOMRect;\n}\n\n/**\n * Checks whether some coordinates are within a `DOMRect`.\n * @param clientRect DOMRect that is being checked.\n * @param x Coordinates along the X axis.\n * @param y Coordinates along the Y axis.\n */\nexport function isInsideClientRect(clientRect: DOMRect, x: number, y: number) {\n const {top, bottom, left, right} = clientRect;\n return y >= top && y <= bottom && x >= left && x <= right;\n}\n\n/**\n * Updates the top/left positions of a `DOMRect`, as well as their bottom/right counterparts.\n * @param domRect `DOMRect` that should be updated.\n * @param top Amount to add to the `top` position.\n * @param left Amount to add to the `left` position.\n */\nexport function adjustDomRect(\n domRect: {\n top: number;\n bottom: number;\n left: number;\n right: number;\n width: number;\n height: number;\n },\n top: number,\n left: number,\n) {\n domRect.top += top;\n domRect.bottom = domRect.top + domRect.height;\n\n domRect.left += left;\n domRect.right = domRect.left + domRect.width;\n}\n\n/**\n * Checks whether the pointer coordinates are close to a DOMRect.\n * @param rect DOMRect to check against.\n * @param threshold Threshold around the DOMRect.\n * @param pointerX Coordinates along the X axis.\n * @param pointerY Coordinates along the Y axis.\n */\nexport function isPointerNearDomRect(\n rect: DOMRect,\n threshold: number,\n pointerX: number,\n pointerY: number,\n): boolean {\n const {top, right, bottom, left, width, height} = rect;\n const xThreshold = width * threshold;\n const yThreshold = height * threshold;\n\n return (\n pointerY > top - yThreshold &&\n pointerY < bottom + yThreshold &&\n pointerX > left - xThreshold &&\n pointerX < right + xThreshold\n );\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {_getEventTarget} from '../../platform';\nimport {getMutableClientRect, adjustDomRect} from './dom-rect';\n\n/** Object holding the scroll position of something. */\ninterface ScrollPosition {\n top: number;\n left: number;\n}\n\n/** Keeps track of the scroll position and dimensions of the parents of an element. */\nexport class ParentPositionTracker {\n /** Cached positions of the scrollable parent elements. */\n readonly positions = new Map<\n Document | HTMLElement,\n {\n scrollPosition: ScrollPosition;\n clientRect?: DOMRect;\n }\n >();\n\n constructor(private _document: Document) {}\n\n /** Clears the cached positions. */\n clear() {\n this.positions.clear();\n }\n\n /** Caches the positions. Should be called at the beginning of a drag sequence. */\n cache(elements: readonly HTMLElement[]) {\n this.clear();\n this.positions.set(this._document, {\n scrollPosition: this.getViewportScrollPosition(),\n });\n\n elements.forEach(element => {\n this.positions.set(element, {\n scrollPosition: {top: element.scrollTop, left: element.scrollLeft},\n clientRect: getMutableClientRect(element),\n });\n });\n }\n\n /** Handles scrolling while a drag is taking place. */\n handleScroll(event: Event): ScrollPosition | null {\n const target = _getEventTarget<HTMLElement | Document>(event)!;\n const cachedPosition = this.positions.get(target);\n\n if (!cachedPosition) {\n return null;\n }\n\n const scrollPosition = cachedPosition.scrollPosition;\n let newTop: number;\n let newLeft: number;\n\n if (target === this._document) {\n const viewportScrollPosition = this.getViewportScrollPosition();\n newTop = viewportScrollPosition.top;\n newLeft = viewportScrollPosition.left;\n } else {\n newTop = (target as HTMLElement).scrollTop;\n newLeft = (target as HTMLElement).scrollLeft;\n }\n\n const topDifference = scrollPosition.top - newTop;\n const leftDifference = scrollPosition.left - newLeft;\n\n // Go through and update the cached positions of the scroll\n // parents that are inside the element that was scrolled.\n this.positions.forEach((position, node) => {\n if (position.clientRect && target !== node && target.contains(node)) {\n adjustDomRect(position.clientRect, topDifference, leftDifference);\n }\n });\n\n scrollPosition.top = newTop;\n scrollPosition.left = newLeft;\n\n return {top: topDifference, left: leftDifference};\n }\n\n /**\n * Gets the scroll position of the viewport. Note that we use the scrollX and scrollY directly,\n * instead of going through the `ViewportRuler`, because the first value the ruler looks at is\n * the top/left offset of the `document.documentElement` which works for most cases, but breaks\n * if the element is offset by something like the `BlockScrollStrategy`.\n */\n getViewportScrollPosition() {\n return {top: window.scrollY, left: window.scrollX};\n }\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {EmbeddedViewRef} from '@angular/core';\n\n/**\n * Gets the root HTML element of an embedded view.\n * If the root is not an HTML element it gets wrapped in one.\n */\nexport function getRootNode(viewRef: EmbeddedViewRef<any>, _document: Document): HTMLElement {\n const rootNodes: Node[] = viewRef.rootNodes;\n\n if (rootNodes.length === 1 && rootNodes[0].nodeType === _document.ELEMENT_NODE) {\n return rootNodes[0] as HTMLElement;\n }\n\n const wrapper = _document.createElement('div');\n rootNodes.forEach(node => wrapper.appendChild(node));\n return wrapper;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/**\n * Extended CSSStyleDeclaration that includes a couple of drag-related\n * properties that aren't in the built-in TS typings.\n */\nexport interface DragCSSStyleDeclaration extends CSSStyleDeclaration {\n msScrollSnapType: string;\n scrollSnapType: string;\n webkitTapHighlightColor: string;\n}\n\n/**\n * Shallow-extends a stylesheet object with another stylesheet-like object.\n * Note that the keys in `source` have to be dash-cased.\n * @docs-private\n */\nexport function extendStyles(\n dest: CSSStyleDeclaration,\n source: Record<string, string>,\n importantProperties?: Set<string>,\n) {\n for (let key in source) {\n if (source.hasOwnProperty(key)) {\n const value = source[key];\n\n if (value) {\n dest.setProperty(key, value, importantProperties?.has(key) ? 'important' : '');\n } else {\n dest.removeProperty(key);\n }\n }\n }\n\n return dest;\n}\n\n/**\n * Toggles whether the native drag interactions should be enabled for an element.\n * @param element Element on which to toggle the drag interactions.\n * @param enable Whether the drag interactions should be enabled.\n * @docs-private\n */\nexport function toggleNativeDragInteractions(element: HTMLElement, enable: boolean) {\n const userSelect = enable ? '' : 'none';\n\n extendStyles(element.style, {\n 'touch-action': enable ? '' : 'none',\n '-webkit-user-drag': enable ? '' : 'none',\n '-webkit-tap-highlight-color': enable ? '' : 'transparent',\n 'user-select': userSelect,\n '-ms-user-select': userSelect,\n '-webkit-user-select': userSelect,\n '-moz-user-select': userSelect,\n });\n}\n\n/**\n * Toggles whether an element is visible while preserving its dimensions.\n * @param element Element whose visibility to toggle\n * @param enable Whether the element should be visible.\n * @param importantProperties Properties to be set as `!important`.\n * @docs-private\n */\nexport function toggleVisibility(\n element: HTMLElement,\n enable: boolean,\n importantProperties?: Set<string>,\n) {\n extendStyles(\n element.style,\n {\n position: enable ? '' : 'fixed',\n top: enable ? '' : '0',\n opacity: enable ? '' : '0',\n left: enable ? '' : '-999em',\n },\n importantProperties,\n );\n}\n\n/**\n * Combines a transform string with an optional other transform\n * that exited before the base transform was applied.\n */\nexport function combineTransforms(transform: string, initialTransform?: string): string {\n return initialTransform && initialTransform != 'none'\n ? transform + ' ' + initialTransform\n : transform;\n}\n\n/**\n * Matches the target element's size to the source's size.\n * @param target Element that needs to be resized.\n * @param sourceRect Dimensions of the source element.\n */\nexport function matchElementSize(target: HTMLElement, sourceRect: DOMRect): void {\n target.style.width = `${sourceRect.width}px`;\n target.style.height = `${sourceRect.height}px`;\n target.style.transform = getTransform(sourceRect.left, sourceRect.top);\n}\n\n/**\n * Gets a 3d `transform` that can be applied to an element.\n * @param x Desired position of the element along the X axis.\n * @param y Desired position of the element along the Y axis.\n */\nexport function getTransform(x: number, y: number): string {\n // Round the transforms since some browsers will\n // blur the elements for sub-pixel transforms.\n return `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\n/** Parses a CSS time value to milliseconds. */\nfunction parseCssTimeUnitsToMs(value: string): number {\n // Some browsers will return it in seconds, whereas others will return milliseconds.\n const multiplier = value.toLowerCase().indexOf('ms') > -1 ? 1 : 1000;\n return parseFloat(value) * multiplier;\n}\n\n/** Gets the transform transition duration, including the delay, of an element in milliseconds. */\nexport function getTransformTransitionDurationInMs(element: HTMLElement): number {\n const computedStyle = getComputedStyle(element);\n const transitionedProperties = parseCssPropertyValue(computedStyle, 'transition-property');\n const property = transitionedProperties.find(prop => prop === 'transform' || prop === 'all');\n\n // If there's no transition for `all` or `transform`, we shouldn't do anything.\n if (!property) {\n return 0;\n }\n\n // Get the index of the property that we're interested in and match\n // it up to the same index in `transition-delay` and `transition-duration`.\n const propertyIndex = transitionedProperties.indexOf(property);\n const rawDurations = parseCssPropertyValue(computedStyle, 'transition-duration');\n const rawDelays = parseCssPropertyValue(computedStyle, 'transition-delay');\n\n return (\n parseCssTimeUnitsToMs(rawDurations[propertyIndex]) +\n parseCssTimeUnitsToMs(rawDelays[propertyIndex])\n );\n}\n\n/** Parses out multiple values from a computed style into an array. */\nfunction parseCssPropertyValue(computedStyle: CSSStyleDeclaration, name: string): string[] {\n const value = computedStyle.getPropertyValue(name);\n return value.split(',').map(part => part.trim());\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {EmbeddedViewRef, Renderer2, TemplateRef, ViewContainerRef} from '@angular/core';\nimport {Direction} from '../bidi';\nimport {\n extendStyles,\n getTransform,\n matchElementSize,\n toggleNativeDragInteractions,\n} from './dom/styling';\nimport {deepCloneNode} from './dom/clone-node';\nimport {getRootNode} from './dom/root-node';\nimport {getTransformTransitionDurationInMs} from './dom/transition-duration';\n\n/** Template that can be used to create a drag preview element. */\nexport interface DragPreviewTemplate<T = any> {\n matchSize?: boolean;\n template: TemplateRef<T> | null;\n viewContainer: ViewContainerRef;\n context: T;\n}\n\n/** Inline styles to be set as `!important` while dragging. */\nconst importantProperties = new Set([\n // Needs to be important, because some `mat-table` sets `position: sticky !important`. See #22781.\n 'position',\n]);\n\nexport class PreviewRef {\n /** Reference to the view of the preview element. */\n private _previewEmbeddedView: EmbeddedViewRef<any> | null;\n\n /** Reference to the preview element. */\n private _preview: HTMLElement;\n\n get element(): HTMLElement {\n return this._preview;\n }\n\n constructor(\n private _document: Document,\n private _rootElement: HTMLElement,\n private _direction: Direction,\n private _initialDomRect: DOMRect,\n private _previewTemplate: DragPreviewTemplate | null,\n private _previewClass: string | string[] | null,\n private _pickupPositionOnPage: {\n x: number;\n y: number;\n },\n private _initialTransform: string | null,\n private _zIndex: number,\n private _renderer: Renderer2,\n ) {}\n\n attach(parent: HTMLElement): void {\n this._preview = this._createPreview();\n parent.appendChild(this._preview);\n\n // The null check is necessary for browsers that don't support the popover API.\n // Note that we use a string access for compatibility with Closure.\n if (supportsPopover(this._preview)) {\n this._preview['showPopover']();\n }\n }\n\n destroy(): void {\n this._preview.remove();\n this._previewEmbeddedView?.destroy();\n this._preview = this._previewEmbeddedView = null!;\n }\n\n setTransform(value: string): void {\n this._preview.style.transform = value;\n }\n\n getBoundingClientRect(): DOMRect {\n return this._preview.getBoundingClientRect();\n }\n\n addClass(className: string): void {\n this._preview.classList.add(className);\n }\n\n getTransitionDuration(): number {\n return getTransformTransitionDurationInMs(this._preview);\n }\n\n addEventListener(name: string, handler: (event: any) => void): () => void {\n return this._renderer.listen(this._preview, name, handler);\n }\n\n private _createPreview(): HTMLElement {\n const previewConfig = this._previewTemplate;\n const previewClass = this._previewClass;\n const previewTemplate = previewConfig ? previewConfig.template : null;\n let preview: HTMLElement;\n\n if (previewTemplate && previewConfig) {\n // Measure the element before we've inserted the preview\n // since the insertion could throw off the measurement.\n const rootRect = previewConfig.matchSize ? this._initialDomRect : null;\n const viewRef = previewConfig.viewContainer.createEmbeddedView(\n previewTemplate,\n previewConfig.context,\n );\n viewRef.detectChanges();\n preview = getRootNode(viewRef, this._document);\n this._previewEmbeddedView = viewRef;\n if (previewConfig.matchSize) {\n matchElementSize(preview, rootRect!);\n } else {\n preview.style.transform = getTransform(\n this._pickupPositionOnPage.x,\n this._pickupPositionOnPage.y,\n );\n }\n } else {\n preview = deepCloneNode(this._rootElement);\n matchElementSize(preview, this._initialDomRect!);\n\n if (this._initialTransform) {\n preview.style.transform = this._initialTransform;\n }\n }\n\n extendStyles(\n preview.style,\n {\n // It's important that we disable the pointer events on the preview, because\n // it can throw off the `document.elementFromPoint` calls in the `CdkDropList`.\n 'pointer-events': 'none',\n // If the preview has a margin, it can throw off our positioning so we reset it. The reset\n // value for `margin-right` needs to be `auto` when opened as a popover, because our\n // positioning is always top/left based, but native popover seems to position itself\n // to the top/right if `<html>` or `<body>` have `dir=\"rtl\"` (see #29604). Setting it\n // to `auto` pushed it to the top/left corner in RTL and is a noop in LTR.\n 'margin': supportsPopover(preview) ? '0 auto 0 0' : '0',\n 'position': 'fixed',\n 'top': '0',\n 'left': '0',\n 'z-index': this._zIndex + '',\n },\n importantProperties,\n );\n\n toggleNativeDragInteractions(preview, false);\n preview.classList.add('cdk-drag-preview');\n preview.setAttribute('popover', 'manual');\n preview.setAttribute('dir', this._direction);\n\n if (previewClass) {\n if (Array.isArray(previewClass)) {\n previewClass.forEach(className => preview.classList.add(className));\n } else {\n preview.classList.add(previewClass);\n }\n }\n\n return preview;\n }\n}\n\n/** Checks whether a specific element supports the popover API. */\nfunction supportsPopover(element: HTMLElement): boolean {\n return 'showPopover' in element;\n}\n","/**\n * @license\n * Copyright Google LLC All Rights Reserved.\n *\n * Use of this source code is governed by an MIT-style license that can be\n * found in the LICENSE file at https://angular.dev/license\n */\n\nimport {isFakeMousedownFromScreenReader, isFakeTouchstartFromScreenReader} from '../a11y';\nimport {Direction} from '../bidi';\nimport {coerceElement} from '../coercion';\nimport {_getEventTarget, _getShadowRoot, _bindEventWithOptions} from '../platform';\nimport {ViewportRuler} from '../scrolling';\nimport {\n ElementRef,\n EmbeddedViewRef,\n NgZone,\n Renderer2,\n TemplateRef,\n ViewContainerRef,\n signal,\n} from '@angular/core';\nimport {Observable, Subject, Subscription} from 'rxjs';\nimport {deepCloneNode} from './dom/clone-node';\nimport {adjustDomRect, getMutableClientRect} from './dom/dom-rect';\nimport {ParentPositionTracker} from './dom/parent-position-tracker';\nimport {getRootNode} from './dom/root-node';\nimport {\n DragCSSStyleDeclaration,\n combineTransforms,\n getTransform,\n toggleNativeDragInteractions,\n toggleVisibility,\n} from './dom/styling';\nimport {DragDropRegistry} from './drag-drop-registry';\nimport type {DropListRef} from './drop-list-ref';\nimport {DragPreviewTemplate, PreviewRef} from './preview-ref';\n\n/** Object that can be used to configure the behavior of DragRef. */\nexport interface DragRefConfig {\n /**\n * Minimum amount of pixels that the user should\n * drag, before the CDK initiates a drag sequence.\n */\n dragStartThreshold: number;\n\n /**\n * Amount the pixels the user should drag before the CDK\n * considers them to have changed the drag direction.\n */\n pointerDirectionChangeThreshold: number;\n\n /** `z-index` for the absolutely-positioned elements that are created by the drag item. */\n zIndex?: number;\n\n /** Ref that the current drag item is nested in. */\n parentDragRef?: DragRef;\n}\n\n/** Options that can be used to bind a passive event listener. */\nconst passiveEventListenerOptions = {passive: true};\n\n/** Options that can be used to bind an active event listener. */\nconst activeEventListenerOptions = {passive: false};\n\n/** Event options that can be used to bind an active, capturing event. */\nconst activeCapturingEventOptions = {\n passive: false,\n capture: true,\n};\n\n/**\n * Time in milliseconds for which to ignore mouse events, after\n * receiving a touch event. Used to avoid doing double work for\n * touch devices where the browser fires fake mouse events, in\n * addition to touch events.\n */\nconst MOUSE_EVENT_IGNORE_TIME = 800;\n\n// TODO(crisbeto): add an API for moving a draggable up/down the\n// list programmatically. Useful for keyboard controls.\n\n/** Template that can be used to create a drag helper element (e.g. a preview or a placeholder). */\ninterface DragHelperTemplate<T = any> {\n template: TemplateRef<T> | null;\n viewContainer: ViewContainerRef;\n context: T;\n}\n\n/** Point on the page or within an element. */\nexport interface Point {\n x: number;\n y: number;\n}\n\n/** Inline styles to be set as `!important` while dragging. */\nconst dragImportantProperties = new Set([\n // Needs to be important, because some `mat-table` sets `position: sticky !important`. See #22781.\n 'position',\n]);\n\n/**\n * Possible places into which the preview of a drag item can be inserted.\n * - `global` - Preview will be inserted at the bottom of the `<body>`. The advantage is that\n * you don't have to worry about `overflow: hidden` or `z-index`, but the item won't retain\n * its inherited styles.\n * - `parent` - Preview will be inserted into the parent of the drag item. The advantage is that\n * inherited styles will be preserved, but it may be clipped by `overflow: hidden` or not be\n * visible due to `z-index`. Furthermore, the preview is going to have an effect over selectors\n * like `:nth-child` and some flexbox configurations.\n * - `ElementRef<HTMLElement> | HTMLElement` - Preview will be inserted into a specific element.\n * Same advantages and disadvantages as `parent`.\n */\nexport type PreviewContainer = 'global' | 'parent' | ElementRef<HTMLElement> | HTMLElement;\n\n/**\n * Reference to a draggable item. Used to manipulate or dispose of the item.\n */\nexport class DragRef<T = any> {\n private _rootElementCleanups: (() => void)[] | undefined;\n private _cleanupShadowRootSelectStart: (() => void) | undefined;\n\n /** Element displayed next to the user's pointer while the element is dragged. */\n private _preview: PreviewRef | null;\n\n /** Container into which to insert the preview. */\n private _previewContainer: PreviewContainer | undefined;\n\n /** Reference to the view of the placeholder element. */\n private _placeholderRef: EmbeddedViewRef<any> | null;\n\n /** Element that is rendered instead of the draggable item while it is being sorted. */\n private _placeholder: HTMLElement;\n\n /** Coordinates within the element at which the user picked up the element. */\n private _pickupPositionInElement: Point;\n\n /** Coordinates on the page at which the user picked up the element. */\n private _pickupPositionOnPage: Point;\n\n /**\n * Anchor node used to save the place in the DOM where the element was\n * picked up so that it can be restored at the end of the drag sequence.\n */\n private _anchor: Comment;\n\n /**\n * CSS `transform` applied to the element when it isn't being dragged. We need a\n * passive transform in order for the dragged element to retain its new position\n * after the user has stopped dragging and because we need to know the relative\n * position in case they start dragging again. This corresponds to `element.style.transform`.\n */\n private _passiveTransform: Point = {x: 0, y: 0};\n\n /** CSS `transform` that is applied to the element while it's being dragged. */\n private _activeTransform: Point = {x: 0, y: 0};\n\n /** Inline `transform` value that the element had before the first dragging sequence. */\n private _initialTransform?: string;\n\n /**\n * Whether the dragging sequence has been started. Doesn't\n * necessarily mean that the element has been moved.\n */\n private _hasStartedDragging = signal(false);\n\n /** Whether the element has moved since the user started dragging it. */\n private _hasMoved: boolean;\n\n /** Drop container in which the DragRef resided when dragging began. */\n private _initialContainer: DropListRef;\n\n /** Index at which the item started in its initial container. */\n private _initialIndex: number;\n\n /** Cached positions of scrollable parent elements. */\n private _parentPositions: ParentPositionTracker;\n\n /** Emits when the item is being moved. */\n private readonly _moveEvents = new Subject<{\n source: DragRef;\n pointerPosition: {x: number; y: number};\n event: MouseEvent | TouchEvent;\n distance: Point;\n delta: {x: -1 | 0 | 1; y: -1 | 0 | 1};\n }>();\n\n /** Keeps track of the direction in which the user is dragging along each axis. */\n private _pointerDirectionDelta: {x: -1 | 0 | 1; y: -1 | 0 | 1};\n\n /** Pointer position at which the last change in the delta occurred. */\n private _pointerPositionAtLastDirectionChange: Point;\n\n /** Position of the pointer at the last pointer event. */\n private _lastKnownPointerPosition: Point;\n\n /**\n * Root DOM node of the drag instance. This is the element that will\n * be moved around as the user is dragging.\n */\n private _rootElement: HTMLElement;\n\n /**\n * Nearest ancestor SVG, relative to which coordinates are calculated if dragging SVGElement\n */\n private _ownerSVGElement: SVGSVGElement | null;\n\n /**\n * Inline style value of `-webkit-tap-highlight-color` at the time the\n * dragging was started. Used to restore the value once we're done dragging.\n */\n private _rootElementTapHighlight: string;\n\n /** Subscription to pointer movement events. */\n private _pointerMoveSubscription = Subscription.EMPTY;\n\n /** Subscription to the event that is dispatched when the user lifts their pointer. */\n private _pointerUpSubscription = Subscription.EMPTY;\n\n /** Subscription to the viewport being scrolled. */\n private _scrollSubscription = Subscription.EMPTY;\n\n /** Subscription to the viewport being resized. */\n private _resizeSubscription = Subscription.EMPTY;\n\n /**\n * Time at which the last touch event occurred. Used to avoid firing the same\n * events multiple times on touch devices where the browser will fire a fake\n * mouse event for each touch event, after a certain time.\n */\n private _lastTouchEventTime: number;\n\n /** Time at which the last dragging sequence was started. */\n private _dragStartTime: number;\n\n /** Cached reference to the boundary element. */\n private _boundaryElement: HTMLElement | null = null;\n\n /** Whether the native dragging interactions have been enabled on the root element. */\n private _nativeInteractionsEnabled = true;\n\n /** Client rect of the root element when the dragging sequence has started. */\n private _initialDomRect?: DOMRect;\n\n /** Cached dimensions of the preview element. Should be read via `_getPreviewRect`. */\n private _previewRect?: DOMRect;\n\n /** Cached dimensions of the boundary element. */\n private _boundaryRect?: DOMRect;\n\n /** Element that will be used as a template to create the draggable item's preview. */\n private _previewTemplate?: DragPreviewTemplate | null;\n\n /** Template for placeholder element rendered to show where a draggable would be dropped. */\n private _placeholderTemplate?: DragHelperTemplate | null;\n\n /** Elements that can be used to drag the draggable item. */\n private _handles: HTMLElement[] = [];\n\n /** Registered handles that are currently disabled. */\n private _disabledHandles = new Set<HTMLElement>();\n\n /** Droppable container that the draggable is a part of. */\n private _dropContainer?: DropListRef;\n\n /** Layout direction of the item. */\n private _direction: Direction = 'ltr';\n\n /** Ref that the current drag item is nested in. */\n private _parentDragRef: DragRef<unknown> | null;\n\n /**\n * Cached shadow root that the element is placed in. `null` means that the element isn't in\n * the shadow DOM and `undefined` means that it hasn't been resolved yet. Should be read via\n * `_getShadowRoot`, not directly.\n */\n private _cachedShadowRoot: ShadowRoot | null | undefined;\n\n /** Axis along which dragging is locked. */\n lockAxis: 'x' | 'y';\n\n /**\n * Amount of milliseconds to wait after the user has put their\n * pointer down before starting to drag the element.\n */\n dragStartDelay: number | {touch: number; mouse: number} = 0;\n\n /** Class to be added to the preview element. */\n previewClass: string | string[] | undefined;\n\n /**\n * If the parent of the dragged element has a `scale` transform, it can throw off the\n * positioning when the user starts dragging. Use this input to notify the CDK of the scale.\n */\n scale: number = 1;\n\n /** Whether starting to drag this element is disabled. */\n get disabled(): boolean {\n return this._disabled || !!(this._dropContainer && this._dropContainer.disabled);\n }\n set disabled(value: boolean) {\n if (value !== this._disabled) {\n this._disabled = value;\n this._toggleNativeDragInteractions();\n this._handles.forEach(handle => toggleNativeDragInteractions(handle, value));\n }\n }\n private _disabled = false;\n\n /** Emits as the drag sequence is being prepared. */\n readonly beforeStarted = new Subject<void>();\n\n /** Emits when the user starts dragging the item. */\n readonly started = new Subject<{source: DragRef; event: MouseEvent | TouchEvent}>();\n\n /** Emits when the user has released a drag item, before any animations have started. */\n readonly released = new Subject<{source: DragRef; event: MouseEvent | TouchEvent}>();\n\n /** Emits when the user stops dragging an item in the container. */\n readonly ended = new Subject<{\n source: DragRef;\n distance: Point;\n dropPoint: Point;\n event: MouseEvent | TouchEvent;\n }>();\n\n /** Emits when the user has moved the item into a new container. */\n readonly entered = new Subject<{container: DropListRef; item: DragRef; currentIndex: number}>();\n\n /** Emits when the user removes the item its container by dragging it into another container. */\n readonly exited = new Subject<{container: DropListRef; item: DragRef}>();\n\n /** Emits when the user drops the item inside a container. */\n readonly dropped = new Subject<{\n previousIndex: number;\n currentIndex: number;\n item: DragRef;\n container: DropListRef;\n previousContainer: DropListRef;\n distance: Point;\n dropPoint: Point;\n isPointerOverContainer: boolean;\n event: MouseEvent | TouchEvent;\n }>();\n\n /**\n * Emits as the user is dragging the item. Use with caution,\n * because this event will fire for every pixel that the user has dragged.\n */\n readonly moved: Observable<{\n source: DragRef;\n pointerPosition: {x: number; y: number};\n event: MouseEvent | TouchEvent;\n distance: Point;\n delta: {x: -1 | 0 | 1; y: -1 | 0 | 1};\n }> = this._moveEvents;\n\n /** Arbitrary data that can be attached to the drag item. */\n data: T;\n\n /**\n * Function that can be used to customize the logic of how the position of the drag item\n * is limited while it's being dragged. Gets called with a point containing the current position\n * of the user's pointer on the page, a reference to the item being dragged and its dimensions.\n * Should return a point describing where the item should be rendered.\n */\n constrainPosition?: (\n userPointerPosition: Point,\n dragRef: DragRef,\n dimensions: DOMRect,\n pickupPositionInElement: Point,\n ) => Point;\n\n constructor(\n element: ElementRef<HTMLElement> | HTMLElement,\n private _config: DragRefConfig,\n private _document: Document,\n private _ngZone: NgZone,\n private _viewportRuler: ViewportRuler,\n private _dragDropRegistry: DragDropRegistry,\n private _renderer: Renderer2,\n ) {\n this.withRootElement(element).withParent(_config.parentDragRef || null);\n this._parentPositions = new ParentPositionTracker(_document);\n _dragDropRegistry.registerDragItem(this);\n }\n\n /**\n * Returns the element that is being used as a placeholder\n * while the current element is being dragged.\n */\n getPlaceholderElement(): HTMLElement {\n return this._placeholder;\n }\n\n /** Returns the root draggable element. */\n getRootElement(): HTMLElement {\n return this._rootElement;\n }\n\n /**\n * Gets the currently-visible element that represents the drag item.\n * While dragging this is the placeholder, otherwise it's the root element.\n */\n getVisibleElement(): HTMLElement {\n return this.isDragging() ? this.getPlaceholderElement() : this.getRootElement();\n }\n\n /** Registers the handles that can be used to drag the element. */\n withHandles(handles: (HTMLElement | ElementRef<HTMLElement>)[]): this {\n this._handles = handles.map(handle => coerceElement(handle));\n this._handles.forEach(handle => toggleNativeDragInteractions(handle, this.disabled));\n this._toggleNativeDragInteractions();\n\n // Delete any lingering disabled handles that may have been destroyed. Note that we re-create\n // the set, rather than iterate over it and filter out the destroyed handles, because while\n // the ES spec allows for sets to be modified while they're being iterated over, some polyfills\n // use an array internally which may throw an error.\n const disabledHandles = new Set<HTMLElement>();\n this._disabledHandles.forEach(handle => {\n if (this._handles.indexOf(handle) > -1) {\n disabledHandles.add(handle);\n }\n });\n this._disabledHandles = disabledHandles;\n return this;\n }\n\n /**\n * Registers the template that should be used for the drag preview.\n * @param template Template that from which to stamp out the preview.\n */\n withPreviewTemplate(template: DragPreviewTemplate | null): this {\n this._previewTemplate = template;\n return this;\n }\n\n /**\n * Registers the template that should be used for the drag placeholder.\n * @param template Template that from which to stamp out the placeholder.\n */\n withPlaceholderTemplate(template: DragHelperTemplate | null): this {\n this._placeholderTemplate = template;\n return this;\n }\n\n /**\n * Sets an alternate drag root element. The root element is the element that will be moved as\n * the user is dragging. Passing an alternate root element is useful when trying to enable\n * dragging on an element that you might not have access to.\n */\n withRootElement(rootElement: ElementRef<HTMLElement> | HTMLElement): this {\n const element = coerceElement(rootElement);\n\n if (element !== this._rootElement) {\n this._removeRootElementListeners();\n this._rootElementCleanups = this._ngZone.runOutsideAngular(() => [\n _bindEventWithOptions(\n this._renderer,\n element,\n 'mousedown',\n this._pointerDown,\n activeEventListenerOptions,\n ),\n _bindEventWithOptions(\n this._renderer,\n element,\n 'touchstart',\n this._pointerDown,\n passiveEventListenerOptions,\n ),\n _bindEventWithOptions(\n this._renderer,\n element,\n 'dragstart',\n this._nativeDragStart,\n activeEventListenerOptions,\n ),\n ]);\n this._initialTransform = undefined;\n this._rootElement = element;\n }\n\n if (typeof SVGElement !== 'undefined' && this._rootElement instanceof SVGElement) {\n this._ownerSVGElement = this._rootElement.ownerSVGElement;\n }\n\n return this;\n }\n\n /**\n * Element to which the draggable's position will be constrained.\n */\n withBoundaryElement(boundaryElement: ElementRef<HTMLElement> | HTMLElement | null): this {\n this._boundaryElement = boundaryElement ? coerceElement(boundaryElement) : null;\n this._resizeSubscription.unsubscribe();\n if (boundaryElement) {\n this._resizeSubscription = this._viewportRuler\n .change(10)\n .subscribe(() => this._containInsideBoundaryOnResize());\n }\n return this;\n }\n\n /** Sets the parent ref that the ref is nested in. */\n withParent(parent: DragRef<unknown> | null): this {\n this._parentDragRef = parent;\n return this;\n }\n\n /** Removes the dragging functionality from the DOM element. */\n dispose() {\n this._removeRootElementListeners();\n\n // Do this check before removing from the registry since it'll\n // stop being considered as dragged once it is removed.\n if (this.isDragging()) {\n // Since we move out the element to the end of the body while it's being\n // dragged, we have to make sure that it's removed if it gets destroyed.\n this._rootElement?.remove();\n }\n\n this._anchor?.remove();\n this._destroyPreview();\n this._destroyPlaceholder();\n this._dragDropRegistry.removeDragItem(this);\n this._removeListeners();\n this.beforeStarted.complete();\n this.started.complete();\n this.released.complete();\n this.ended.complete();\n this.entered.complete();\n this.exited.complete();\n this.dropped.complete();\n this._moveEvents.complete();\n this._handles = [];\n this._disabledHandles.clear();\n this._dropContainer = undefined;\n this._resizeSubscription.unsubscribe();\n this._parentPositions.clear();\n this._boundaryElement =\n this._rootElement =\n this._ownerSVGElement =\n this._placeholderTemplate =\n this._previewTemplate =\n this._anchor =\n this._parentDragRef =\n null!;\n }\n\n /** Checks whether the element is currently being dragged. */\n isDragging(): boolean {\n return this._hasStartedDragging() && this._dragDropRegistry.isDragging(this);\n }\n\n /** Resets a standalone drag item to its initial position. */\n reset(): void {\n this._rootElement.style.transform = this._initialTransform || '';\n this._activeTransform = {x: 0, y: 0};\n this._passiveTransform = {x: 0, y: 0};\n }\n\n /**\n * Sets a handle as disabled. While a handle is disabled, it'll capture and interrupt dragging.\n * @param handle Handle element that should be disabled.\n */\n disableHandle(handle: HTMLElement) {\n if (!this._disabledHandles.has(handle) && this._handles.indexOf(handle) > -1) {\n this._disabledHandles.add(handle);\n toggleNativeDragInteractions(handle, true);\n }\n }\n\n /**\n * Enables a handle, if it has been disabled.\n * @param handle Handle element to be enabled.\n */\n enableHandle(handle: HTMLElement) {\n if (this._disabledHandles.has(handle)) {\n this._disabledHandles.delete(handle);\n toggleNativeDragInteractions(handle, this.disabled);\n }\n }\n\n /** Sets the layout direction of the draggable item. */\n withDirection(direction: Direction): this {\n this._direction = direction;\n return this;\n }\n\n /** Sets the container that the item is part of. */\n _withDropContainer(container: DropListRef) {\n this._dropContainer = container;\n }\n\n /**\n * Gets the current position in pixels the draggable outside of a drop container.\n */\n getFreeDragPosition(): Readonly<Point> {\n const position = this.isDragging() ? this._activeTransform : this._passiveTransform;\n return {x: position.x, y: position.y};\n }\n\n /**\n * Sets the current position in pixels the draggable outside of a drop container.\n * @param value New position to be set.\n */\n setFreeDragPosition(value: Point): this {\n this._activeTransform = {x: 0, y: 0};\n this._passiveTransform.x = value.x;\n this._passiveTransform.y = value.y;\n\n if (!this._dropContainer) {\n this._applyRootElementTransform(value.x, value.y);\n }\n\n return this;\n }\n\n /**\n * Sets the container into which to insert the preview element.\n * @param value Container into which to insert the preview.\n */\n withPreviewContainer(value: PreviewContainer): this {\n this._previewContainer = value;\n return this;\n }\n\n /** Updates the item's sort order based on the last-known pointer position. */\n _sortFromLastPointerPosition() {\n const position = this._lastKnownPointerPosition;\n\n if (position && this._dropContainer) {\n this._updateActiveDropContainer(this._getConstrainedPointerPosition(position), position);\n }\n }\n\n /** Unsubscribes from the global subscriptions. */\n private _removeListeners() {\n this._pointerMoveSubscription.unsubscribe();\n this._pointerUpSubscription.unsubscribe();\n this._scrollSubscription.unsubscribe();\n this._cleanupShadowRootSelectStart?.();\n this._cleanupShadowRootSelectStart = undefined;\n }\n\n /** Destroys the preview element and its ViewRef. */\n private _destroyPreview() {\n this._preview?.destroy();\n this._preview = null;\n }\n\n /** Destroys the placeholder element and its ViewRef. */\n private _destroyPlaceholder() {\n this._placeholder?.remove();\n this._placeholderRef?.destroy();\n this._placeholder = this._placeholderRef = null!;\n }\n\n /** Handler for the `mousedown`/`touchstart` events. */\n private _pointerDown = (event: MouseEvent | TouchEvent) => {\n this.beforeStarted.next();\n\n // Delegate the event based on whether it started from a handle or the element itself.\n if (this._handles.length) {\n const targetHandle = this._getTargetHandle(event);\n\n if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) {\n this._initializeDragSequence(targetHandle, event);\n }\n } else if (!this.disabled) {\n this._initializeDragSequence(this._rootElement, event);\n }\n };\n\n /** Handler that is invoked when the user moves their pointer after they've initiated a drag. */\n private _pointerMove = (event: MouseEvent | TouchEvent) => {\n const pointerPosition = this._getPointerPositionOnPage(event);\n\n if (!this._hasStartedDragging()) {\n const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x);\n const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y);\n const isOverThreshold = distanceX + distanceY >= this._config.dragStartThreshold;\n\n // Only start dragging after the user has moved more than the minimum distance in either\n // direction. Note that this is preferable over doing something like `skip(minimumDistance)`\n // in the `pointerMove` subscription, because we're not guaranteed to have one move event\n // per pixel of movement (e.g. if the user moves their pointer quickly).\n if (isOverThreshold) {\n const isDelayElapsed = Date.now() >= this._dragStartTime + this._getDragStartDelay(event);\n const container = this._dropContainer;\n\n if (!isDelayElapsed) {\n this._endDragSequence(event);\n return;\n }\n\n // Prevent other drag sequences from starting while something in the container is still\n // being dragged. This can happen while we're waiting for the drop animation to finish\n // and can cause errors, because some elements might still be moving around.\n if (!container || (!container.isDragging() && !container.isReceiving())) {\n // Prevent the default action as soon as the dragging sequence is considered as\n // \"started\" since waiting for the next event can allow the device to begin scrolling.\n if (event.cancelable) {\n event.preventDefault();\n }\n this._hasStartedDragging.set(true);\n this._ngZone.run(() => this._startDragSequence(event));\n }\n }\n\n return;\n }\n\n // We prevent the default action down here so that we know that dragging has started. This is\n // important for touch devices where doing this too early can unnecessarily block scrolling,\n // if there's a dragging delay.\n if (event.cancelable) {\n event.preventDefault();\n }\n\n const constrainedPointerPosition = this._getConstrainedPointerPosition(pointerPosition);\n this._hasMoved = true;\n this._lastKnownPointerPosition = pointerPosition;\n this._updatePointerDirectionDelta(constrainedPointerPosition);\n\n if (this._dropContainer) {\n this._updateActiveDropContainer(constrainedPointerPosition, pointerPositi