@angular/cdk
Version:
Angular Material Component Development Kit
1 lines • 354 kB
Source Map (JSON)
{"version":3,"file":"drag-drop.mjs","sources":["../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/dom/clone-node.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/dom/dom-rect.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/dom/parent-position-tracker.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/dom/root-node.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/dom/styling.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/drag-drop-registry.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/dom/transition-duration.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/preview-ref.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/drag-ref.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/drag-utils.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/sorting/single-axis-sort-strategy.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/sorting/mixed-sort-strategy.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/drop-list-ref.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/drag-drop.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/drag-parent.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/directives/assertions.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/directives/drag-handle.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/directives/config.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/directives/drag.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/directives/drop-list-group.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/directives/drop-list.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/directives/drag-preview.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/bin/src/cdk/drag-drop/directives/drag-placeholder.ts","../../../../../darwin_arm64-fastbuild-ST-fdfa778d11ba/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 * Checks if the child element is overflowing from its parent.\n * @param parentRect - The bounding rect of the parent element.\n * @param childRect - The bounding rect of the child element.\n */\nexport function isOverflowingParent(parentRect: DOMRect, childRect: DOMRect): boolean {\n // check for horizontal overflow (left and right)\n const isLeftOverflowing = childRect.left < parentRect.left;\n const isRightOverflowing = childRect.left + childRect.width > parentRect.right;\n\n // check for vertical overflow (top and bottom)\n const isTopOverflowing = childRect.top < parentRect.top;\n const isBottomOverflowing = childRect.top + childRect.height > parentRect.bottom;\n\n return isLeftOverflowing || isRightOverflowing || isTopOverflowing || isBottomOverflowing;\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\nimport {\n ChangeDetectionStrategy,\n Component,\n Injectable,\n ListenerOptions,\n NgZone,\n OnDestroy,\n RendererFactory2,\n ViewEncapsulation,\n WritableSignal,\n inject,\n signal,\n DOCUMENT,\n} from '@angular/core';\n\nimport {_CdkPrivateStyleLoader} from '../private';\nimport {Observable, Observer, Subject, merge} from 'rxjs';\nimport type {DropListRef} from './drop-list-ref';\nimport type {DragRef} from './drag-ref';\nimport type {CdkDrag} from './directives/drag';\n\n/** Event options that can be used to bind a capturing event. */\nconst capturingEventOptions = {\n capture: true,\n};\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 * Component used to load the drag&drop reset styles.\n * @docs-private\n */\n@Component({\n styleUrl: 'resets.css',\n encapsulation: ViewEncapsulation.None,\n template: '',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {'cdk-drag-resets-container': ''},\n})\nexport class _ResetsLoader {}\n\n/**\n * Service that keeps track of all the drag item and drop container\n * instances, and manages global event listeners on the `document`.\n * @docs-private\n */\n@Injectable({providedIn: 'root'})\nexport class DragDropRegistry implements OnDestroy {\n private _ngZone = inject(NgZone);\n private _document = inject(DOCUMENT);\n private _styleLoader = inject(_CdkPrivateStyleLoader);\n private _renderer = inject(RendererFactory2).createRenderer(null, null);\n private _cleanupDocumentTouchmove: (() => void) | undefined;\n private _scroll: Subject<Event> = new Subject<Event>();\n\n /** Registered drop container instances. */\n private _dropInstances = new Set<DropListRef>();\n\n /** Registered drag item instances. */\n private _dragInstances = new Set<DragRef>();\n\n /** Drag item instances that are currently being dragged. */\n private _activeDragInstances: WritableSignal<DragRef[]> = signal([]);\n\n /** Keeps track of the event listeners that we've bound to the `document`. */\n private _globalListeners: (() => void)[] | undefined;\n\n /**\n * Predicate function to check if an item is being dragged. Moved out into a property,\n * because it'll be called a lot and we don't want to create a new function every time.\n */\n private _draggingPredicate = (item: DragRef) => item.isDragging();\n\n /**\n * Map tracking DOM nodes and their corresponding drag directives. Note that this is different\n * from looking through the `_dragInstances` and getting their root node, because the root node\n * isn't necessarily the node that the directive is set on.\n */\n private _domNodesToDirectives: WeakMap<Node, CdkDrag> | null = null;\n\n /**\n * Emits the `touchmove` or `mousemove` events that are dispatched\n * while the user is dragging a drag item instance.\n */\n readonly pointerMove: Subject<TouchEvent | MouseEvent> = new Subject<TouchEvent | MouseEvent>();\n\n /**\n * Emits the `touchend` or `mouseup` events that are dispatched\n * while the user is dragging a drag item instance.\n */\n readonly pointerUp: Subject<TouchEvent | MouseEvent> = new Subject<TouchEvent | MouseEvent>();\n\n constructor(...args: unknown[]);\n constructor() {}\n\n /** Adds a drop container to the registry. */\n registerDropContainer(drop: DropListRef) {\n if (!this._dropInstances.has(drop)) {\n this._dropInstances.add(drop);\n }\n }\n\n /** Adds a drag item instance to the registry. */\n registerDragItem(drag: DragRef) {\n this._dragInstances.add(drag);\n\n // The `touchmove` event gets bound once, ahead of time, because WebKit\n // won't preventDefault on a dynamically-added `touchmove` listener.\n // See https://bugs.webkit.org/show_bug.cgi?id=184250.\n if (this._dragInstances.size === 1) {\n this._ngZone.runOutsideAngular(() => {\n // The event handler has to be explicitly active,\n // because newer browsers make it passive by default.\n this._cleanupDocumentTouchmove?.();\n this._cleanupDocumentTouchmove = this._renderer.listen(\n this._document,\n 'touchmove',\n this._persistentTouchmoveListener,\n activeCapturingEventOptions,\n );\n });\n }\n }\n\n /** Removes a drop container from the registry. */\n removeDropContainer(drop: DropListRef) {\n this._dropInstances.delete(drop);\n }\n\n /** Removes a drag item instance from the registry. */\n removeDragItem(drag: DragRef) {\n this._dragInstances.delete(drag);\n this.stopDragging(drag);\n\n if (this._dragInstances.size === 0) {\n this._cleanupDocumentTouchmove?.();\n }\n }\n\n /**\n * Starts the dragging sequence for a drag instance.\n * @param drag Drag instance which is being dragged.\n * @param event Event that initiated the dragging.\n */\n startDragging(drag: DragRef, event: TouchEvent | MouseEvent) {\n // Do not process the same drag twice to avoid memory leaks and redundant listeners\n if (this._activeDragInstances().indexOf(drag) > -1) {\n return;\n }\n\n this._styleLoader.load(_ResetsLoader);\n this._activeDragInstances.update(instances => [...instances, drag]);\n\n if (this._activeDragInstances().length === 1) {\n // We explicitly bind __active__ listeners here, because newer browsers will default to\n // passive ones for `mousemove` and `touchmove`. The events need to be active, because we\n // use `preventDefault` to prevent the page from scrolling while the user is dragging.\n const isTouchEvent = event.type.startsWith('touch');\n const endEventHandler = (e: Event) => this.pointerUp.next(e as TouchEvent | MouseEvent);\n\n const toBind: [name: string, handler: (event: Event) => void, options: ListenerOptions][] = [\n // Use capturing so that we pick up scroll changes in any scrollable nodes that aren't\n // the document. See https://github.com/angular/components/issues/17144.\n ['scroll', (e: Event) => this._scroll.next(e), capturingEventOptions],\n\n // Preventing the default action on `mousemove` isn't enough to disable text selection\n // on Safari so we need to prevent the selection event as well. Alternatively this can\n // be done by setting `user-select: none` on the `body`, however it has causes a style\n // recalculation which can be expensive on pages with a lot of elements.\n ['selectstart', this._preventDefaultWhileDragging, activeCapturingEventOptions],\n ];\n\n if (isTouchEvent) {\n toBind.push(\n ['touchend', endEventHandler, capturingEventOptions],\n ['touchcancel', endEventHandler, capturingEventOptions],\n );\n } else {\n toBind.push(['mouseup', endEventHandler, capturingEventOptions]);\n }\n\n // We don't have to bind a move event for touch drag sequences, because\n // we already have a persistent global one bound from `registerDragItem`.\n if (!isTouchEvent) {\n toBind.push([\n 'mousemove',\n (e: Event) => this.pointerMove.next(e as MouseEvent),\n activeCapturingEventOptions,\n ]);\n }\n\n this._ngZone.runOutsideAngular(() => {\n this._globalListeners = toBind.map(([name, handler, options]) =>\n this._renderer.listen(this._document, name, handler, options),\n );\n });\n }\n }\n\n /** Stops dragging a drag item instance. */\n stopDragging(drag: DragRef) {\n this._activeDragInstances.update(instances => {\n const index = instances.indexOf(drag);\n if (index > -1) {\n instances.splice(index, 1);\n return [...instances];\n }\n return instances;\n });\n\n if (this._activeDragInstances().length === 0) {\n this._clearGlobalListeners();\n }\n }\n\n /** Gets whether a drag item instance is currently being dragged. */\n isDragging(drag: DragRef) {\n return this._activeDragInstances().indexOf(drag) > -1;\n }\n\n /**\n * Gets a stream that will emit when any element on the page is scrolled while an item is being\n * dragged.\n * @param shadowRoot Optional shadow root that the current dragging sequence started from.\n * Top-level listeners won't pick up events coming from the shadow DOM so this parameter can\n * be used to include an additional top-level listener at the shadow root level.\n */\n scrolled(shadowRoot?: DocumentOrShadowRoot | null): Observable<Event> {\n const streams: Observable<Event>[] = [this._scroll];\n\n if (shadowRoot && shadowRoot !== this._document) {\n // Note that this is basically the same as `fromEvent` from rxjs, but we do it ourselves,\n // because we want to guarantee that the event is bound outside of the `NgZone`. With\n // `fromEvent` it'll only happen if the subscription is outside the `NgZone`.\n streams.push(\n new Observable((observer: Observer<Event>) => {\n return this._ngZone.runOutsideAngular(() => {\n const cleanup = this._renderer.listen(\n shadowRoot as ShadowRoot,\n 'scroll',\n (event: Event) => {\n if (this._activeDragInstances().length) {\n observer.next(event);\n }\n },\n capturingEventOptions,\n );\n\n return () => {\n cleanup();\n };\n });\n }),\n );\n }\n\n return merge(...streams);\n }\n\n /**\n * Tracks the DOM node which has a draggable directive.\n * @param node Node to track.\n * @param dragRef Drag directive set on the node.\n */\n registerDirectiveNode(node: Node, dragRef: CdkDrag): void {\n this._domNodesToDirectives ??= new WeakMap();\n this._domNodesToDirectives.set(node, dragRef);\n }\n\n /**\n * Stops tracking a draggable directive node.\n * @param node Node to stop tracking.\n */\n removeDirectiveNode(node: Node): void {\n this._domNodesToDirectives?.delete(node);\n }\n\n /**\n * Gets the drag directive corresponding to a specific DOM node, if any.\n * @param node Node for which to do the lookup.\n */\n getDragDirectiveForNode(node: Node): CdkDrag | null {\n return this._domNodesToDirectives?.get(node) || null;\n }\n\n ngOnDestroy() {\n this._dragInstances.forEach(instance => this.removeDragItem(instance));\n this._dropInstances.forEach(instance => this.removeDropContainer(instance));\n this._domNodesToDirectives = null;\n this._clearGlobalListeners();\n this.pointerMove.complete();\n this.pointerUp.complete();\n }\n\n /**\n * Event listener that will prevent the default browser action while the user is dragging.\n * @param event Event whose default action should be prevented.\n */\n private _preventDefaultWhileDragging = (event: Event) => {\n if (this._activeDragInstances().length > 0) {\n event.preventDefault();\n }\n };\n\n /** Event listener for `touchmove` that is bound even if no dragging is happening. */\n private _persistentTouchmoveListener = (event: TouchEvent) => {\n if (this._activeDragInstances().length > 0) {\n // Note that we only want to prevent the default action after dragging has actually started.\n // Usually this is the same time at which the item is added to the `_activeDragInstances`,\n // but it could be pushed back if the user has set up a drag delay or threshold.\n if (this._activeDragInstances().some(this._draggingPredicate)) {\n event.preventDefault();\n }\n\n this.pointerMove.next(event);\n }\n };\n\n /** Clears out the global event listeners from the `document`. */\n private _clearGlobalListeners() {\n this._globalListeners?.forEach(cleanup => cleanup());\n this._globalListeners = undefined;\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/** 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} from '../platform';\nimport {ViewportRuler} from '../scrolling';\nimport {\n DOCUMENT,\n ElementRef,\n EmbeddedViewRef,\n Injector,\n NgZone,\n Renderer2,\n RendererFactory2,\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, isOverflowingParent} 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/** Function that can be used to constrain the position of a dragged element. */\nexport type DragConstrainPosition = (\n userPointerPosition: Point,\n dragRef: DragRef,\n dimensions: DOMRect,\n pickupPositionInElement: Point,\n) => Point;\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/** Class applied to the drag placeholder. */\nconst PLACEHOLDER_CLASS = 'cdk-drag-placeholder';\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 * Creates a `DragRef` for an element, turning it into a draggable item.\n * @param injector Injector used to resolve dependencies.\n * @param element Element to which to attach the dragging functionality.\n * @param config Object used to configure the dragging behavior.\n */\nexport function createDragRef<T = unknown>(\n injector: Injector,\n element: ElementRef<HTMLElement> | HTMLElement,\n config: DragRefConfig = {\n dragStartThreshold: 5,\n pointerDirectionChangeThreshold: 5,\n },\n): DragRef<T> {\n const renderer =\n injector.get(Renderer2, null, {optional: true}) ||\n injector.get(RendererFactory2).createRenderer(null, null);\n\n return new DragRef(\n element,\n config,\n injector.get(DOCUMENT),\n injector.get(NgZone),\n injector.get(ViewportRuler),\n injector.get(DragDropRegistry),\n renderer,\n );\n}\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 * Marker 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 _marker: Comment;\n\n /**\n * Element indicating the position from which the item was picked up initially.\n */\n private _anchor: HTMLElement | null = null;\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' | null = null;\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 /**