drag-drop.mjs 172 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648
  1. import * as i0 from '@angular/core';
  2. import { Injectable, Inject, InjectionToken, Directive, Optional, SkipSelf, Input, EventEmitter, Self, ContentChildren, ContentChild, Output, NgModule } from '@angular/core';
  3. import { DOCUMENT } from '@angular/common';
  4. import * as i1 from '@angular/cdk/scrolling';
  5. import { CdkScrollableModule } from '@angular/cdk/scrolling';
  6. import { _getEventTarget, normalizePassiveListenerOptions, _getShadowRoot } from '@angular/cdk/platform';
  7. import { coerceBooleanProperty, coerceElement, coerceNumberProperty, coerceArray } from '@angular/cdk/coercion';
  8. import { isFakeTouchstartFromScreenReader, isFakeMousedownFromScreenReader } from '@angular/cdk/a11y';
  9. import { Subject, Subscription, interval, animationFrameScheduler, Observable, merge } from 'rxjs';
  10. import { takeUntil, map, take, startWith, tap, switchMap } from 'rxjs/operators';
  11. import * as i1$1 from '@angular/cdk/bidi';
  12. /**
  13. * Shallow-extends a stylesheet object with another stylesheet-like object.
  14. * Note that the keys in `source` have to be dash-cased.
  15. * @docs-private
  16. */
  17. function extendStyles(dest, source, importantProperties) {
  18. for (let key in source) {
  19. if (source.hasOwnProperty(key)) {
  20. const value = source[key];
  21. if (value) {
  22. dest.setProperty(key, value, importantProperties?.has(key) ? 'important' : '');
  23. }
  24. else {
  25. dest.removeProperty(key);
  26. }
  27. }
  28. }
  29. return dest;
  30. }
  31. /**
  32. * Toggles whether the native drag interactions should be enabled for an element.
  33. * @param element Element on which to toggle the drag interactions.
  34. * @param enable Whether the drag interactions should be enabled.
  35. * @docs-private
  36. */
  37. function toggleNativeDragInteractions(element, enable) {
  38. const userSelect = enable ? '' : 'none';
  39. extendStyles(element.style, {
  40. 'touch-action': enable ? '' : 'none',
  41. '-webkit-user-drag': enable ? '' : 'none',
  42. '-webkit-tap-highlight-color': enable ? '' : 'transparent',
  43. 'user-select': userSelect,
  44. '-ms-user-select': userSelect,
  45. '-webkit-user-select': userSelect,
  46. '-moz-user-select': userSelect,
  47. });
  48. }
  49. /**
  50. * Toggles whether an element is visible while preserving its dimensions.
  51. * @param element Element whose visibility to toggle
  52. * @param enable Whether the element should be visible.
  53. * @param importantProperties Properties to be set as `!important`.
  54. * @docs-private
  55. */
  56. function toggleVisibility(element, enable, importantProperties) {
  57. extendStyles(element.style, {
  58. position: enable ? '' : 'fixed',
  59. top: enable ? '' : '0',
  60. opacity: enable ? '' : '0',
  61. left: enable ? '' : '-999em',
  62. }, importantProperties);
  63. }
  64. /**
  65. * Combines a transform string with an optional other transform
  66. * that exited before the base transform was applied.
  67. */
  68. function combineTransforms(transform, initialTransform) {
  69. return initialTransform && initialTransform != 'none'
  70. ? transform + ' ' + initialTransform
  71. : transform;
  72. }
  73. /** Parses a CSS time value to milliseconds. */
  74. function parseCssTimeUnitsToMs(value) {
  75. // Some browsers will return it in seconds, whereas others will return milliseconds.
  76. const multiplier = value.toLowerCase().indexOf('ms') > -1 ? 1 : 1000;
  77. return parseFloat(value) * multiplier;
  78. }
  79. /** Gets the transform transition duration, including the delay, of an element in milliseconds. */
  80. function getTransformTransitionDurationInMs(element) {
  81. const computedStyle = getComputedStyle(element);
  82. const transitionedProperties = parseCssPropertyValue(computedStyle, 'transition-property');
  83. const property = transitionedProperties.find(prop => prop === 'transform' || prop === 'all');
  84. // If there's no transition for `all` or `transform`, we shouldn't do anything.
  85. if (!property) {
  86. return 0;
  87. }
  88. // Get the index of the property that we're interested in and match
  89. // it up to the same index in `transition-delay` and `transition-duration`.
  90. const propertyIndex = transitionedProperties.indexOf(property);
  91. const rawDurations = parseCssPropertyValue(computedStyle, 'transition-duration');
  92. const rawDelays = parseCssPropertyValue(computedStyle, 'transition-delay');
  93. return (parseCssTimeUnitsToMs(rawDurations[propertyIndex]) +
  94. parseCssTimeUnitsToMs(rawDelays[propertyIndex]));
  95. }
  96. /** Parses out multiple values from a computed style into an array. */
  97. function parseCssPropertyValue(computedStyle, name) {
  98. const value = computedStyle.getPropertyValue(name);
  99. return value.split(',').map(part => part.trim());
  100. }
  101. /** Gets a mutable version of an element's bounding `ClientRect`. */
  102. function getMutableClientRect(element) {
  103. const clientRect = element.getBoundingClientRect();
  104. // We need to clone the `clientRect` here, because all the values on it are readonly
  105. // and we need to be able to update them. Also we can't use a spread here, because
  106. // the values on a `ClientRect` aren't own properties. See:
  107. // https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect#Notes
  108. return {
  109. top: clientRect.top,
  110. right: clientRect.right,
  111. bottom: clientRect.bottom,
  112. left: clientRect.left,
  113. width: clientRect.width,
  114. height: clientRect.height,
  115. x: clientRect.x,
  116. y: clientRect.y,
  117. };
  118. }
  119. /**
  120. * Checks whether some coordinates are within a `ClientRect`.
  121. * @param clientRect ClientRect that is being checked.
  122. * @param x Coordinates along the X axis.
  123. * @param y Coordinates along the Y axis.
  124. */
  125. function isInsideClientRect(clientRect, x, y) {
  126. const { top, bottom, left, right } = clientRect;
  127. return y >= top && y <= bottom && x >= left && x <= right;
  128. }
  129. /**
  130. * Updates the top/left positions of a `ClientRect`, as well as their bottom/right counterparts.
  131. * @param clientRect `ClientRect` that should be updated.
  132. * @param top Amount to add to the `top` position.
  133. * @param left Amount to add to the `left` position.
  134. */
  135. function adjustClientRect(clientRect, top, left) {
  136. clientRect.top += top;
  137. clientRect.bottom = clientRect.top + clientRect.height;
  138. clientRect.left += left;
  139. clientRect.right = clientRect.left + clientRect.width;
  140. }
  141. /**
  142. * Checks whether the pointer coordinates are close to a ClientRect.
  143. * @param rect ClientRect to check against.
  144. * @param threshold Threshold around the ClientRect.
  145. * @param pointerX Coordinates along the X axis.
  146. * @param pointerY Coordinates along the Y axis.
  147. */
  148. function isPointerNearClientRect(rect, threshold, pointerX, pointerY) {
  149. const { top, right, bottom, left, width, height } = rect;
  150. const xThreshold = width * threshold;
  151. const yThreshold = height * threshold;
  152. return (pointerY > top - yThreshold &&
  153. pointerY < bottom + yThreshold &&
  154. pointerX > left - xThreshold &&
  155. pointerX < right + xThreshold);
  156. }
  157. /** Keeps track of the scroll position and dimensions of the parents of an element. */
  158. class ParentPositionTracker {
  159. constructor(_document) {
  160. this._document = _document;
  161. /** Cached positions of the scrollable parent elements. */
  162. this.positions = new Map();
  163. }
  164. /** Clears the cached positions. */
  165. clear() {
  166. this.positions.clear();
  167. }
  168. /** Caches the positions. Should be called at the beginning of a drag sequence. */
  169. cache(elements) {
  170. this.clear();
  171. this.positions.set(this._document, {
  172. scrollPosition: this.getViewportScrollPosition(),
  173. });
  174. elements.forEach(element => {
  175. this.positions.set(element, {
  176. scrollPosition: { top: element.scrollTop, left: element.scrollLeft },
  177. clientRect: getMutableClientRect(element),
  178. });
  179. });
  180. }
  181. /** Handles scrolling while a drag is taking place. */
  182. handleScroll(event) {
  183. const target = _getEventTarget(event);
  184. const cachedPosition = this.positions.get(target);
  185. if (!cachedPosition) {
  186. return null;
  187. }
  188. const scrollPosition = cachedPosition.scrollPosition;
  189. let newTop;
  190. let newLeft;
  191. if (target === this._document) {
  192. const viewportScrollPosition = this.getViewportScrollPosition();
  193. newTop = viewportScrollPosition.top;
  194. newLeft = viewportScrollPosition.left;
  195. }
  196. else {
  197. newTop = target.scrollTop;
  198. newLeft = target.scrollLeft;
  199. }
  200. const topDifference = scrollPosition.top - newTop;
  201. const leftDifference = scrollPosition.left - newLeft;
  202. // Go through and update the cached positions of the scroll
  203. // parents that are inside the element that was scrolled.
  204. this.positions.forEach((position, node) => {
  205. if (position.clientRect && target !== node && target.contains(node)) {
  206. adjustClientRect(position.clientRect, topDifference, leftDifference);
  207. }
  208. });
  209. scrollPosition.top = newTop;
  210. scrollPosition.left = newLeft;
  211. return { top: topDifference, left: leftDifference };
  212. }
  213. /**
  214. * Gets the scroll position of the viewport. Note that we use the scrollX and scrollY directly,
  215. * instead of going through the `ViewportRuler`, because the first value the ruler looks at is
  216. * the top/left offset of the `document.documentElement` which works for most cases, but breaks
  217. * if the element is offset by something like the `BlockScrollStrategy`.
  218. */
  219. getViewportScrollPosition() {
  220. return { top: window.scrollY, left: window.scrollX };
  221. }
  222. }
  223. /** Creates a deep clone of an element. */
  224. function deepCloneNode(node) {
  225. const clone = node.cloneNode(true);
  226. const descendantsWithId = clone.querySelectorAll('[id]');
  227. const nodeName = node.nodeName.toLowerCase();
  228. // Remove the `id` to avoid having multiple elements with the same id on the page.
  229. clone.removeAttribute('id');
  230. for (let i = 0; i < descendantsWithId.length; i++) {
  231. descendantsWithId[i].removeAttribute('id');
  232. }
  233. if (nodeName === 'canvas') {
  234. transferCanvasData(node, clone);
  235. }
  236. else if (nodeName === 'input' || nodeName === 'select' || nodeName === 'textarea') {
  237. transferInputData(node, clone);
  238. }
  239. transferData('canvas', node, clone, transferCanvasData);
  240. transferData('input, textarea, select', node, clone, transferInputData);
  241. return clone;
  242. }
  243. /** Matches elements between an element and its clone and allows for their data to be cloned. */
  244. function transferData(selector, node, clone, callback) {
  245. const descendantElements = node.querySelectorAll(selector);
  246. if (descendantElements.length) {
  247. const cloneElements = clone.querySelectorAll(selector);
  248. for (let i = 0; i < descendantElements.length; i++) {
  249. callback(descendantElements[i], cloneElements[i]);
  250. }
  251. }
  252. }
  253. // Counter for unique cloned radio button names.
  254. let cloneUniqueId = 0;
  255. /** Transfers the data of one input element to another. */
  256. function transferInputData(source, clone) {
  257. // Browsers throw an error when assigning the value of a file input programmatically.
  258. if (clone.type !== 'file') {
  259. clone.value = source.value;
  260. }
  261. // Radio button `name` attributes must be unique for radio button groups
  262. // otherwise original radio buttons can lose their checked state
  263. // once the clone is inserted in the DOM.
  264. if (clone.type === 'radio' && clone.name) {
  265. clone.name = `mat-clone-${clone.name}-${cloneUniqueId++}`;
  266. }
  267. }
  268. /** Transfers the data of one canvas element to another. */
  269. function transferCanvasData(source, clone) {
  270. const context = clone.getContext('2d');
  271. if (context) {
  272. // In some cases `drawImage` can throw (e.g. if the canvas size is 0x0).
  273. // We can't do much about it so just ignore the error.
  274. try {
  275. context.drawImage(source, 0, 0);
  276. }
  277. catch { }
  278. }
  279. }
  280. /** Options that can be used to bind a passive event listener. */
  281. const passiveEventListenerOptions = normalizePassiveListenerOptions({ passive: true });
  282. /** Options that can be used to bind an active event listener. */
  283. const activeEventListenerOptions = normalizePassiveListenerOptions({ passive: false });
  284. /**
  285. * Time in milliseconds for which to ignore mouse events, after
  286. * receiving a touch event. Used to avoid doing double work for
  287. * touch devices where the browser fires fake mouse events, in
  288. * addition to touch events.
  289. */
  290. const MOUSE_EVENT_IGNORE_TIME = 800;
  291. /** Inline styles to be set as `!important` while dragging. */
  292. const dragImportantProperties = new Set([
  293. // Needs to be important, because some `mat-table` sets `position: sticky !important`. See #22781.
  294. 'position',
  295. ]);
  296. /**
  297. * Reference to a draggable item. Used to manipulate or dispose of the item.
  298. */
  299. class DragRef {
  300. /** Whether starting to drag this element is disabled. */
  301. get disabled() {
  302. return this._disabled || !!(this._dropContainer && this._dropContainer.disabled);
  303. }
  304. set disabled(value) {
  305. const newValue = coerceBooleanProperty(value);
  306. if (newValue !== this._disabled) {
  307. this._disabled = newValue;
  308. this._toggleNativeDragInteractions();
  309. this._handles.forEach(handle => toggleNativeDragInteractions(handle, newValue));
  310. }
  311. }
  312. constructor(element, _config, _document, _ngZone, _viewportRuler, _dragDropRegistry) {
  313. this._config = _config;
  314. this._document = _document;
  315. this._ngZone = _ngZone;
  316. this._viewportRuler = _viewportRuler;
  317. this._dragDropRegistry = _dragDropRegistry;
  318. /**
  319. * CSS `transform` applied to the element when it isn't being dragged. We need a
  320. * passive transform in order for the dragged element to retain its new position
  321. * after the user has stopped dragging and because we need to know the relative
  322. * position in case they start dragging again. This corresponds to `element.style.transform`.
  323. */
  324. this._passiveTransform = { x: 0, y: 0 };
  325. /** CSS `transform` that is applied to the element while it's being dragged. */
  326. this._activeTransform = { x: 0, y: 0 };
  327. /**
  328. * Whether the dragging sequence has been started. Doesn't
  329. * necessarily mean that the element has been moved.
  330. */
  331. this._hasStartedDragging = false;
  332. /** Emits when the item is being moved. */
  333. this._moveEvents = new Subject();
  334. /** Subscription to pointer movement events. */
  335. this._pointerMoveSubscription = Subscription.EMPTY;
  336. /** Subscription to the event that is dispatched when the user lifts their pointer. */
  337. this._pointerUpSubscription = Subscription.EMPTY;
  338. /** Subscription to the viewport being scrolled. */
  339. this._scrollSubscription = Subscription.EMPTY;
  340. /** Subscription to the viewport being resized. */
  341. this._resizeSubscription = Subscription.EMPTY;
  342. /** Cached reference to the boundary element. */
  343. this._boundaryElement = null;
  344. /** Whether the native dragging interactions have been enabled on the root element. */
  345. this._nativeInteractionsEnabled = true;
  346. /** Elements that can be used to drag the draggable item. */
  347. this._handles = [];
  348. /** Registered handles that are currently disabled. */
  349. this._disabledHandles = new Set();
  350. /** Layout direction of the item. */
  351. this._direction = 'ltr';
  352. /**
  353. * Amount of milliseconds to wait after the user has put their
  354. * pointer down before starting to drag the element.
  355. */
  356. this.dragStartDelay = 0;
  357. this._disabled = false;
  358. /** Emits as the drag sequence is being prepared. */
  359. this.beforeStarted = new Subject();
  360. /** Emits when the user starts dragging the item. */
  361. this.started = new Subject();
  362. /** Emits when the user has released a drag item, before any animations have started. */
  363. this.released = new Subject();
  364. /** Emits when the user stops dragging an item in the container. */
  365. this.ended = new Subject();
  366. /** Emits when the user has moved the item into a new container. */
  367. this.entered = new Subject();
  368. /** Emits when the user removes the item its container by dragging it into another container. */
  369. this.exited = new Subject();
  370. /** Emits when the user drops the item inside a container. */
  371. this.dropped = new Subject();
  372. /**
  373. * Emits as the user is dragging the item. Use with caution,
  374. * because this event will fire for every pixel that the user has dragged.
  375. */
  376. this.moved = this._moveEvents;
  377. /** Handler for the `mousedown`/`touchstart` events. */
  378. this._pointerDown = (event) => {
  379. this.beforeStarted.next();
  380. // Delegate the event based on whether it started from a handle or the element itself.
  381. if (this._handles.length) {
  382. const targetHandle = this._getTargetHandle(event);
  383. if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) {
  384. this._initializeDragSequence(targetHandle, event);
  385. }
  386. }
  387. else if (!this.disabled) {
  388. this._initializeDragSequence(this._rootElement, event);
  389. }
  390. };
  391. /** Handler that is invoked when the user moves their pointer after they've initiated a drag. */
  392. this._pointerMove = (event) => {
  393. const pointerPosition = this._getPointerPositionOnPage(event);
  394. if (!this._hasStartedDragging) {
  395. const distanceX = Math.abs(pointerPosition.x - this._pickupPositionOnPage.x);
  396. const distanceY = Math.abs(pointerPosition.y - this._pickupPositionOnPage.y);
  397. const isOverThreshold = distanceX + distanceY >= this._config.dragStartThreshold;
  398. // Only start dragging after the user has moved more than the minimum distance in either
  399. // direction. Note that this is preferable over doing something like `skip(minimumDistance)`
  400. // in the `pointerMove` subscription, because we're not guaranteed to have one move event
  401. // per pixel of movement (e.g. if the user moves their pointer quickly).
  402. if (isOverThreshold) {
  403. const isDelayElapsed = Date.now() >= this._dragStartTime + this._getDragStartDelay(event);
  404. const container = this._dropContainer;
  405. if (!isDelayElapsed) {
  406. this._endDragSequence(event);
  407. return;
  408. }
  409. // Prevent other drag sequences from starting while something in the container is still
  410. // being dragged. This can happen while we're waiting for the drop animation to finish
  411. // and can cause errors, because some elements might still be moving around.
  412. if (!container || (!container.isDragging() && !container.isReceiving())) {
  413. // Prevent the default action as soon as the dragging sequence is considered as
  414. // "started" since waiting for the next event can allow the device to begin scrolling.
  415. event.preventDefault();
  416. this._hasStartedDragging = true;
  417. this._ngZone.run(() => this._startDragSequence(event));
  418. }
  419. }
  420. return;
  421. }
  422. // We prevent the default action down here so that we know that dragging has started. This is
  423. // important for touch devices where doing this too early can unnecessarily block scrolling,
  424. // if there's a dragging delay.
  425. event.preventDefault();
  426. const constrainedPointerPosition = this._getConstrainedPointerPosition(pointerPosition);
  427. this._hasMoved = true;
  428. this._lastKnownPointerPosition = pointerPosition;
  429. this._updatePointerDirectionDelta(constrainedPointerPosition);
  430. if (this._dropContainer) {
  431. this._updateActiveDropContainer(constrainedPointerPosition, pointerPosition);
  432. }
  433. else {
  434. // If there's a position constraint function, we want the element's top/left to be at the
  435. // specific position on the page. Use the initial position as a reference if that's the case.
  436. const offset = this.constrainPosition ? this._initialClientRect : this._pickupPositionOnPage;
  437. const activeTransform = this._activeTransform;
  438. activeTransform.x = constrainedPointerPosition.x - offset.x + this._passiveTransform.x;
  439. activeTransform.y = constrainedPointerPosition.y - offset.y + this._passiveTransform.y;
  440. this._applyRootElementTransform(activeTransform.x, activeTransform.y);
  441. }
  442. // Since this event gets fired for every pixel while dragging, we only
  443. // want to fire it if the consumer opted into it. Also we have to
  444. // re-enter the zone because we run all of the events on the outside.
  445. if (this._moveEvents.observers.length) {
  446. this._ngZone.run(() => {
  447. this._moveEvents.next({
  448. source: this,
  449. pointerPosition: constrainedPointerPosition,
  450. event,
  451. distance: this._getDragDistance(constrainedPointerPosition),
  452. delta: this._pointerDirectionDelta,
  453. });
  454. });
  455. }
  456. };
  457. /** Handler that is invoked when the user lifts their pointer up, after initiating a drag. */
  458. this._pointerUp = (event) => {
  459. this._endDragSequence(event);
  460. };
  461. /** Handles a native `dragstart` event. */
  462. this._nativeDragStart = (event) => {
  463. if (this._handles.length) {
  464. const targetHandle = this._getTargetHandle(event);
  465. if (targetHandle && !this._disabledHandles.has(targetHandle) && !this.disabled) {
  466. event.preventDefault();
  467. }
  468. }
  469. else if (!this.disabled) {
  470. // Usually this isn't necessary since the we prevent the default action in `pointerDown`,
  471. // but some cases like dragging of links can slip through (see #24403).
  472. event.preventDefault();
  473. }
  474. };
  475. this.withRootElement(element).withParent(_config.parentDragRef || null);
  476. this._parentPositions = new ParentPositionTracker(_document);
  477. _dragDropRegistry.registerDragItem(this);
  478. }
  479. /**
  480. * Returns the element that is being used as a placeholder
  481. * while the current element is being dragged.
  482. */
  483. getPlaceholderElement() {
  484. return this._placeholder;
  485. }
  486. /** Returns the root draggable element. */
  487. getRootElement() {
  488. return this._rootElement;
  489. }
  490. /**
  491. * Gets the currently-visible element that represents the drag item.
  492. * While dragging this is the placeholder, otherwise it's the root element.
  493. */
  494. getVisibleElement() {
  495. return this.isDragging() ? this.getPlaceholderElement() : this.getRootElement();
  496. }
  497. /** Registers the handles that can be used to drag the element. */
  498. withHandles(handles) {
  499. this._handles = handles.map(handle => coerceElement(handle));
  500. this._handles.forEach(handle => toggleNativeDragInteractions(handle, this.disabled));
  501. this._toggleNativeDragInteractions();
  502. // Delete any lingering disabled handles that may have been destroyed. Note that we re-create
  503. // the set, rather than iterate over it and filter out the destroyed handles, because while
  504. // the ES spec allows for sets to be modified while they're being iterated over, some polyfills
  505. // use an array internally which may throw an error.
  506. const disabledHandles = new Set();
  507. this._disabledHandles.forEach(handle => {
  508. if (this._handles.indexOf(handle) > -1) {
  509. disabledHandles.add(handle);
  510. }
  511. });
  512. this._disabledHandles = disabledHandles;
  513. return this;
  514. }
  515. /**
  516. * Registers the template that should be used for the drag preview.
  517. * @param template Template that from which to stamp out the preview.
  518. */
  519. withPreviewTemplate(template) {
  520. this._previewTemplate = template;
  521. return this;
  522. }
  523. /**
  524. * Registers the template that should be used for the drag placeholder.
  525. * @param template Template that from which to stamp out the placeholder.
  526. */
  527. withPlaceholderTemplate(template) {
  528. this._placeholderTemplate = template;
  529. return this;
  530. }
  531. /**
  532. * Sets an alternate drag root element. The root element is the element that will be moved as
  533. * the user is dragging. Passing an alternate root element is useful when trying to enable
  534. * dragging on an element that you might not have access to.
  535. */
  536. withRootElement(rootElement) {
  537. const element = coerceElement(rootElement);
  538. if (element !== this._rootElement) {
  539. if (this._rootElement) {
  540. this._removeRootElementListeners(this._rootElement);
  541. }
  542. this._ngZone.runOutsideAngular(() => {
  543. element.addEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
  544. element.addEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
  545. element.addEventListener('dragstart', this._nativeDragStart, activeEventListenerOptions);
  546. });
  547. this._initialTransform = undefined;
  548. this._rootElement = element;
  549. }
  550. if (typeof SVGElement !== 'undefined' && this._rootElement instanceof SVGElement) {
  551. this._ownerSVGElement = this._rootElement.ownerSVGElement;
  552. }
  553. return this;
  554. }
  555. /**
  556. * Element to which the draggable's position will be constrained.
  557. */
  558. withBoundaryElement(boundaryElement) {
  559. this._boundaryElement = boundaryElement ? coerceElement(boundaryElement) : null;
  560. this._resizeSubscription.unsubscribe();
  561. if (boundaryElement) {
  562. this._resizeSubscription = this._viewportRuler
  563. .change(10)
  564. .subscribe(() => this._containInsideBoundaryOnResize());
  565. }
  566. return this;
  567. }
  568. /** Sets the parent ref that the ref is nested in. */
  569. withParent(parent) {
  570. this._parentDragRef = parent;
  571. return this;
  572. }
  573. /** Removes the dragging functionality from the DOM element. */
  574. dispose() {
  575. this._removeRootElementListeners(this._rootElement);
  576. // Do this check before removing from the registry since it'll
  577. // stop being considered as dragged once it is removed.
  578. if (this.isDragging()) {
  579. // Since we move out the element to the end of the body while it's being
  580. // dragged, we have to make sure that it's removed if it gets destroyed.
  581. this._rootElement?.remove();
  582. }
  583. this._anchor?.remove();
  584. this._destroyPreview();
  585. this._destroyPlaceholder();
  586. this._dragDropRegistry.removeDragItem(this);
  587. this._removeSubscriptions();
  588. this.beforeStarted.complete();
  589. this.started.complete();
  590. this.released.complete();
  591. this.ended.complete();
  592. this.entered.complete();
  593. this.exited.complete();
  594. this.dropped.complete();
  595. this._moveEvents.complete();
  596. this._handles = [];
  597. this._disabledHandles.clear();
  598. this._dropContainer = undefined;
  599. this._resizeSubscription.unsubscribe();
  600. this._parentPositions.clear();
  601. this._boundaryElement =
  602. this._rootElement =
  603. this._ownerSVGElement =
  604. this._placeholderTemplate =
  605. this._previewTemplate =
  606. this._anchor =
  607. this._parentDragRef =
  608. null;
  609. }
  610. /** Checks whether the element is currently being dragged. */
  611. isDragging() {
  612. return this._hasStartedDragging && this._dragDropRegistry.isDragging(this);
  613. }
  614. /** Resets a standalone drag item to its initial position. */
  615. reset() {
  616. this._rootElement.style.transform = this._initialTransform || '';
  617. this._activeTransform = { x: 0, y: 0 };
  618. this._passiveTransform = { x: 0, y: 0 };
  619. }
  620. /**
  621. * Sets a handle as disabled. While a handle is disabled, it'll capture and interrupt dragging.
  622. * @param handle Handle element that should be disabled.
  623. */
  624. disableHandle(handle) {
  625. if (!this._disabledHandles.has(handle) && this._handles.indexOf(handle) > -1) {
  626. this._disabledHandles.add(handle);
  627. toggleNativeDragInteractions(handle, true);
  628. }
  629. }
  630. /**
  631. * Enables a handle, if it has been disabled.
  632. * @param handle Handle element to be enabled.
  633. */
  634. enableHandle(handle) {
  635. if (this._disabledHandles.has(handle)) {
  636. this._disabledHandles.delete(handle);
  637. toggleNativeDragInteractions(handle, this.disabled);
  638. }
  639. }
  640. /** Sets the layout direction of the draggable item. */
  641. withDirection(direction) {
  642. this._direction = direction;
  643. return this;
  644. }
  645. /** Sets the container that the item is part of. */
  646. _withDropContainer(container) {
  647. this._dropContainer = container;
  648. }
  649. /**
  650. * Gets the current position in pixels the draggable outside of a drop container.
  651. */
  652. getFreeDragPosition() {
  653. const position = this.isDragging() ? this._activeTransform : this._passiveTransform;
  654. return { x: position.x, y: position.y };
  655. }
  656. /**
  657. * Sets the current position in pixels the draggable outside of a drop container.
  658. * @param value New position to be set.
  659. */
  660. setFreeDragPosition(value) {
  661. this._activeTransform = { x: 0, y: 0 };
  662. this._passiveTransform.x = value.x;
  663. this._passiveTransform.y = value.y;
  664. if (!this._dropContainer) {
  665. this._applyRootElementTransform(value.x, value.y);
  666. }
  667. return this;
  668. }
  669. /**
  670. * Sets the container into which to insert the preview element.
  671. * @param value Container into which to insert the preview.
  672. */
  673. withPreviewContainer(value) {
  674. this._previewContainer = value;
  675. return this;
  676. }
  677. /** Updates the item's sort order based on the last-known pointer position. */
  678. _sortFromLastPointerPosition() {
  679. const position = this._lastKnownPointerPosition;
  680. if (position && this._dropContainer) {
  681. this._updateActiveDropContainer(this._getConstrainedPointerPosition(position), position);
  682. }
  683. }
  684. /** Unsubscribes from the global subscriptions. */
  685. _removeSubscriptions() {
  686. this._pointerMoveSubscription.unsubscribe();
  687. this._pointerUpSubscription.unsubscribe();
  688. this._scrollSubscription.unsubscribe();
  689. }
  690. /** Destroys the preview element and its ViewRef. */
  691. _destroyPreview() {
  692. this._preview?.remove();
  693. this._previewRef?.destroy();
  694. this._preview = this._previewRef = null;
  695. }
  696. /** Destroys the placeholder element and its ViewRef. */
  697. _destroyPlaceholder() {
  698. this._placeholder?.remove();
  699. this._placeholderRef?.destroy();
  700. this._placeholder = this._placeholderRef = null;
  701. }
  702. /**
  703. * Clears subscriptions and stops the dragging sequence.
  704. * @param event Browser event object that ended the sequence.
  705. */
  706. _endDragSequence(event) {
  707. // Note that here we use `isDragging` from the service, rather than from `this`.
  708. // The difference is that the one from the service reflects whether a dragging sequence
  709. // has been initiated, whereas the one on `this` includes whether the user has passed
  710. // the minimum dragging threshold.
  711. if (!this._dragDropRegistry.isDragging(this)) {
  712. return;
  713. }
  714. this._removeSubscriptions();
  715. this._dragDropRegistry.stopDragging(this);
  716. this._toggleNativeDragInteractions();
  717. if (this._handles) {
  718. this._rootElement.style.webkitTapHighlightColor =
  719. this._rootElementTapHighlight;
  720. }
  721. if (!this._hasStartedDragging) {
  722. return;
  723. }
  724. this.released.next({ source: this, event });
  725. if (this._dropContainer) {
  726. // Stop scrolling immediately, instead of waiting for the animation to finish.
  727. this._dropContainer._stopScrolling();
  728. this._animatePreviewToPlaceholder().then(() => {
  729. this._cleanupDragArtifacts(event);
  730. this._cleanupCachedDimensions();
  731. this._dragDropRegistry.stopDragging(this);
  732. });
  733. }
  734. else {
  735. // Convert the active transform into a passive one. This means that next time
  736. // the user starts dragging the item, its position will be calculated relatively
  737. // to the new passive transform.
  738. this._passiveTransform.x = this._activeTransform.x;
  739. const pointerPosition = this._getPointerPositionOnPage(event);
  740. this._passiveTransform.y = this._activeTransform.y;
  741. this._ngZone.run(() => {
  742. this.ended.next({
  743. source: this,
  744. distance: this._getDragDistance(pointerPosition),
  745. dropPoint: pointerPosition,
  746. event,
  747. });
  748. });
  749. this._cleanupCachedDimensions();
  750. this._dragDropRegistry.stopDragging(this);
  751. }
  752. }
  753. /** Starts the dragging sequence. */
  754. _startDragSequence(event) {
  755. if (isTouchEvent(event)) {
  756. this._lastTouchEventTime = Date.now();
  757. }
  758. this._toggleNativeDragInteractions();
  759. const dropContainer = this._dropContainer;
  760. if (dropContainer) {
  761. const element = this._rootElement;
  762. const parent = element.parentNode;
  763. const placeholder = (this._placeholder = this._createPlaceholderElement());
  764. const anchor = (this._anchor = this._anchor || this._document.createComment(''));
  765. // Needs to happen before the root element is moved.
  766. const shadowRoot = this._getShadowRoot();
  767. // Insert an anchor node so that we can restore the element's position in the DOM.
  768. parent.insertBefore(anchor, element);
  769. // There's no risk of transforms stacking when inside a drop container so
  770. // we can keep the initial transform up to date any time dragging starts.
  771. this._initialTransform = element.style.transform || '';
  772. // Create the preview after the initial transform has
  773. // been cached, because it can be affected by the transform.
  774. this._preview = this._createPreviewElement();
  775. // We move the element out at the end of the body and we make it hidden, because keeping it in
  776. // place will throw off the consumer's `:last-child` selectors. We can't remove the element
  777. // from the DOM completely, because iOS will stop firing all subsequent events in the chain.
  778. toggleVisibility(element, false, dragImportantProperties);
  779. this._document.body.appendChild(parent.replaceChild(placeholder, element));
  780. this._getPreviewInsertionPoint(parent, shadowRoot).appendChild(this._preview);
  781. this.started.next({ source: this, event }); // Emit before notifying the container.
  782. dropContainer.start();
  783. this._initialContainer = dropContainer;
  784. this._initialIndex = dropContainer.getItemIndex(this);
  785. }
  786. else {
  787. this.started.next({ source: this, event });
  788. this._initialContainer = this._initialIndex = undefined;
  789. }
  790. // Important to run after we've called `start` on the parent container
  791. // so that it has had time to resolve its scrollable parents.
  792. this._parentPositions.cache(dropContainer ? dropContainer.getScrollableParents() : []);
  793. }
  794. /**
  795. * Sets up the different variables and subscriptions
  796. * that will be necessary for the dragging sequence.
  797. * @param referenceElement Element that started the drag sequence.
  798. * @param event Browser event object that started the sequence.
  799. */
  800. _initializeDragSequence(referenceElement, event) {
  801. // Stop propagation if the item is inside another
  802. // draggable so we don't start multiple drag sequences.
  803. if (this._parentDragRef) {
  804. event.stopPropagation();
  805. }
  806. const isDragging = this.isDragging();
  807. const isTouchSequence = isTouchEvent(event);
  808. const isAuxiliaryMouseButton = !isTouchSequence && event.button !== 0;
  809. const rootElement = this._rootElement;
  810. const target = _getEventTarget(event);
  811. const isSyntheticEvent = !isTouchSequence &&
  812. this._lastTouchEventTime &&
  813. this._lastTouchEventTime + MOUSE_EVENT_IGNORE_TIME > Date.now();
  814. const isFakeEvent = isTouchSequence
  815. ? isFakeTouchstartFromScreenReader(event)
  816. : isFakeMousedownFromScreenReader(event);
  817. // If the event started from an element with the native HTML drag&drop, it'll interfere
  818. // with our own dragging (e.g. `img` tags do it by default). Prevent the default action
  819. // to stop it from happening. Note that preventing on `dragstart` also seems to work, but
  820. // it's flaky and it fails if the user drags it away quickly. Also note that we only want
  821. // to do this for `mousedown` since doing the same for `touchstart` will stop any `click`
  822. // events from firing on touch devices.
  823. if (target && target.draggable && event.type === 'mousedown') {
  824. event.preventDefault();
  825. }
  826. // Abort if the user is already dragging or is using a mouse button other than the primary one.
  827. if (isDragging || isAuxiliaryMouseButton || isSyntheticEvent || isFakeEvent) {
  828. return;
  829. }
  830. // If we've got handles, we need to disable the tap highlight on the entire root element,
  831. // otherwise iOS will still add it, even though all the drag interactions on the handle
  832. // are disabled.
  833. if (this._handles.length) {
  834. const rootStyles = rootElement.style;
  835. this._rootElementTapHighlight = rootStyles.webkitTapHighlightColor || '';
  836. rootStyles.webkitTapHighlightColor = 'transparent';
  837. }
  838. this._hasStartedDragging = this._hasMoved = false;
  839. // Avoid multiple subscriptions and memory leaks when multi touch
  840. // (isDragging check above isn't enough because of possible temporal and/or dimensional delays)
  841. this._removeSubscriptions();
  842. this._initialClientRect = this._rootElement.getBoundingClientRect();
  843. this._pointerMoveSubscription = this._dragDropRegistry.pointerMove.subscribe(this._pointerMove);
  844. this._pointerUpSubscription = this._dragDropRegistry.pointerUp.subscribe(this._pointerUp);
  845. this._scrollSubscription = this._dragDropRegistry
  846. .scrolled(this._getShadowRoot())
  847. .subscribe(scrollEvent => this._updateOnScroll(scrollEvent));
  848. if (this._boundaryElement) {
  849. this._boundaryRect = getMutableClientRect(this._boundaryElement);
  850. }
  851. // If we have a custom preview we can't know ahead of time how large it'll be so we position
  852. // it next to the cursor. The exception is when the consumer has opted into making the preview
  853. // the same size as the root element, in which case we do know the size.
  854. const previewTemplate = this._previewTemplate;
  855. this._pickupPositionInElement =
  856. previewTemplate && previewTemplate.template && !previewTemplate.matchSize
  857. ? { x: 0, y: 0 }
  858. : this._getPointerPositionInElement(this._initialClientRect, referenceElement, event);
  859. const pointerPosition = (this._pickupPositionOnPage =
  860. this._lastKnownPointerPosition =
  861. this._getPointerPositionOnPage(event));
  862. this._pointerDirectionDelta = { x: 0, y: 0 };
  863. this._pointerPositionAtLastDirectionChange = { x: pointerPosition.x, y: pointerPosition.y };
  864. this._dragStartTime = Date.now();
  865. this._dragDropRegistry.startDragging(this, event);
  866. }
  867. /** Cleans up the DOM artifacts that were added to facilitate the element being dragged. */
  868. _cleanupDragArtifacts(event) {
  869. // Restore the element's visibility and insert it at its old position in the DOM.
  870. // It's important that we maintain the position, because moving the element around in the DOM
  871. // can throw off `NgFor` which does smart diffing and re-creates elements only when necessary,
  872. // while moving the existing elements in all other cases.
  873. toggleVisibility(this._rootElement, true, dragImportantProperties);
  874. this._anchor.parentNode.replaceChild(this._rootElement, this._anchor);
  875. this._destroyPreview();
  876. this._destroyPlaceholder();
  877. this._initialClientRect =
  878. this._boundaryRect =
  879. this._previewRect =
  880. this._initialTransform =
  881. undefined;
  882. // Re-enter the NgZone since we bound `document` events on the outside.
  883. this._ngZone.run(() => {
  884. const container = this._dropContainer;
  885. const currentIndex = container.getItemIndex(this);
  886. const pointerPosition = this._getPointerPositionOnPage(event);
  887. const distance = this._getDragDistance(pointerPosition);
  888. const isPointerOverContainer = container._isOverContainer(pointerPosition.x, pointerPosition.y);
  889. this.ended.next({ source: this, distance, dropPoint: pointerPosition, event });
  890. this.dropped.next({
  891. item: this,
  892. currentIndex,
  893. previousIndex: this._initialIndex,
  894. container: container,
  895. previousContainer: this._initialContainer,
  896. isPointerOverContainer,
  897. distance,
  898. dropPoint: pointerPosition,
  899. event,
  900. });
  901. container.drop(this, currentIndex, this._initialIndex, this._initialContainer, isPointerOverContainer, distance, pointerPosition, event);
  902. this._dropContainer = this._initialContainer;
  903. });
  904. }
  905. /**
  906. * Updates the item's position in its drop container, or moves it
  907. * into a new one, depending on its current drag position.
  908. */
  909. _updateActiveDropContainer({ x, y }, { x: rawX, y: rawY }) {
  910. // Drop container that draggable has been moved into.
  911. let newContainer = this._initialContainer._getSiblingContainerFromPosition(this, x, y);
  912. // If we couldn't find a new container to move the item into, and the item has left its
  913. // initial container, check whether the it's over the initial container. This handles the
  914. // case where two containers are connected one way and the user tries to undo dragging an
  915. // item into a new container.
  916. if (!newContainer &&
  917. this._dropContainer !== this._initialContainer &&
  918. this._initialContainer._isOverContainer(x, y)) {
  919. newContainer = this._initialContainer;
  920. }
  921. if (newContainer && newContainer !== this._dropContainer) {
  922. this._ngZone.run(() => {
  923. // Notify the old container that the item has left.
  924. this.exited.next({ item: this, container: this._dropContainer });
  925. this._dropContainer.exit(this);
  926. // Notify the new container that the item has entered.
  927. this._dropContainer = newContainer;
  928. this._dropContainer.enter(this, x, y, newContainer === this._initialContainer &&
  929. // If we're re-entering the initial container and sorting is disabled,
  930. // put item the into its starting index to begin with.
  931. newContainer.sortingDisabled
  932. ? this._initialIndex
  933. : undefined);
  934. this.entered.next({
  935. item: this,
  936. container: newContainer,
  937. currentIndex: newContainer.getItemIndex(this),
  938. });
  939. });
  940. }
  941. // Dragging may have been interrupted as a result of the events above.
  942. if (this.isDragging()) {
  943. this._dropContainer._startScrollingIfNecessary(rawX, rawY);
  944. this._dropContainer._sortItem(this, x, y, this._pointerDirectionDelta);
  945. if (this.constrainPosition) {
  946. this._applyPreviewTransform(x, y);
  947. }
  948. else {
  949. this._applyPreviewTransform(x - this._pickupPositionInElement.x, y - this._pickupPositionInElement.y);
  950. }
  951. }
  952. }
  953. /**
  954. * Creates the element that will be rendered next to the user's pointer
  955. * and will be used as a preview of the element that is being dragged.
  956. */
  957. _createPreviewElement() {
  958. const previewConfig = this._previewTemplate;
  959. const previewClass = this.previewClass;
  960. const previewTemplate = previewConfig ? previewConfig.template : null;
  961. let preview;
  962. if (previewTemplate && previewConfig) {
  963. // Measure the element before we've inserted the preview
  964. // since the insertion could throw off the measurement.
  965. const rootRect = previewConfig.matchSize ? this._initialClientRect : null;
  966. const viewRef = previewConfig.viewContainer.createEmbeddedView(previewTemplate, previewConfig.context);
  967. viewRef.detectChanges();
  968. preview = getRootNode(viewRef, this._document);
  969. this._previewRef = viewRef;
  970. if (previewConfig.matchSize) {
  971. matchElementSize(preview, rootRect);
  972. }
  973. else {
  974. preview.style.transform = getTransform(this._pickupPositionOnPage.x, this._pickupPositionOnPage.y);
  975. }
  976. }
  977. else {
  978. preview = deepCloneNode(this._rootElement);
  979. matchElementSize(preview, this._initialClientRect);
  980. if (this._initialTransform) {
  981. preview.style.transform = this._initialTransform;
  982. }
  983. }
  984. extendStyles(preview.style, {
  985. // It's important that we disable the pointer events on the preview, because
  986. // it can throw off the `document.elementFromPoint` calls in the `CdkDropList`.
  987. 'pointer-events': 'none',
  988. // We have to reset the margin, because it can throw off positioning relative to the viewport.
  989. 'margin': '0',
  990. 'position': 'fixed',
  991. 'top': '0',
  992. 'left': '0',
  993. 'z-index': `${this._config.zIndex || 1000}`,
  994. }, dragImportantProperties);
  995. toggleNativeDragInteractions(preview, false);
  996. preview.classList.add('cdk-drag-preview');
  997. preview.setAttribute('dir', this._direction);
  998. if (previewClass) {
  999. if (Array.isArray(previewClass)) {
  1000. previewClass.forEach(className => preview.classList.add(className));
  1001. }
  1002. else {
  1003. preview.classList.add(previewClass);
  1004. }
  1005. }
  1006. return preview;
  1007. }
  1008. /**
  1009. * Animates the preview element from its current position to the location of the drop placeholder.
  1010. * @returns Promise that resolves when the animation completes.
  1011. */
  1012. _animatePreviewToPlaceholder() {
  1013. // If the user hasn't moved yet, the transitionend event won't fire.
  1014. if (!this._hasMoved) {
  1015. return Promise.resolve();
  1016. }
  1017. const placeholderRect = this._placeholder.getBoundingClientRect();
  1018. // Apply the class that adds a transition to the preview.
  1019. this._preview.classList.add('cdk-drag-animating');
  1020. // Move the preview to the placeholder position.
  1021. this._applyPreviewTransform(placeholderRect.left, placeholderRect.top);
  1022. // If the element doesn't have a `transition`, the `transitionend` event won't fire. Since
  1023. // we need to trigger a style recalculation in order for the `cdk-drag-animating` class to
  1024. // apply its style, we take advantage of the available info to figure out whether we need to
  1025. // bind the event in the first place.
  1026. const duration = getTransformTransitionDurationInMs(this._preview);
  1027. if (duration === 0) {
  1028. return Promise.resolve();
  1029. }
  1030. return this._ngZone.runOutsideAngular(() => {
  1031. return new Promise(resolve => {
  1032. const handler = ((event) => {
  1033. if (!event ||
  1034. (_getEventTarget(event) === this._preview && event.propertyName === 'transform')) {
  1035. this._preview?.removeEventListener('transitionend', handler);
  1036. resolve();
  1037. clearTimeout(timeout);
  1038. }
  1039. });
  1040. // If a transition is short enough, the browser might not fire the `transitionend` event.
  1041. // Since we know how long it's supposed to take, add a timeout with a 50% buffer that'll
  1042. // fire if the transition hasn't completed when it was supposed to.
  1043. const timeout = setTimeout(handler, duration * 1.5);
  1044. this._preview.addEventListener('transitionend', handler);
  1045. });
  1046. });
  1047. }
  1048. /** Creates an element that will be shown instead of the current element while dragging. */
  1049. _createPlaceholderElement() {
  1050. const placeholderConfig = this._placeholderTemplate;
  1051. const placeholderTemplate = placeholderConfig ? placeholderConfig.template : null;
  1052. let placeholder;
  1053. if (placeholderTemplate) {
  1054. this._placeholderRef = placeholderConfig.viewContainer.createEmbeddedView(placeholderTemplate, placeholderConfig.context);
  1055. this._placeholderRef.detectChanges();
  1056. placeholder = getRootNode(this._placeholderRef, this._document);
  1057. }
  1058. else {
  1059. placeholder = deepCloneNode(this._rootElement);
  1060. }
  1061. // Stop pointer events on the preview so the user can't
  1062. // interact with it while the preview is animating.
  1063. placeholder.style.pointerEvents = 'none';
  1064. placeholder.classList.add('cdk-drag-placeholder');
  1065. return placeholder;
  1066. }
  1067. /**
  1068. * Figures out the coordinates at which an element was picked up.
  1069. * @param referenceElement Element that initiated the dragging.
  1070. * @param event Event that initiated the dragging.
  1071. */
  1072. _getPointerPositionInElement(elementRect, referenceElement, event) {
  1073. const handleElement = referenceElement === this._rootElement ? null : referenceElement;
  1074. const referenceRect = handleElement ? handleElement.getBoundingClientRect() : elementRect;
  1075. const point = isTouchEvent(event) ? event.targetTouches[0] : event;
  1076. const scrollPosition = this._getViewportScrollPosition();
  1077. const x = point.pageX - referenceRect.left - scrollPosition.left;
  1078. const y = point.pageY - referenceRect.top - scrollPosition.top;
  1079. return {
  1080. x: referenceRect.left - elementRect.left + x,
  1081. y: referenceRect.top - elementRect.top + y,
  1082. };
  1083. }
  1084. /** Determines the point of the page that was touched by the user. */
  1085. _getPointerPositionOnPage(event) {
  1086. const scrollPosition = this._getViewportScrollPosition();
  1087. const point = isTouchEvent(event)
  1088. ? // `touches` will be empty for start/end events so we have to fall back to `changedTouches`.
  1089. // Also note that on real devices we're guaranteed for either `touches` or `changedTouches`
  1090. // to have a value, but Firefox in device emulation mode has a bug where both can be empty
  1091. // for `touchstart` and `touchend` so we fall back to a dummy object in order to avoid
  1092. // throwing an error. The value returned here will be incorrect, but since this only
  1093. // breaks inside a developer tool and the value is only used for secondary information,
  1094. // we can get away with it. See https://bugzilla.mozilla.org/show_bug.cgi?id=1615824.
  1095. event.touches[0] || event.changedTouches[0] || { pageX: 0, pageY: 0 }
  1096. : event;
  1097. const x = point.pageX - scrollPosition.left;
  1098. const y = point.pageY - scrollPosition.top;
  1099. // if dragging SVG element, try to convert from the screen coordinate system to the SVG
  1100. // coordinate system
  1101. if (this._ownerSVGElement) {
  1102. const svgMatrix = this._ownerSVGElement.getScreenCTM();
  1103. if (svgMatrix) {
  1104. const svgPoint = this._ownerSVGElement.createSVGPoint();
  1105. svgPoint.x = x;
  1106. svgPoint.y = y;
  1107. return svgPoint.matrixTransform(svgMatrix.inverse());
  1108. }
  1109. }
  1110. return { x, y };
  1111. }
  1112. /** Gets the pointer position on the page, accounting for any position constraints. */
  1113. _getConstrainedPointerPosition(point) {
  1114. const dropContainerLock = this._dropContainer ? this._dropContainer.lockAxis : null;
  1115. let { x, y } = this.constrainPosition
  1116. ? this.constrainPosition(point, this, this._initialClientRect, this._pickupPositionInElement)
  1117. : point;
  1118. if (this.lockAxis === 'x' || dropContainerLock === 'x') {
  1119. y = this._pickupPositionOnPage.y;
  1120. }
  1121. else if (this.lockAxis === 'y' || dropContainerLock === 'y') {
  1122. x = this._pickupPositionOnPage.x;
  1123. }
  1124. if (this._boundaryRect) {
  1125. const { x: pickupX, y: pickupY } = this._pickupPositionInElement;
  1126. const boundaryRect = this._boundaryRect;
  1127. const { width: previewWidth, height: previewHeight } = this._getPreviewRect();
  1128. const minY = boundaryRect.top + pickupY;
  1129. const maxY = boundaryRect.bottom - (previewHeight - pickupY);
  1130. const minX = boundaryRect.left + pickupX;
  1131. const maxX = boundaryRect.right - (previewWidth - pickupX);
  1132. x = clamp$1(x, minX, maxX);
  1133. y = clamp$1(y, minY, maxY);
  1134. }
  1135. return { x, y };
  1136. }
  1137. /** Updates the current drag delta, based on the user's current pointer position on the page. */
  1138. _updatePointerDirectionDelta(pointerPositionOnPage) {
  1139. const { x, y } = pointerPositionOnPage;
  1140. const delta = this._pointerDirectionDelta;
  1141. const positionSinceLastChange = this._pointerPositionAtLastDirectionChange;
  1142. // Amount of pixels the user has dragged since the last time the direction changed.
  1143. const changeX = Math.abs(x - positionSinceLastChange.x);
  1144. const changeY = Math.abs(y - positionSinceLastChange.y);
  1145. // Because we handle pointer events on a per-pixel basis, we don't want the delta
  1146. // to change for every pixel, otherwise anything that depends on it can look erratic.
  1147. // To make the delta more consistent, we track how much the user has moved since the last
  1148. // delta change and we only update it after it has reached a certain threshold.
  1149. if (changeX > this._config.pointerDirectionChangeThreshold) {
  1150. delta.x = x > positionSinceLastChange.x ? 1 : -1;
  1151. positionSinceLastChange.x = x;
  1152. }
  1153. if (changeY > this._config.pointerDirectionChangeThreshold) {
  1154. delta.y = y > positionSinceLastChange.y ? 1 : -1;
  1155. positionSinceLastChange.y = y;
  1156. }
  1157. return delta;
  1158. }
  1159. /** Toggles the native drag interactions, based on how many handles are registered. */
  1160. _toggleNativeDragInteractions() {
  1161. if (!this._rootElement || !this._handles) {
  1162. return;
  1163. }
  1164. const shouldEnable = this._handles.length > 0 || !this.isDragging();
  1165. if (shouldEnable !== this._nativeInteractionsEnabled) {
  1166. this._nativeInteractionsEnabled = shouldEnable;
  1167. toggleNativeDragInteractions(this._rootElement, shouldEnable);
  1168. }
  1169. }
  1170. /** Removes the manually-added event listeners from the root element. */
  1171. _removeRootElementListeners(element) {
  1172. element.removeEventListener('mousedown', this._pointerDown, activeEventListenerOptions);
  1173. element.removeEventListener('touchstart', this._pointerDown, passiveEventListenerOptions);
  1174. element.removeEventListener('dragstart', this._nativeDragStart, activeEventListenerOptions);
  1175. }
  1176. /**
  1177. * Applies a `transform` to the root element, taking into account any existing transforms on it.
  1178. * @param x New transform value along the X axis.
  1179. * @param y New transform value along the Y axis.
  1180. */
  1181. _applyRootElementTransform(x, y) {
  1182. const transform = getTransform(x, y);
  1183. const styles = this._rootElement.style;
  1184. // Cache the previous transform amount only after the first drag sequence, because
  1185. // we don't want our own transforms to stack on top of each other.
  1186. // Should be excluded none because none + translate3d(x, y, x) is invalid css
  1187. if (this._initialTransform == null) {
  1188. this._initialTransform =
  1189. styles.transform && styles.transform != 'none' ? styles.transform : '';
  1190. }
  1191. // Preserve the previous `transform` value, if there was one. Note that we apply our own
  1192. // transform before the user's, because things like rotation can affect which direction
  1193. // the element will be translated towards.
  1194. styles.transform = combineTransforms(transform, this._initialTransform);
  1195. }
  1196. /**
  1197. * Applies a `transform` to the preview, taking into account any existing transforms on it.
  1198. * @param x New transform value along the X axis.
  1199. * @param y New transform value along the Y axis.
  1200. */
  1201. _applyPreviewTransform(x, y) {
  1202. // Only apply the initial transform if the preview is a clone of the original element, otherwise
  1203. // it could be completely different and the transform might not make sense anymore.
  1204. const initialTransform = this._previewTemplate?.template ? undefined : this._initialTransform;
  1205. const transform = getTransform(x, y);
  1206. this._preview.style.transform = combineTransforms(transform, initialTransform);
  1207. }
  1208. /**
  1209. * Gets the distance that the user has dragged during the current drag sequence.
  1210. * @param currentPosition Current position of the user's pointer.
  1211. */
  1212. _getDragDistance(currentPosition) {
  1213. const pickupPosition = this._pickupPositionOnPage;
  1214. if (pickupPosition) {
  1215. return { x: currentPosition.x - pickupPosition.x, y: currentPosition.y - pickupPosition.y };
  1216. }
  1217. return { x: 0, y: 0 };
  1218. }
  1219. /** Cleans up any cached element dimensions that we don't need after dragging has stopped. */
  1220. _cleanupCachedDimensions() {
  1221. this._boundaryRect = this._previewRect = undefined;
  1222. this._parentPositions.clear();
  1223. }
  1224. /**
  1225. * Checks whether the element is still inside its boundary after the viewport has been resized.
  1226. * If not, the position is adjusted so that the element fits again.
  1227. */
  1228. _containInsideBoundaryOnResize() {
  1229. let { x, y } = this._passiveTransform;
  1230. if ((x === 0 && y === 0) || this.isDragging() || !this._boundaryElement) {
  1231. return;
  1232. }
  1233. // Note: don't use `_clientRectAtStart` here, because we want the latest position.
  1234. const elementRect = this._rootElement.getBoundingClientRect();
  1235. const boundaryRect = this._boundaryElement.getBoundingClientRect();
  1236. // It's possible that the element got hidden away after dragging (e.g. by switching to a
  1237. // different tab). Don't do anything in this case so we don't clear the user's position.
  1238. if ((boundaryRect.width === 0 && boundaryRect.height === 0) ||
  1239. (elementRect.width === 0 && elementRect.height === 0)) {
  1240. return;
  1241. }
  1242. const leftOverflow = boundaryRect.left - elementRect.left;
  1243. const rightOverflow = elementRect.right - boundaryRect.right;
  1244. const topOverflow = boundaryRect.top - elementRect.top;
  1245. const bottomOverflow = elementRect.bottom - boundaryRect.bottom;
  1246. // If the element has become wider than the boundary, we can't
  1247. // do much to make it fit so we just anchor it to the left.
  1248. if (boundaryRect.width > elementRect.width) {
  1249. if (leftOverflow > 0) {
  1250. x += leftOverflow;
  1251. }
  1252. if (rightOverflow > 0) {
  1253. x -= rightOverflow;
  1254. }
  1255. }
  1256. else {
  1257. x = 0;
  1258. }
  1259. // If the element has become taller than the boundary, we can't
  1260. // do much to make it fit so we just anchor it to the top.
  1261. if (boundaryRect.height > elementRect.height) {
  1262. if (topOverflow > 0) {
  1263. y += topOverflow;
  1264. }
  1265. if (bottomOverflow > 0) {
  1266. y -= bottomOverflow;
  1267. }
  1268. }
  1269. else {
  1270. y = 0;
  1271. }
  1272. if (x !== this._passiveTransform.x || y !== this._passiveTransform.y) {
  1273. this.setFreeDragPosition({ y, x });
  1274. }
  1275. }
  1276. /** Gets the drag start delay, based on the event type. */
  1277. _getDragStartDelay(event) {
  1278. const value = this.dragStartDelay;
  1279. if (typeof value === 'number') {
  1280. return value;
  1281. }
  1282. else if (isTouchEvent(event)) {
  1283. return value.touch;
  1284. }
  1285. return value ? value.mouse : 0;
  1286. }
  1287. /** Updates the internal state of the draggable element when scrolling has occurred. */
  1288. _updateOnScroll(event) {
  1289. const scrollDifference = this._parentPositions.handleScroll(event);
  1290. if (scrollDifference) {
  1291. const target = _getEventTarget(event);
  1292. // ClientRect dimensions are based on the scroll position of the page and its parent
  1293. // node so we have to update the cached boundary ClientRect if the user has scrolled.
  1294. if (this._boundaryRect &&
  1295. target !== this._boundaryElement &&
  1296. target.contains(this._boundaryElement)) {
  1297. adjustClientRect(this._boundaryRect, scrollDifference.top, scrollDifference.left);
  1298. }
  1299. this._pickupPositionOnPage.x += scrollDifference.left;
  1300. this._pickupPositionOnPage.y += scrollDifference.top;
  1301. // If we're in free drag mode, we have to update the active transform, because
  1302. // it isn't relative to the viewport like the preview inside a drop list.
  1303. if (!this._dropContainer) {
  1304. this._activeTransform.x -= scrollDifference.left;
  1305. this._activeTransform.y -= scrollDifference.top;
  1306. this._applyRootElementTransform(this._activeTransform.x, this._activeTransform.y);
  1307. }
  1308. }
  1309. }
  1310. /** Gets the scroll position of the viewport. */
  1311. _getViewportScrollPosition() {
  1312. return (this._parentPositions.positions.get(this._document)?.scrollPosition ||
  1313. this._parentPositions.getViewportScrollPosition());
  1314. }
  1315. /**
  1316. * Lazily resolves and returns the shadow root of the element. We do this in a function, rather
  1317. * than saving it in property directly on init, because we want to resolve it as late as possible
  1318. * in order to ensure that the element has been moved into the shadow DOM. Doing it inside the
  1319. * constructor might be too early if the element is inside of something like `ngFor` or `ngIf`.
  1320. */
  1321. _getShadowRoot() {
  1322. if (this._cachedShadowRoot === undefined) {
  1323. this._cachedShadowRoot = _getShadowRoot(this._rootElement);
  1324. }
  1325. return this._cachedShadowRoot;
  1326. }
  1327. /** Gets the element into which the drag preview should be inserted. */
  1328. _getPreviewInsertionPoint(initialParent, shadowRoot) {
  1329. const previewContainer = this._previewContainer || 'global';
  1330. if (previewContainer === 'parent') {
  1331. return initialParent;
  1332. }
  1333. if (previewContainer === 'global') {
  1334. const documentRef = this._document;
  1335. // We can't use the body if the user is in fullscreen mode,
  1336. // because the preview will render under the fullscreen element.
  1337. // TODO(crisbeto): dedupe this with the `FullscreenOverlayContainer` eventually.
  1338. return (shadowRoot ||
  1339. documentRef.fullscreenElement ||
  1340. documentRef.webkitFullscreenElement ||
  1341. documentRef.mozFullScreenElement ||
  1342. documentRef.msFullscreenElement ||
  1343. documentRef.body);
  1344. }
  1345. return coerceElement(previewContainer);
  1346. }
  1347. /** Lazily resolves and returns the dimensions of the preview. */
  1348. _getPreviewRect() {
  1349. // Cache the preview element rect if we haven't cached it already or if
  1350. // we cached it too early before the element dimensions were computed.
  1351. if (!this._previewRect || (!this._previewRect.width && !this._previewRect.height)) {
  1352. this._previewRect = this._preview
  1353. ? this._preview.getBoundingClientRect()
  1354. : this._initialClientRect;
  1355. }
  1356. return this._previewRect;
  1357. }
  1358. /** Gets a handle that is the target of an event. */
  1359. _getTargetHandle(event) {
  1360. return this._handles.find(handle => {
  1361. return event.target && (event.target === handle || handle.contains(event.target));
  1362. });
  1363. }
  1364. }
  1365. /**
  1366. * Gets a 3d `transform` that can be applied to an element.
  1367. * @param x Desired position of the element along the X axis.
  1368. * @param y Desired position of the element along the Y axis.
  1369. */
  1370. function getTransform(x, y) {
  1371. // Round the transforms since some browsers will
  1372. // blur the elements for sub-pixel transforms.
  1373. return `translate3d(${Math.round(x)}px, ${Math.round(y)}px, 0)`;
  1374. }
  1375. /** Clamps a value between a minimum and a maximum. */
  1376. function clamp$1(value, min, max) {
  1377. return Math.max(min, Math.min(max, value));
  1378. }
  1379. /** Determines whether an event is a touch event. */
  1380. function isTouchEvent(event) {
  1381. // This function is called for every pixel that the user has dragged so we need it to be
  1382. // as fast as possible. Since we only bind mouse events and touch events, we can assume
  1383. // that if the event's name starts with `t`, it's a touch event.
  1384. return event.type[0] === 't';
  1385. }
  1386. /**
  1387. * Gets the root HTML element of an embedded view.
  1388. * If the root is not an HTML element it gets wrapped in one.
  1389. */
  1390. function getRootNode(viewRef, _document) {
  1391. const rootNodes = viewRef.rootNodes;
  1392. if (rootNodes.length === 1 && rootNodes[0].nodeType === _document.ELEMENT_NODE) {
  1393. return rootNodes[0];
  1394. }
  1395. const wrapper = _document.createElement('div');
  1396. rootNodes.forEach(node => wrapper.appendChild(node));
  1397. return wrapper;
  1398. }
  1399. /**
  1400. * Matches the target element's size to the source's size.
  1401. * @param target Element that needs to be resized.
  1402. * @param sourceRect Dimensions of the source element.
  1403. */
  1404. function matchElementSize(target, sourceRect) {
  1405. target.style.width = `${sourceRect.width}px`;
  1406. target.style.height = `${sourceRect.height}px`;
  1407. target.style.transform = getTransform(sourceRect.left, sourceRect.top);
  1408. }
  1409. /**
  1410. * Moves an item one index in an array to another.
  1411. * @param array Array in which to move the item.
  1412. * @param fromIndex Starting index of the item.
  1413. * @param toIndex Index to which the item should be moved.
  1414. */
  1415. function moveItemInArray(array, fromIndex, toIndex) {
  1416. const from = clamp(fromIndex, array.length - 1);
  1417. const to = clamp(toIndex, array.length - 1);
  1418. if (from === to) {
  1419. return;
  1420. }
  1421. const target = array[from];
  1422. const delta = to < from ? -1 : 1;
  1423. for (let i = from; i !== to; i += delta) {
  1424. array[i] = array[i + delta];
  1425. }
  1426. array[to] = target;
  1427. }
  1428. /**
  1429. * Moves an item from one array to another.
  1430. * @param currentArray Array from which to transfer the item.
  1431. * @param targetArray Array into which to put the item.
  1432. * @param currentIndex Index of the item in its current array.
  1433. * @param targetIndex Index at which to insert the item.
  1434. */
  1435. function transferArrayItem(currentArray, targetArray, currentIndex, targetIndex) {
  1436. const from = clamp(currentIndex, currentArray.length - 1);
  1437. const to = clamp(targetIndex, targetArray.length);
  1438. if (currentArray.length) {
  1439. targetArray.splice(to, 0, currentArray.splice(from, 1)[0]);
  1440. }
  1441. }
  1442. /**
  1443. * Copies an item from one array to another, leaving it in its
  1444. * original position in current array.
  1445. * @param currentArray Array from which to copy the item.
  1446. * @param targetArray Array into which is copy the item.
  1447. * @param currentIndex Index of the item in its current array.
  1448. * @param targetIndex Index at which to insert the item.
  1449. *
  1450. */
  1451. function copyArrayItem(currentArray, targetArray, currentIndex, targetIndex) {
  1452. const to = clamp(targetIndex, targetArray.length);
  1453. if (currentArray.length) {
  1454. targetArray.splice(to, 0, currentArray[currentIndex]);
  1455. }
  1456. }
  1457. /** Clamps a number between zero and a maximum. */
  1458. function clamp(value, max) {
  1459. return Math.max(0, Math.min(max, value));
  1460. }
  1461. /**
  1462. * Strategy that only supports sorting along a single axis.
  1463. * Items are reordered using CSS transforms which allows for sorting to be animated.
  1464. * @docs-private
  1465. */
  1466. class SingleAxisSortStrategy {
  1467. constructor(_element, _dragDropRegistry) {
  1468. this._element = _element;
  1469. this._dragDropRegistry = _dragDropRegistry;
  1470. /** Cache of the dimensions of all the items inside the container. */
  1471. this._itemPositions = [];
  1472. /** Direction in which the list is oriented. */
  1473. this.orientation = 'vertical';
  1474. /**
  1475. * Keeps track of the item that was last swapped with the dragged item, as well as what direction
  1476. * the pointer was moving in when the swap occurred and whether the user's pointer continued to
  1477. * overlap with the swapped item after the swapping occurred.
  1478. */
  1479. this._previousSwap = {
  1480. drag: null,
  1481. delta: 0,
  1482. overlaps: false,
  1483. };
  1484. }
  1485. /**
  1486. * To be called when the drag sequence starts.
  1487. * @param items Items that are currently in the list.
  1488. */
  1489. start(items) {
  1490. this.withItems(items);
  1491. }
  1492. /**
  1493. * To be called when an item is being sorted.
  1494. * @param item Item to be sorted.
  1495. * @param pointerX Position of the item along the X axis.
  1496. * @param pointerY Position of the item along the Y axis.
  1497. * @param pointerDelta Direction in which the pointer is moving along each axis.
  1498. */
  1499. sort(item, pointerX, pointerY, pointerDelta) {
  1500. const siblings = this._itemPositions;
  1501. const newIndex = this._getItemIndexFromPointerPosition(item, pointerX, pointerY, pointerDelta);
  1502. if (newIndex === -1 && siblings.length > 0) {
  1503. return null;
  1504. }
  1505. const isHorizontal = this.orientation === 'horizontal';
  1506. const currentIndex = siblings.findIndex(currentItem => currentItem.drag === item);
  1507. const siblingAtNewPosition = siblings[newIndex];
  1508. const currentPosition = siblings[currentIndex].clientRect;
  1509. const newPosition = siblingAtNewPosition.clientRect;
  1510. const delta = currentIndex > newIndex ? 1 : -1;
  1511. // How many pixels the item's placeholder should be offset.
  1512. const itemOffset = this._getItemOffsetPx(currentPosition, newPosition, delta);
  1513. // How many pixels all the other items should be offset.
  1514. const siblingOffset = this._getSiblingOffsetPx(currentIndex, siblings, delta);
  1515. // Save the previous order of the items before moving the item to its new index.
  1516. // We use this to check whether an item has been moved as a result of the sorting.
  1517. const oldOrder = siblings.slice();
  1518. // Shuffle the array in place.
  1519. moveItemInArray(siblings, currentIndex, newIndex);
  1520. siblings.forEach((sibling, index) => {
  1521. // Don't do anything if the position hasn't changed.
  1522. if (oldOrder[index] === sibling) {
  1523. return;
  1524. }
  1525. const isDraggedItem = sibling.drag === item;
  1526. const offset = isDraggedItem ? itemOffset : siblingOffset;
  1527. const elementToOffset = isDraggedItem
  1528. ? item.getPlaceholderElement()
  1529. : sibling.drag.getRootElement();
  1530. // Update the offset to reflect the new position.
  1531. sibling.offset += offset;
  1532. // Since we're moving the items with a `transform`, we need to adjust their cached
  1533. // client rects to reflect their new position, as well as swap their positions in the cache.
  1534. // Note that we shouldn't use `getBoundingClientRect` here to update the cache, because the
  1535. // elements may be mid-animation which will give us a wrong result.
  1536. if (isHorizontal) {
  1537. // Round the transforms since some browsers will
  1538. // blur the elements, for sub-pixel transforms.
  1539. elementToOffset.style.transform = combineTransforms(`translate3d(${Math.round(sibling.offset)}px, 0, 0)`, sibling.initialTransform);
  1540. adjustClientRect(sibling.clientRect, 0, offset);
  1541. }
  1542. else {
  1543. elementToOffset.style.transform = combineTransforms(`translate3d(0, ${Math.round(sibling.offset)}px, 0)`, sibling.initialTransform);
  1544. adjustClientRect(sibling.clientRect, offset, 0);
  1545. }
  1546. });
  1547. // Note that it's important that we do this after the client rects have been adjusted.
  1548. this._previousSwap.overlaps = isInsideClientRect(newPosition, pointerX, pointerY);
  1549. this._previousSwap.drag = siblingAtNewPosition.drag;
  1550. this._previousSwap.delta = isHorizontal ? pointerDelta.x : pointerDelta.y;
  1551. return { previousIndex: currentIndex, currentIndex: newIndex };
  1552. }
  1553. /**
  1554. * Called when an item is being moved into the container.
  1555. * @param item Item that was moved into the container.
  1556. * @param pointerX Position of the item along the X axis.
  1557. * @param pointerY Position of the item along the Y axis.
  1558. * @param index Index at which the item entered. If omitted, the container will try to figure it
  1559. * out automatically.
  1560. */
  1561. enter(item, pointerX, pointerY, index) {
  1562. const newIndex = index == null || index < 0
  1563. ? // We use the coordinates of where the item entered the drop
  1564. // zone to figure out at which index it should be inserted.
  1565. this._getItemIndexFromPointerPosition(item, pointerX, pointerY)
  1566. : index;
  1567. const activeDraggables = this._activeDraggables;
  1568. const currentIndex = activeDraggables.indexOf(item);
  1569. const placeholder = item.getPlaceholderElement();
  1570. let newPositionReference = activeDraggables[newIndex];
  1571. // If the item at the new position is the same as the item that is being dragged,
  1572. // it means that we're trying to restore the item to its initial position. In this
  1573. // case we should use the next item from the list as the reference.
  1574. if (newPositionReference === item) {
  1575. newPositionReference = activeDraggables[newIndex + 1];
  1576. }
  1577. // If we didn't find a new position reference, it means that either the item didn't start off
  1578. // in this container, or that the item requested to be inserted at the end of the list.
  1579. if (!newPositionReference &&
  1580. (newIndex == null || newIndex === -1 || newIndex < activeDraggables.length - 1) &&
  1581. this._shouldEnterAsFirstChild(pointerX, pointerY)) {
  1582. newPositionReference = activeDraggables[0];
  1583. }
  1584. // Since the item may be in the `activeDraggables` already (e.g. if the user dragged it
  1585. // into another container and back again), we have to ensure that it isn't duplicated.
  1586. if (currentIndex > -1) {
  1587. activeDraggables.splice(currentIndex, 1);
  1588. }
  1589. // Don't use items that are being dragged as a reference, because
  1590. // their element has been moved down to the bottom of the body.
  1591. if (newPositionReference && !this._dragDropRegistry.isDragging(newPositionReference)) {
  1592. const element = newPositionReference.getRootElement();
  1593. element.parentElement.insertBefore(placeholder, element);
  1594. activeDraggables.splice(newIndex, 0, item);
  1595. }
  1596. else {
  1597. coerceElement(this._element).appendChild(placeholder);
  1598. activeDraggables.push(item);
  1599. }
  1600. // The transform needs to be cleared so it doesn't throw off the measurements.
  1601. placeholder.style.transform = '';
  1602. // Note that usually `start` is called together with `enter` when an item goes into a new
  1603. // container. This will cache item positions, but we need to refresh them since the amount
  1604. // of items has changed.
  1605. this._cacheItemPositions();
  1606. }
  1607. /** Sets the items that are currently part of the list. */
  1608. withItems(items) {
  1609. this._activeDraggables = items.slice();
  1610. this._cacheItemPositions();
  1611. }
  1612. /** Assigns a sort predicate to the strategy. */
  1613. withSortPredicate(predicate) {
  1614. this._sortPredicate = predicate;
  1615. }
  1616. /** Resets the strategy to its initial state before dragging was started. */
  1617. reset() {
  1618. // TODO(crisbeto): may have to wait for the animations to finish.
  1619. this._activeDraggables.forEach(item => {
  1620. const rootElement = item.getRootElement();
  1621. if (rootElement) {
  1622. const initialTransform = this._itemPositions.find(p => p.drag === item)?.initialTransform;
  1623. rootElement.style.transform = initialTransform || '';
  1624. }
  1625. });
  1626. this._itemPositions = [];
  1627. this._activeDraggables = [];
  1628. this._previousSwap.drag = null;
  1629. this._previousSwap.delta = 0;
  1630. this._previousSwap.overlaps = false;
  1631. }
  1632. /**
  1633. * Gets a snapshot of items currently in the list.
  1634. * Can include items that we dragged in from another list.
  1635. */
  1636. getActiveItemsSnapshot() {
  1637. return this._activeDraggables;
  1638. }
  1639. /** Gets the index of a specific item. */
  1640. getItemIndex(item) {
  1641. // Items are sorted always by top/left in the cache, however they flow differently in RTL.
  1642. // The rest of the logic still stands no matter what orientation we're in, however
  1643. // we need to invert the array when determining the index.
  1644. const items = this.orientation === 'horizontal' && this.direction === 'rtl'
  1645. ? this._itemPositions.slice().reverse()
  1646. : this._itemPositions;
  1647. return items.findIndex(currentItem => currentItem.drag === item);
  1648. }
  1649. /** Used to notify the strategy that the scroll position has changed. */
  1650. updateOnScroll(topDifference, leftDifference) {
  1651. // Since we know the amount that the user has scrolled we can shift all of the
  1652. // client rectangles ourselves. This is cheaper than re-measuring everything and
  1653. // we can avoid inconsistent behavior where we might be measuring the element before
  1654. // its position has changed.
  1655. this._itemPositions.forEach(({ clientRect }) => {
  1656. adjustClientRect(clientRect, topDifference, leftDifference);
  1657. });
  1658. // We need two loops for this, because we want all of the cached
  1659. // positions to be up-to-date before we re-sort the item.
  1660. this._itemPositions.forEach(({ drag }) => {
  1661. if (this._dragDropRegistry.isDragging(drag)) {
  1662. // We need to re-sort the item manually, because the pointer move
  1663. // events won't be dispatched while the user is scrolling.
  1664. drag._sortFromLastPointerPosition();
  1665. }
  1666. });
  1667. }
  1668. /** Refreshes the position cache of the items and sibling containers. */
  1669. _cacheItemPositions() {
  1670. const isHorizontal = this.orientation === 'horizontal';
  1671. this._itemPositions = this._activeDraggables
  1672. .map(drag => {
  1673. const elementToMeasure = drag.getVisibleElement();
  1674. return {
  1675. drag,
  1676. offset: 0,
  1677. initialTransform: elementToMeasure.style.transform || '',
  1678. clientRect: getMutableClientRect(elementToMeasure),
  1679. };
  1680. })
  1681. .sort((a, b) => {
  1682. return isHorizontal
  1683. ? a.clientRect.left - b.clientRect.left
  1684. : a.clientRect.top - b.clientRect.top;
  1685. });
  1686. }
  1687. /**
  1688. * Gets the offset in pixels by which the item that is being dragged should be moved.
  1689. * @param currentPosition Current position of the item.
  1690. * @param newPosition Position of the item where the current item should be moved.
  1691. * @param delta Direction in which the user is moving.
  1692. */
  1693. _getItemOffsetPx(currentPosition, newPosition, delta) {
  1694. const isHorizontal = this.orientation === 'horizontal';
  1695. let itemOffset = isHorizontal
  1696. ? newPosition.left - currentPosition.left
  1697. : newPosition.top - currentPosition.top;
  1698. // Account for differences in the item width/height.
  1699. if (delta === -1) {
  1700. itemOffset += isHorizontal
  1701. ? newPosition.width - currentPosition.width
  1702. : newPosition.height - currentPosition.height;
  1703. }
  1704. return itemOffset;
  1705. }
  1706. /**
  1707. * Gets the offset in pixels by which the items that aren't being dragged should be moved.
  1708. * @param currentIndex Index of the item currently being dragged.
  1709. * @param siblings All of the items in the list.
  1710. * @param delta Direction in which the user is moving.
  1711. */
  1712. _getSiblingOffsetPx(currentIndex, siblings, delta) {
  1713. const isHorizontal = this.orientation === 'horizontal';
  1714. const currentPosition = siblings[currentIndex].clientRect;
  1715. const immediateSibling = siblings[currentIndex + delta * -1];
  1716. let siblingOffset = currentPosition[isHorizontal ? 'width' : 'height'] * delta;
  1717. if (immediateSibling) {
  1718. const start = isHorizontal ? 'left' : 'top';
  1719. const end = isHorizontal ? 'right' : 'bottom';
  1720. // Get the spacing between the start of the current item and the end of the one immediately
  1721. // after it in the direction in which the user is dragging, or vice versa. We add it to the
  1722. // offset in order to push the element to where it will be when it's inline and is influenced
  1723. // by the `margin` of its siblings.
  1724. if (delta === -1) {
  1725. siblingOffset -= immediateSibling.clientRect[start] - currentPosition[end];
  1726. }
  1727. else {
  1728. siblingOffset += currentPosition[start] - immediateSibling.clientRect[end];
  1729. }
  1730. }
  1731. return siblingOffset;
  1732. }
  1733. /**
  1734. * Checks if pointer is entering in the first position
  1735. * @param pointerX Position of the user's pointer along the X axis.
  1736. * @param pointerY Position of the user's pointer along the Y axis.
  1737. */
  1738. _shouldEnterAsFirstChild(pointerX, pointerY) {
  1739. if (!this._activeDraggables.length) {
  1740. return false;
  1741. }
  1742. const itemPositions = this._itemPositions;
  1743. const isHorizontal = this.orientation === 'horizontal';
  1744. // `itemPositions` are sorted by position while `activeDraggables` are sorted by child index
  1745. // check if container is using some sort of "reverse" ordering (eg: flex-direction: row-reverse)
  1746. const reversed = itemPositions[0].drag !== this._activeDraggables[0];
  1747. if (reversed) {
  1748. const lastItemRect = itemPositions[itemPositions.length - 1].clientRect;
  1749. return isHorizontal ? pointerX >= lastItemRect.right : pointerY >= lastItemRect.bottom;
  1750. }
  1751. else {
  1752. const firstItemRect = itemPositions[0].clientRect;
  1753. return isHorizontal ? pointerX <= firstItemRect.left : pointerY <= firstItemRect.top;
  1754. }
  1755. }
  1756. /**
  1757. * Gets the index of an item in the drop container, based on the position of the user's pointer.
  1758. * @param item Item that is being sorted.
  1759. * @param pointerX Position of the user's pointer along the X axis.
  1760. * @param pointerY Position of the user's pointer along the Y axis.
  1761. * @param delta Direction in which the user is moving their pointer.
  1762. */
  1763. _getItemIndexFromPointerPosition(item, pointerX, pointerY, delta) {
  1764. const isHorizontal = this.orientation === 'horizontal';
  1765. const index = this._itemPositions.findIndex(({ drag, clientRect }) => {
  1766. // Skip the item itself.
  1767. if (drag === item) {
  1768. return false;
  1769. }
  1770. if (delta) {
  1771. const direction = isHorizontal ? delta.x : delta.y;
  1772. // If the user is still hovering over the same item as last time, their cursor hasn't left
  1773. // the item after we made the swap, and they didn't change the direction in which they're
  1774. // dragging, we don't consider it a direction swap.
  1775. if (drag === this._previousSwap.drag &&
  1776. this._previousSwap.overlaps &&
  1777. direction === this._previousSwap.delta) {
  1778. return false;
  1779. }
  1780. }
  1781. return isHorizontal
  1782. ? // Round these down since most browsers report client rects with
  1783. // sub-pixel precision, whereas the pointer coordinates are rounded to pixels.
  1784. pointerX >= Math.floor(clientRect.left) && pointerX < Math.floor(clientRect.right)
  1785. : pointerY >= Math.floor(clientRect.top) && pointerY < Math.floor(clientRect.bottom);
  1786. });
  1787. return index === -1 || !this._sortPredicate(index, item) ? -1 : index;
  1788. }
  1789. }
  1790. /**
  1791. * Proximity, as a ratio to width/height, at which a
  1792. * dragged item will affect the drop container.
  1793. */
  1794. const DROP_PROXIMITY_THRESHOLD = 0.05;
  1795. /**
  1796. * Proximity, as a ratio to width/height at which to start auto-scrolling the drop list or the
  1797. * viewport. The value comes from trying it out manually until it feels right.
  1798. */
  1799. const SCROLL_PROXIMITY_THRESHOLD = 0.05;
  1800. /**
  1801. * Reference to a drop list. Used to manipulate or dispose of the container.
  1802. */
  1803. class DropListRef {
  1804. constructor(element, _dragDropRegistry, _document, _ngZone, _viewportRuler) {
  1805. this._dragDropRegistry = _dragDropRegistry;
  1806. this._ngZone = _ngZone;
  1807. this._viewportRuler = _viewportRuler;
  1808. /** Whether starting a dragging sequence from this container is disabled. */
  1809. this.disabled = false;
  1810. /** Whether sorting items within the list is disabled. */
  1811. this.sortingDisabled = false;
  1812. /**
  1813. * Whether auto-scrolling the view when the user
  1814. * moves their pointer close to the edges is disabled.
  1815. */
  1816. this.autoScrollDisabled = false;
  1817. /** Number of pixels to scroll for each frame when auto-scrolling an element. */
  1818. this.autoScrollStep = 2;
  1819. /**
  1820. * Function that is used to determine whether an item
  1821. * is allowed to be moved into a drop container.
  1822. */
  1823. this.enterPredicate = () => true;
  1824. /** Function that is used to determine whether an item can be sorted into a particular index. */
  1825. this.sortPredicate = () => true;
  1826. /** Emits right before dragging has started. */
  1827. this.beforeStarted = new Subject();
  1828. /**
  1829. * Emits when the user has moved a new drag item into this container.
  1830. */
  1831. this.entered = new Subject();
  1832. /**
  1833. * Emits when the user removes an item from the container
  1834. * by dragging it into another container.
  1835. */
  1836. this.exited = new Subject();
  1837. /** Emits when the user drops an item inside the container. */
  1838. this.dropped = new Subject();
  1839. /** Emits as the user is swapping items while actively dragging. */
  1840. this.sorted = new Subject();
  1841. /** Emits when a dragging sequence is started in a list connected to the current one. */
  1842. this.receivingStarted = new Subject();
  1843. /** Emits when a dragging sequence is stopped from a list connected to the current one. */
  1844. this.receivingStopped = new Subject();
  1845. /** Whether an item in the list is being dragged. */
  1846. this._isDragging = false;
  1847. /** Draggable items in the container. */
  1848. this._draggables = [];
  1849. /** Drop lists that are connected to the current one. */
  1850. this._siblings = [];
  1851. /** Connected siblings that currently have a dragged item. */
  1852. this._activeSiblings = new Set();
  1853. /** Subscription to the window being scrolled. */
  1854. this._viewportScrollSubscription = Subscription.EMPTY;
  1855. /** Vertical direction in which the list is currently scrolling. */
  1856. this._verticalScrollDirection = 0 /* AutoScrollVerticalDirection.NONE */;
  1857. /** Horizontal direction in which the list is currently scrolling. */
  1858. this._horizontalScrollDirection = 0 /* AutoScrollHorizontalDirection.NONE */;
  1859. /** Used to signal to the current auto-scroll sequence when to stop. */
  1860. this._stopScrollTimers = new Subject();
  1861. /** Shadow root of the current element. Necessary for `elementFromPoint` to resolve correctly. */
  1862. this._cachedShadowRoot = null;
  1863. /** Starts the interval that'll auto-scroll the element. */
  1864. this._startScrollInterval = () => {
  1865. this._stopScrolling();
  1866. interval(0, animationFrameScheduler)
  1867. .pipe(takeUntil(this._stopScrollTimers))
  1868. .subscribe(() => {
  1869. const node = this._scrollNode;
  1870. const scrollStep = this.autoScrollStep;
  1871. if (this._verticalScrollDirection === 1 /* AutoScrollVerticalDirection.UP */) {
  1872. node.scrollBy(0, -scrollStep);
  1873. }
  1874. else if (this._verticalScrollDirection === 2 /* AutoScrollVerticalDirection.DOWN */) {
  1875. node.scrollBy(0, scrollStep);
  1876. }
  1877. if (this._horizontalScrollDirection === 1 /* AutoScrollHorizontalDirection.LEFT */) {
  1878. node.scrollBy(-scrollStep, 0);
  1879. }
  1880. else if (this._horizontalScrollDirection === 2 /* AutoScrollHorizontalDirection.RIGHT */) {
  1881. node.scrollBy(scrollStep, 0);
  1882. }
  1883. });
  1884. };
  1885. this.element = coerceElement(element);
  1886. this._document = _document;
  1887. this.withScrollableParents([this.element]);
  1888. _dragDropRegistry.registerDropContainer(this);
  1889. this._parentPositions = new ParentPositionTracker(_document);
  1890. this._sortStrategy = new SingleAxisSortStrategy(this.element, _dragDropRegistry);
  1891. this._sortStrategy.withSortPredicate((index, item) => this.sortPredicate(index, item, this));
  1892. }
  1893. /** Removes the drop list functionality from the DOM element. */
  1894. dispose() {
  1895. this._stopScrolling();
  1896. this._stopScrollTimers.complete();
  1897. this._viewportScrollSubscription.unsubscribe();
  1898. this.beforeStarted.complete();
  1899. this.entered.complete();
  1900. this.exited.complete();
  1901. this.dropped.complete();
  1902. this.sorted.complete();
  1903. this.receivingStarted.complete();
  1904. this.receivingStopped.complete();
  1905. this._activeSiblings.clear();
  1906. this._scrollNode = null;
  1907. this._parentPositions.clear();
  1908. this._dragDropRegistry.removeDropContainer(this);
  1909. }
  1910. /** Whether an item from this list is currently being dragged. */
  1911. isDragging() {
  1912. return this._isDragging;
  1913. }
  1914. /** Starts dragging an item. */
  1915. start() {
  1916. this._draggingStarted();
  1917. this._notifyReceivingSiblings();
  1918. }
  1919. /**
  1920. * Attempts to move an item into the container.
  1921. * @param item Item that was moved into the container.
  1922. * @param pointerX Position of the item along the X axis.
  1923. * @param pointerY Position of the item along the Y axis.
  1924. * @param index Index at which the item entered. If omitted, the container will try to figure it
  1925. * out automatically.
  1926. */
  1927. enter(item, pointerX, pointerY, index) {
  1928. this._draggingStarted();
  1929. // If sorting is disabled, we want the item to return to its starting
  1930. // position if the user is returning it to its initial container.
  1931. if (index == null && this.sortingDisabled) {
  1932. index = this._draggables.indexOf(item);
  1933. }
  1934. this._sortStrategy.enter(item, pointerX, pointerY, index);
  1935. // Note that this usually happens inside `_draggingStarted` as well, but the dimensions
  1936. // can change when the sort strategy moves the item around inside `enter`.
  1937. this._cacheParentPositions();
  1938. // Notify siblings at the end so that the item has been inserted into the `activeDraggables`.
  1939. this._notifyReceivingSiblings();
  1940. this.entered.next({ item, container: this, currentIndex: this.getItemIndex(item) });
  1941. }
  1942. /**
  1943. * Removes an item from the container after it was dragged into another container by the user.
  1944. * @param item Item that was dragged out.
  1945. */
  1946. exit(item) {
  1947. this._reset();
  1948. this.exited.next({ item, container: this });
  1949. }
  1950. /**
  1951. * Drops an item into this container.
  1952. * @param item Item being dropped into the container.
  1953. * @param currentIndex Index at which the item should be inserted.
  1954. * @param previousIndex Index of the item when dragging started.
  1955. * @param previousContainer Container from which the item got dragged in.
  1956. * @param isPointerOverContainer Whether the user's pointer was over the
  1957. * container when the item was dropped.
  1958. * @param distance Distance the user has dragged since the start of the dragging sequence.
  1959. * @param event Event that triggered the dropping sequence.
  1960. *
  1961. * @breaking-change 15.0.0 `previousIndex` and `event` parameters to become required.
  1962. */
  1963. drop(item, currentIndex, previousIndex, previousContainer, isPointerOverContainer, distance, dropPoint, event = {}) {
  1964. this._reset();
  1965. this.dropped.next({
  1966. item,
  1967. currentIndex,
  1968. previousIndex,
  1969. container: this,
  1970. previousContainer,
  1971. isPointerOverContainer,
  1972. distance,
  1973. dropPoint,
  1974. event,
  1975. });
  1976. }
  1977. /**
  1978. * Sets the draggable items that are a part of this list.
  1979. * @param items Items that are a part of this list.
  1980. */
  1981. withItems(items) {
  1982. const previousItems = this._draggables;
  1983. this._draggables = items;
  1984. items.forEach(item => item._withDropContainer(this));
  1985. if (this.isDragging()) {
  1986. const draggedItems = previousItems.filter(item => item.isDragging());
  1987. // If all of the items being dragged were removed
  1988. // from the list, abort the current drag sequence.
  1989. if (draggedItems.every(item => items.indexOf(item) === -1)) {
  1990. this._reset();
  1991. }
  1992. else {
  1993. this._sortStrategy.withItems(this._draggables);
  1994. }
  1995. }
  1996. return this;
  1997. }
  1998. /** Sets the layout direction of the drop list. */
  1999. withDirection(direction) {
  2000. this._sortStrategy.direction = direction;
  2001. return this;
  2002. }
  2003. /**
  2004. * Sets the containers that are connected to this one. When two or more containers are
  2005. * connected, the user will be allowed to transfer items between them.
  2006. * @param connectedTo Other containers that the current containers should be connected to.
  2007. */
  2008. connectedTo(connectedTo) {
  2009. this._siblings = connectedTo.slice();
  2010. return this;
  2011. }
  2012. /**
  2013. * Sets the orientation of the container.
  2014. * @param orientation New orientation for the container.
  2015. */
  2016. withOrientation(orientation) {
  2017. // TODO(crisbeto): eventually we should be constructing the new sort strategy here based on
  2018. // the new orientation. For now we can assume that it'll always be `SingleAxisSortStrategy`.
  2019. this._sortStrategy.orientation = orientation;
  2020. return this;
  2021. }
  2022. /**
  2023. * Sets which parent elements are can be scrolled while the user is dragging.
  2024. * @param elements Elements that can be scrolled.
  2025. */
  2026. withScrollableParents(elements) {
  2027. const element = coerceElement(this.element);
  2028. // We always allow the current element to be scrollable
  2029. // so we need to ensure that it's in the array.
  2030. this._scrollableElements =
  2031. elements.indexOf(element) === -1 ? [element, ...elements] : elements.slice();
  2032. return this;
  2033. }
  2034. /** Gets the scrollable parents that are registered with this drop container. */
  2035. getScrollableParents() {
  2036. return this._scrollableElements;
  2037. }
  2038. /**
  2039. * Figures out the index of an item in the container.
  2040. * @param item Item whose index should be determined.
  2041. */
  2042. getItemIndex(item) {
  2043. return this._isDragging
  2044. ? this._sortStrategy.getItemIndex(item)
  2045. : this._draggables.indexOf(item);
  2046. }
  2047. /**
  2048. * Whether the list is able to receive the item that
  2049. * is currently being dragged inside a connected drop list.
  2050. */
  2051. isReceiving() {
  2052. return this._activeSiblings.size > 0;
  2053. }
  2054. /**
  2055. * Sorts an item inside the container based on its position.
  2056. * @param item Item to be sorted.
  2057. * @param pointerX Position of the item along the X axis.
  2058. * @param pointerY Position of the item along the Y axis.
  2059. * @param pointerDelta Direction in which the pointer is moving along each axis.
  2060. */
  2061. _sortItem(item, pointerX, pointerY, pointerDelta) {
  2062. // Don't sort the item if sorting is disabled or it's out of range.
  2063. if (this.sortingDisabled ||
  2064. !this._clientRect ||
  2065. !isPointerNearClientRect(this._clientRect, DROP_PROXIMITY_THRESHOLD, pointerX, pointerY)) {
  2066. return;
  2067. }
  2068. const result = this._sortStrategy.sort(item, pointerX, pointerY, pointerDelta);
  2069. if (result) {
  2070. this.sorted.next({
  2071. previousIndex: result.previousIndex,
  2072. currentIndex: result.currentIndex,
  2073. container: this,
  2074. item,
  2075. });
  2076. }
  2077. }
  2078. /**
  2079. * Checks whether the user's pointer is close to the edges of either the
  2080. * viewport or the drop list and starts the auto-scroll sequence.
  2081. * @param pointerX User's pointer position along the x axis.
  2082. * @param pointerY User's pointer position along the y axis.
  2083. */
  2084. _startScrollingIfNecessary(pointerX, pointerY) {
  2085. if (this.autoScrollDisabled) {
  2086. return;
  2087. }
  2088. let scrollNode;
  2089. let verticalScrollDirection = 0 /* AutoScrollVerticalDirection.NONE */;
  2090. let horizontalScrollDirection = 0 /* AutoScrollHorizontalDirection.NONE */;
  2091. // Check whether we should start scrolling any of the parent containers.
  2092. this._parentPositions.positions.forEach((position, element) => {
  2093. // We have special handling for the `document` below. Also this would be
  2094. // nicer with a for...of loop, but it requires changing a compiler flag.
  2095. if (element === this._document || !position.clientRect || scrollNode) {
  2096. return;
  2097. }
  2098. if (isPointerNearClientRect(position.clientRect, DROP_PROXIMITY_THRESHOLD, pointerX, pointerY)) {
  2099. [verticalScrollDirection, horizontalScrollDirection] = getElementScrollDirections(element, position.clientRect, pointerX, pointerY);
  2100. if (verticalScrollDirection || horizontalScrollDirection) {
  2101. scrollNode = element;
  2102. }
  2103. }
  2104. });
  2105. // Otherwise check if we can start scrolling the viewport.
  2106. if (!verticalScrollDirection && !horizontalScrollDirection) {
  2107. const { width, height } = this._viewportRuler.getViewportSize();
  2108. const clientRect = {
  2109. width,
  2110. height,
  2111. top: 0,
  2112. right: width,
  2113. bottom: height,
  2114. left: 0,
  2115. };
  2116. verticalScrollDirection = getVerticalScrollDirection(clientRect, pointerY);
  2117. horizontalScrollDirection = getHorizontalScrollDirection(clientRect, pointerX);
  2118. scrollNode = window;
  2119. }
  2120. if (scrollNode &&
  2121. (verticalScrollDirection !== this._verticalScrollDirection ||
  2122. horizontalScrollDirection !== this._horizontalScrollDirection ||
  2123. scrollNode !== this._scrollNode)) {
  2124. this._verticalScrollDirection = verticalScrollDirection;
  2125. this._horizontalScrollDirection = horizontalScrollDirection;
  2126. this._scrollNode = scrollNode;
  2127. if ((verticalScrollDirection || horizontalScrollDirection) && scrollNode) {
  2128. this._ngZone.runOutsideAngular(this._startScrollInterval);
  2129. }
  2130. else {
  2131. this._stopScrolling();
  2132. }
  2133. }
  2134. }
  2135. /** Stops any currently-running auto-scroll sequences. */
  2136. _stopScrolling() {
  2137. this._stopScrollTimers.next();
  2138. }
  2139. /** Starts the dragging sequence within the list. */
  2140. _draggingStarted() {
  2141. const styles = coerceElement(this.element).style;
  2142. this.beforeStarted.next();
  2143. this._isDragging = true;
  2144. // We need to disable scroll snapping while the user is dragging, because it breaks automatic
  2145. // scrolling. The browser seems to round the value based on the snapping points which means
  2146. // that we can't increment/decrement the scroll position.
  2147. this._initialScrollSnap = styles.msScrollSnapType || styles.scrollSnapType || '';
  2148. styles.scrollSnapType = styles.msScrollSnapType = 'none';
  2149. this._sortStrategy.start(this._draggables);
  2150. this._cacheParentPositions();
  2151. this._viewportScrollSubscription.unsubscribe();
  2152. this._listenToScrollEvents();
  2153. }
  2154. /** Caches the positions of the configured scrollable parents. */
  2155. _cacheParentPositions() {
  2156. const element = coerceElement(this.element);
  2157. this._parentPositions.cache(this._scrollableElements);
  2158. // The list element is always in the `scrollableElements`
  2159. // so we can take advantage of the cached `ClientRect`.
  2160. this._clientRect = this._parentPositions.positions.get(element).clientRect;
  2161. }
  2162. /** Resets the container to its initial state. */
  2163. _reset() {
  2164. this._isDragging = false;
  2165. const styles = coerceElement(this.element).style;
  2166. styles.scrollSnapType = styles.msScrollSnapType = this._initialScrollSnap;
  2167. this._siblings.forEach(sibling => sibling._stopReceiving(this));
  2168. this._sortStrategy.reset();
  2169. this._stopScrolling();
  2170. this._viewportScrollSubscription.unsubscribe();
  2171. this._parentPositions.clear();
  2172. }
  2173. /**
  2174. * Checks whether the user's pointer is positioned over the container.
  2175. * @param x Pointer position along the X axis.
  2176. * @param y Pointer position along the Y axis.
  2177. */
  2178. _isOverContainer(x, y) {
  2179. return this._clientRect != null && isInsideClientRect(this._clientRect, x, y);
  2180. }
  2181. /**
  2182. * Figures out whether an item should be moved into a sibling
  2183. * drop container, based on its current position.
  2184. * @param item Drag item that is being moved.
  2185. * @param x Position of the item along the X axis.
  2186. * @param y Position of the item along the Y axis.
  2187. */
  2188. _getSiblingContainerFromPosition(item, x, y) {
  2189. return this._siblings.find(sibling => sibling._canReceive(item, x, y));
  2190. }
  2191. /**
  2192. * Checks whether the drop list can receive the passed-in item.
  2193. * @param item Item that is being dragged into the list.
  2194. * @param x Position of the item along the X axis.
  2195. * @param y Position of the item along the Y axis.
  2196. */
  2197. _canReceive(item, x, y) {
  2198. if (!this._clientRect ||
  2199. !isInsideClientRect(this._clientRect, x, y) ||
  2200. !this.enterPredicate(item, this)) {
  2201. return false;
  2202. }
  2203. const elementFromPoint = this._getShadowRoot().elementFromPoint(x, y);
  2204. // If there's no element at the pointer position, then
  2205. // the client rect is probably scrolled out of the view.
  2206. if (!elementFromPoint) {
  2207. return false;
  2208. }
  2209. const nativeElement = coerceElement(this.element);
  2210. // The `ClientRect`, that we're using to find the container over which the user is
  2211. // hovering, doesn't give us any information on whether the element has been scrolled
  2212. // out of the view or whether it's overlapping with other containers. This means that
  2213. // we could end up transferring the item into a container that's invisible or is positioned
  2214. // below another one. We use the result from `elementFromPoint` to get the top-most element
  2215. // at the pointer position and to find whether it's one of the intersecting drop containers.
  2216. return elementFromPoint === nativeElement || nativeElement.contains(elementFromPoint);
  2217. }
  2218. /**
  2219. * Called by one of the connected drop lists when a dragging sequence has started.
  2220. * @param sibling Sibling in which dragging has started.
  2221. */
  2222. _startReceiving(sibling, items) {
  2223. const activeSiblings = this._activeSiblings;
  2224. if (!activeSiblings.has(sibling) &&
  2225. items.every(item => {
  2226. // Note that we have to add an exception to the `enterPredicate` for items that started off
  2227. // in this drop list. The drag ref has logic that allows an item to return to its initial
  2228. // container, if it has left the initial container and none of the connected containers
  2229. // allow it to enter. See `DragRef._updateActiveDropContainer` for more context.
  2230. return this.enterPredicate(item, this) || this._draggables.indexOf(item) > -1;
  2231. })) {
  2232. activeSiblings.add(sibling);
  2233. this._cacheParentPositions();
  2234. this._listenToScrollEvents();
  2235. this.receivingStarted.next({
  2236. initiator: sibling,
  2237. receiver: this,
  2238. items,
  2239. });
  2240. }
  2241. }
  2242. /**
  2243. * Called by a connected drop list when dragging has stopped.
  2244. * @param sibling Sibling whose dragging has stopped.
  2245. */
  2246. _stopReceiving(sibling) {
  2247. this._activeSiblings.delete(sibling);
  2248. this._viewportScrollSubscription.unsubscribe();
  2249. this.receivingStopped.next({ initiator: sibling, receiver: this });
  2250. }
  2251. /**
  2252. * Starts listening to scroll events on the viewport.
  2253. * Used for updating the internal state of the list.
  2254. */
  2255. _listenToScrollEvents() {
  2256. this._viewportScrollSubscription = this._dragDropRegistry
  2257. .scrolled(this._getShadowRoot())
  2258. .subscribe(event => {
  2259. if (this.isDragging()) {
  2260. const scrollDifference = this._parentPositions.handleScroll(event);
  2261. if (scrollDifference) {
  2262. this._sortStrategy.updateOnScroll(scrollDifference.top, scrollDifference.left);
  2263. }
  2264. }
  2265. else if (this.isReceiving()) {
  2266. this._cacheParentPositions();
  2267. }
  2268. });
  2269. }
  2270. /**
  2271. * Lazily resolves and returns the shadow root of the element. We do this in a function, rather
  2272. * than saving it in property directly on init, because we want to resolve it as late as possible
  2273. * in order to ensure that the element has been moved into the shadow DOM. Doing it inside the
  2274. * constructor might be too early if the element is inside of something like `ngFor` or `ngIf`.
  2275. */
  2276. _getShadowRoot() {
  2277. if (!this._cachedShadowRoot) {
  2278. const shadowRoot = _getShadowRoot(coerceElement(this.element));
  2279. this._cachedShadowRoot = (shadowRoot || this._document);
  2280. }
  2281. return this._cachedShadowRoot;
  2282. }
  2283. /** Notifies any siblings that may potentially receive the item. */
  2284. _notifyReceivingSiblings() {
  2285. const draggedItems = this._sortStrategy
  2286. .getActiveItemsSnapshot()
  2287. .filter(item => item.isDragging());
  2288. this._siblings.forEach(sibling => sibling._startReceiving(this, draggedItems));
  2289. }
  2290. }
  2291. /**
  2292. * Gets whether the vertical auto-scroll direction of a node.
  2293. * @param clientRect Dimensions of the node.
  2294. * @param pointerY Position of the user's pointer along the y axis.
  2295. */
  2296. function getVerticalScrollDirection(clientRect, pointerY) {
  2297. const { top, bottom, height } = clientRect;
  2298. const yThreshold = height * SCROLL_PROXIMITY_THRESHOLD;
  2299. if (pointerY >= top - yThreshold && pointerY <= top + yThreshold) {
  2300. return 1 /* AutoScrollVerticalDirection.UP */;
  2301. }
  2302. else if (pointerY >= bottom - yThreshold && pointerY <= bottom + yThreshold) {
  2303. return 2 /* AutoScrollVerticalDirection.DOWN */;
  2304. }
  2305. return 0 /* AutoScrollVerticalDirection.NONE */;
  2306. }
  2307. /**
  2308. * Gets whether the horizontal auto-scroll direction of a node.
  2309. * @param clientRect Dimensions of the node.
  2310. * @param pointerX Position of the user's pointer along the x axis.
  2311. */
  2312. function getHorizontalScrollDirection(clientRect, pointerX) {
  2313. const { left, right, width } = clientRect;
  2314. const xThreshold = width * SCROLL_PROXIMITY_THRESHOLD;
  2315. if (pointerX >= left - xThreshold && pointerX <= left + xThreshold) {
  2316. return 1 /* AutoScrollHorizontalDirection.LEFT */;
  2317. }
  2318. else if (pointerX >= right - xThreshold && pointerX <= right + xThreshold) {
  2319. return 2 /* AutoScrollHorizontalDirection.RIGHT */;
  2320. }
  2321. return 0 /* AutoScrollHorizontalDirection.NONE */;
  2322. }
  2323. /**
  2324. * Gets the directions in which an element node should be scrolled,
  2325. * assuming that the user's pointer is already within it scrollable region.
  2326. * @param element Element for which we should calculate the scroll direction.
  2327. * @param clientRect Bounding client rectangle of the element.
  2328. * @param pointerX Position of the user's pointer along the x axis.
  2329. * @param pointerY Position of the user's pointer along the y axis.
  2330. */
  2331. function getElementScrollDirections(element, clientRect, pointerX, pointerY) {
  2332. const computedVertical = getVerticalScrollDirection(clientRect, pointerY);
  2333. const computedHorizontal = getHorizontalScrollDirection(clientRect, pointerX);
  2334. let verticalScrollDirection = 0 /* AutoScrollVerticalDirection.NONE */;
  2335. let horizontalScrollDirection = 0 /* AutoScrollHorizontalDirection.NONE */;
  2336. // Note that we here we do some extra checks for whether the element is actually scrollable in
  2337. // a certain direction and we only assign the scroll direction if it is. We do this so that we
  2338. // can allow other elements to be scrolled, if the current element can't be scrolled anymore.
  2339. // This allows us to handle cases where the scroll regions of two scrollable elements overlap.
  2340. if (computedVertical) {
  2341. const scrollTop = element.scrollTop;
  2342. if (computedVertical === 1 /* AutoScrollVerticalDirection.UP */) {
  2343. if (scrollTop > 0) {
  2344. verticalScrollDirection = 1 /* AutoScrollVerticalDirection.UP */;
  2345. }
  2346. }
  2347. else if (element.scrollHeight - scrollTop > element.clientHeight) {
  2348. verticalScrollDirection = 2 /* AutoScrollVerticalDirection.DOWN */;
  2349. }
  2350. }
  2351. if (computedHorizontal) {
  2352. const scrollLeft = element.scrollLeft;
  2353. if (computedHorizontal === 1 /* AutoScrollHorizontalDirection.LEFT */) {
  2354. if (scrollLeft > 0) {
  2355. horizontalScrollDirection = 1 /* AutoScrollHorizontalDirection.LEFT */;
  2356. }
  2357. }
  2358. else if (element.scrollWidth - scrollLeft > element.clientWidth) {
  2359. horizontalScrollDirection = 2 /* AutoScrollHorizontalDirection.RIGHT */;
  2360. }
  2361. }
  2362. return [verticalScrollDirection, horizontalScrollDirection];
  2363. }
  2364. /** Event options that can be used to bind an active, capturing event. */
  2365. const activeCapturingEventOptions = normalizePassiveListenerOptions({
  2366. passive: false,
  2367. capture: true,
  2368. });
  2369. /**
  2370. * Service that keeps track of all the drag item and drop container
  2371. * instances, and manages global event listeners on the `document`.
  2372. * @docs-private
  2373. */
  2374. // Note: this class is generic, rather than referencing CdkDrag and CdkDropList directly, in order
  2375. // to avoid circular imports. If we were to reference them here, importing the registry into the
  2376. // classes that are registering themselves will introduce a circular import.
  2377. class DragDropRegistry {
  2378. constructor(_ngZone, _document) {
  2379. this._ngZone = _ngZone;
  2380. /** Registered drop container instances. */
  2381. this._dropInstances = new Set();
  2382. /** Registered drag item instances. */
  2383. this._dragInstances = new Set();
  2384. /** Drag item instances that are currently being dragged. */
  2385. this._activeDragInstances = [];
  2386. /** Keeps track of the event listeners that we've bound to the `document`. */
  2387. this._globalListeners = new Map();
  2388. /**
  2389. * Predicate function to check if an item is being dragged. Moved out into a property,
  2390. * because it'll be called a lot and we don't want to create a new function every time.
  2391. */
  2392. this._draggingPredicate = (item) => item.isDragging();
  2393. /**
  2394. * Emits the `touchmove` or `mousemove` events that are dispatched
  2395. * while the user is dragging a drag item instance.
  2396. */
  2397. this.pointerMove = new Subject();
  2398. /**
  2399. * Emits the `touchend` or `mouseup` events that are dispatched
  2400. * while the user is dragging a drag item instance.
  2401. */
  2402. this.pointerUp = new Subject();
  2403. /**
  2404. * Emits when the viewport has been scrolled while the user is dragging an item.
  2405. * @deprecated To be turned into a private member. Use the `scrolled` method instead.
  2406. * @breaking-change 13.0.0
  2407. */
  2408. this.scroll = new Subject();
  2409. /**
  2410. * Event listener that will prevent the default browser action while the user is dragging.
  2411. * @param event Event whose default action should be prevented.
  2412. */
  2413. this._preventDefaultWhileDragging = (event) => {
  2414. if (this._activeDragInstances.length > 0) {
  2415. event.preventDefault();
  2416. }
  2417. };
  2418. /** Event listener for `touchmove` that is bound even if no dragging is happening. */
  2419. this._persistentTouchmoveListener = (event) => {
  2420. if (this._activeDragInstances.length > 0) {
  2421. // Note that we only want to prevent the default action after dragging has actually started.
  2422. // Usually this is the same time at which the item is added to the `_activeDragInstances`,
  2423. // but it could be pushed back if the user has set up a drag delay or threshold.
  2424. if (this._activeDragInstances.some(this._draggingPredicate)) {
  2425. event.preventDefault();
  2426. }
  2427. this.pointerMove.next(event);
  2428. }
  2429. };
  2430. this._document = _document;
  2431. }
  2432. /** Adds a drop container to the registry. */
  2433. registerDropContainer(drop) {
  2434. if (!this._dropInstances.has(drop)) {
  2435. this._dropInstances.add(drop);
  2436. }
  2437. }
  2438. /** Adds a drag item instance to the registry. */
  2439. registerDragItem(drag) {
  2440. this._dragInstances.add(drag);
  2441. // The `touchmove` event gets bound once, ahead of time, because WebKit
  2442. // won't preventDefault on a dynamically-added `touchmove` listener.
  2443. // See https://bugs.webkit.org/show_bug.cgi?id=184250.
  2444. if (this._dragInstances.size === 1) {
  2445. this._ngZone.runOutsideAngular(() => {
  2446. // The event handler has to be explicitly active,
  2447. // because newer browsers make it passive by default.
  2448. this._document.addEventListener('touchmove', this._persistentTouchmoveListener, activeCapturingEventOptions);
  2449. });
  2450. }
  2451. }
  2452. /** Removes a drop container from the registry. */
  2453. removeDropContainer(drop) {
  2454. this._dropInstances.delete(drop);
  2455. }
  2456. /** Removes a drag item instance from the registry. */
  2457. removeDragItem(drag) {
  2458. this._dragInstances.delete(drag);
  2459. this.stopDragging(drag);
  2460. if (this._dragInstances.size === 0) {
  2461. this._document.removeEventListener('touchmove', this._persistentTouchmoveListener, activeCapturingEventOptions);
  2462. }
  2463. }
  2464. /**
  2465. * Starts the dragging sequence for a drag instance.
  2466. * @param drag Drag instance which is being dragged.
  2467. * @param event Event that initiated the dragging.
  2468. */
  2469. startDragging(drag, event) {
  2470. // Do not process the same drag twice to avoid memory leaks and redundant listeners
  2471. if (this._activeDragInstances.indexOf(drag) > -1) {
  2472. return;
  2473. }
  2474. this._activeDragInstances.push(drag);
  2475. if (this._activeDragInstances.length === 1) {
  2476. const isTouchEvent = event.type.startsWith('touch');
  2477. // We explicitly bind __active__ listeners here, because newer browsers will default to
  2478. // passive ones for `mousemove` and `touchmove`. The events need to be active, because we
  2479. // use `preventDefault` to prevent the page from scrolling while the user is dragging.
  2480. this._globalListeners
  2481. .set(isTouchEvent ? 'touchend' : 'mouseup', {
  2482. handler: (e) => this.pointerUp.next(e),
  2483. options: true,
  2484. })
  2485. .set('scroll', {
  2486. handler: (e) => this.scroll.next(e),
  2487. // Use capturing so that we pick up scroll changes in any scrollable nodes that aren't
  2488. // the document. See https://github.com/angular/components/issues/17144.
  2489. options: true,
  2490. })
  2491. // Preventing the default action on `mousemove` isn't enough to disable text selection
  2492. // on Safari so we need to prevent the selection event as well. Alternatively this can
  2493. // be done by setting `user-select: none` on the `body`, however it has causes a style
  2494. // recalculation which can be expensive on pages with a lot of elements.
  2495. .set('selectstart', {
  2496. handler: this._preventDefaultWhileDragging,
  2497. options: activeCapturingEventOptions,
  2498. });
  2499. // We don't have to bind a move event for touch drag sequences, because
  2500. // we already have a persistent global one bound from `registerDragItem`.
  2501. if (!isTouchEvent) {
  2502. this._globalListeners.set('mousemove', {
  2503. handler: (e) => this.pointerMove.next(e),
  2504. options: activeCapturingEventOptions,
  2505. });
  2506. }
  2507. this._ngZone.runOutsideAngular(() => {
  2508. this._globalListeners.forEach((config, name) => {
  2509. this._document.addEventListener(name, config.handler, config.options);
  2510. });
  2511. });
  2512. }
  2513. }
  2514. /** Stops dragging a drag item instance. */
  2515. stopDragging(drag) {
  2516. const index = this._activeDragInstances.indexOf(drag);
  2517. if (index > -1) {
  2518. this._activeDragInstances.splice(index, 1);
  2519. if (this._activeDragInstances.length === 0) {
  2520. this._clearGlobalListeners();
  2521. }
  2522. }
  2523. }
  2524. /** Gets whether a drag item instance is currently being dragged. */
  2525. isDragging(drag) {
  2526. return this._activeDragInstances.indexOf(drag) > -1;
  2527. }
  2528. /**
  2529. * Gets a stream that will emit when any element on the page is scrolled while an item is being
  2530. * dragged.
  2531. * @param shadowRoot Optional shadow root that the current dragging sequence started from.
  2532. * Top-level listeners won't pick up events coming from the shadow DOM so this parameter can
  2533. * be used to include an additional top-level listener at the shadow root level.
  2534. */
  2535. scrolled(shadowRoot) {
  2536. const streams = [this.scroll];
  2537. if (shadowRoot && shadowRoot !== this._document) {
  2538. // Note that this is basically the same as `fromEvent` from rxjs, but we do it ourselves,
  2539. // because we want to guarantee that the event is bound outside of the `NgZone`. With
  2540. // `fromEvent` it'll only happen if the subscription is outside the `NgZone`.
  2541. streams.push(new Observable((observer) => {
  2542. return this._ngZone.runOutsideAngular(() => {
  2543. const eventOptions = true;
  2544. const callback = (event) => {
  2545. if (this._activeDragInstances.length) {
  2546. observer.next(event);
  2547. }
  2548. };
  2549. shadowRoot.addEventListener('scroll', callback, eventOptions);
  2550. return () => {
  2551. shadowRoot.removeEventListener('scroll', callback, eventOptions);
  2552. };
  2553. });
  2554. }));
  2555. }
  2556. return merge(...streams);
  2557. }
  2558. ngOnDestroy() {
  2559. this._dragInstances.forEach(instance => this.removeDragItem(instance));
  2560. this._dropInstances.forEach(instance => this.removeDropContainer(instance));
  2561. this._clearGlobalListeners();
  2562. this.pointerMove.complete();
  2563. this.pointerUp.complete();
  2564. }
  2565. /** Clears out the global event listeners from the `document`. */
  2566. _clearGlobalListeners() {
  2567. this._globalListeners.forEach((config, name) => {
  2568. this._document.removeEventListener(name, config.handler, config.options);
  2569. });
  2570. this._globalListeners.clear();
  2571. }
  2572. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: DragDropRegistry, deps: [{ token: i0.NgZone }, { token: DOCUMENT }], target: i0.ɵɵFactoryTarget.Injectable }); }
  2573. static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: DragDropRegistry, providedIn: 'root' }); }
  2574. }
  2575. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: DragDropRegistry, decorators: [{
  2576. type: Injectable,
  2577. args: [{ providedIn: 'root' }]
  2578. }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: undefined, decorators: [{
  2579. type: Inject,
  2580. args: [DOCUMENT]
  2581. }] }]; } });
  2582. /** Default configuration to be used when creating a `DragRef`. */
  2583. const DEFAULT_CONFIG = {
  2584. dragStartThreshold: 5,
  2585. pointerDirectionChangeThreshold: 5,
  2586. };
  2587. /**
  2588. * Service that allows for drag-and-drop functionality to be attached to DOM elements.
  2589. */
  2590. class DragDrop {
  2591. constructor(_document, _ngZone, _viewportRuler, _dragDropRegistry) {
  2592. this._document = _document;
  2593. this._ngZone = _ngZone;
  2594. this._viewportRuler = _viewportRuler;
  2595. this._dragDropRegistry = _dragDropRegistry;
  2596. }
  2597. /**
  2598. * Turns an element into a draggable item.
  2599. * @param element Element to which to attach the dragging functionality.
  2600. * @param config Object used to configure the dragging behavior.
  2601. */
  2602. createDrag(element, config = DEFAULT_CONFIG) {
  2603. return new DragRef(element, config, this._document, this._ngZone, this._viewportRuler, this._dragDropRegistry);
  2604. }
  2605. /**
  2606. * Turns an element into a drop list.
  2607. * @param element Element to which to attach the drop list functionality.
  2608. */
  2609. createDropList(element) {
  2610. return new DropListRef(element, this._dragDropRegistry, this._document, this._ngZone, this._viewportRuler);
  2611. }
  2612. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: DragDrop, deps: [{ token: DOCUMENT }, { token: i0.NgZone }, { token: i1.ViewportRuler }, { token: DragDropRegistry }], target: i0.ɵɵFactoryTarget.Injectable }); }
  2613. static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: DragDrop, providedIn: 'root' }); }
  2614. }
  2615. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: DragDrop, decorators: [{
  2616. type: Injectable,
  2617. args: [{ providedIn: 'root' }]
  2618. }], ctorParameters: function () { return [{ type: undefined, decorators: [{
  2619. type: Inject,
  2620. args: [DOCUMENT]
  2621. }] }, { type: i0.NgZone }, { type: i1.ViewportRuler }, { type: DragDropRegistry }]; } });
  2622. /**
  2623. * Injection token that can be used for a `CdkDrag` to provide itself as a parent to the
  2624. * drag-specific child directive (`CdkDragHandle`, `CdkDragPreview` etc.). Used primarily
  2625. * to avoid circular imports.
  2626. * @docs-private
  2627. */
  2628. const CDK_DRAG_PARENT = new InjectionToken('CDK_DRAG_PARENT');
  2629. /**
  2630. * Asserts that a particular node is an element.
  2631. * @param node Node to be checked.
  2632. * @param name Name to attach to the error message.
  2633. */
  2634. function assertElementNode(node, name) {
  2635. if (node.nodeType !== 1) {
  2636. throw Error(`${name} must be attached to an element node. ` + `Currently attached to "${node.nodeName}".`);
  2637. }
  2638. }
  2639. /**
  2640. * Injection token that can be used to reference instances of `CdkDragHandle`. It serves as
  2641. * alternative token to the actual `CdkDragHandle` class which could cause unnecessary
  2642. * retention of the class and its directive metadata.
  2643. */
  2644. const CDK_DRAG_HANDLE = new InjectionToken('CdkDragHandle');
  2645. /** Handle that can be used to drag a CdkDrag instance. */
  2646. class CdkDragHandle {
  2647. /** Whether starting to drag through this handle is disabled. */
  2648. get disabled() {
  2649. return this._disabled;
  2650. }
  2651. set disabled(value) {
  2652. this._disabled = coerceBooleanProperty(value);
  2653. this._stateChanges.next(this);
  2654. }
  2655. constructor(element, parentDrag) {
  2656. this.element = element;
  2657. /** Emits when the state of the handle has changed. */
  2658. this._stateChanges = new Subject();
  2659. this._disabled = false;
  2660. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  2661. assertElementNode(element.nativeElement, 'cdkDragHandle');
  2662. }
  2663. this._parentDrag = parentDrag;
  2664. }
  2665. ngOnDestroy() {
  2666. this._stateChanges.complete();
  2667. }
  2668. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDragHandle, deps: [{ token: i0.ElementRef }, { token: CDK_DRAG_PARENT, optional: true, skipSelf: true }], target: i0.ɵɵFactoryTarget.Directive }); }
  2669. static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.0", type: CdkDragHandle, isStandalone: true, selector: "[cdkDragHandle]", inputs: { disabled: ["cdkDragHandleDisabled", "disabled"] }, host: { classAttribute: "cdk-drag-handle" }, providers: [{ provide: CDK_DRAG_HANDLE, useExisting: CdkDragHandle }], ngImport: i0 }); }
  2670. }
  2671. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDragHandle, decorators: [{
  2672. type: Directive,
  2673. args: [{
  2674. selector: '[cdkDragHandle]',
  2675. standalone: true,
  2676. host: {
  2677. 'class': 'cdk-drag-handle',
  2678. },
  2679. providers: [{ provide: CDK_DRAG_HANDLE, useExisting: CdkDragHandle }],
  2680. }]
  2681. }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: undefined, decorators: [{
  2682. type: Inject,
  2683. args: [CDK_DRAG_PARENT]
  2684. }, {
  2685. type: Optional
  2686. }, {
  2687. type: SkipSelf
  2688. }] }]; }, propDecorators: { disabled: [{
  2689. type: Input,
  2690. args: ['cdkDragHandleDisabled']
  2691. }] } });
  2692. /**
  2693. * Injection token that can be used to reference instances of `CdkDragPlaceholder`. It serves as
  2694. * alternative token to the actual `CdkDragPlaceholder` class which could cause unnecessary
  2695. * retention of the class and its directive metadata.
  2696. */
  2697. const CDK_DRAG_PLACEHOLDER = new InjectionToken('CdkDragPlaceholder');
  2698. /**
  2699. * Element that will be used as a template for the placeholder of a CdkDrag when
  2700. * it is being dragged. The placeholder is displayed in place of the element being dragged.
  2701. */
  2702. class CdkDragPlaceholder {
  2703. constructor(templateRef) {
  2704. this.templateRef = templateRef;
  2705. }
  2706. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDragPlaceholder, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
  2707. static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.0", type: CdkDragPlaceholder, isStandalone: true, selector: "ng-template[cdkDragPlaceholder]", inputs: { data: "data" }, providers: [{ provide: CDK_DRAG_PLACEHOLDER, useExisting: CdkDragPlaceholder }], ngImport: i0 }); }
  2708. }
  2709. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDragPlaceholder, decorators: [{
  2710. type: Directive,
  2711. args: [{
  2712. selector: 'ng-template[cdkDragPlaceholder]',
  2713. standalone: true,
  2714. providers: [{ provide: CDK_DRAG_PLACEHOLDER, useExisting: CdkDragPlaceholder }],
  2715. }]
  2716. }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; }, propDecorators: { data: [{
  2717. type: Input
  2718. }] } });
  2719. /**
  2720. * Injection token that can be used to reference instances of `CdkDragPreview`. It serves as
  2721. * alternative token to the actual `CdkDragPreview` class which could cause unnecessary
  2722. * retention of the class and its directive metadata.
  2723. */
  2724. const CDK_DRAG_PREVIEW = new InjectionToken('CdkDragPreview');
  2725. /**
  2726. * Element that will be used as a template for the preview
  2727. * of a CdkDrag when it is being dragged.
  2728. */
  2729. class CdkDragPreview {
  2730. /** Whether the preview should preserve the same size as the item that is being dragged. */
  2731. get matchSize() {
  2732. return this._matchSize;
  2733. }
  2734. set matchSize(value) {
  2735. this._matchSize = coerceBooleanProperty(value);
  2736. }
  2737. constructor(templateRef) {
  2738. this.templateRef = templateRef;
  2739. this._matchSize = false;
  2740. }
  2741. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDragPreview, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
  2742. static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.0", type: CdkDragPreview, isStandalone: true, selector: "ng-template[cdkDragPreview]", inputs: { data: "data", matchSize: "matchSize" }, providers: [{ provide: CDK_DRAG_PREVIEW, useExisting: CdkDragPreview }], ngImport: i0 }); }
  2743. }
  2744. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDragPreview, decorators: [{
  2745. type: Directive,
  2746. args: [{
  2747. selector: 'ng-template[cdkDragPreview]',
  2748. standalone: true,
  2749. providers: [{ provide: CDK_DRAG_PREVIEW, useExisting: CdkDragPreview }],
  2750. }]
  2751. }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; }, propDecorators: { data: [{
  2752. type: Input
  2753. }], matchSize: [{
  2754. type: Input
  2755. }] } });
  2756. /**
  2757. * Injection token that can be used to configure the
  2758. * behavior of the drag&drop-related components.
  2759. */
  2760. const CDK_DRAG_CONFIG = new InjectionToken('CDK_DRAG_CONFIG');
  2761. const DRAG_HOST_CLASS = 'cdk-drag';
  2762. /**
  2763. * Injection token that can be used to reference instances of `CdkDropList`. It serves as
  2764. * alternative token to the actual `CdkDropList` class which could cause unnecessary
  2765. * retention of the class and its directive metadata.
  2766. */
  2767. const CDK_DROP_LIST = new InjectionToken('CdkDropList');
  2768. /** Element that can be moved inside a CdkDropList container. */
  2769. class CdkDrag {
  2770. static { this._dragInstances = []; }
  2771. /** Whether starting to drag this element is disabled. */
  2772. get disabled() {
  2773. return this._disabled || (this.dropContainer && this.dropContainer.disabled);
  2774. }
  2775. set disabled(value) {
  2776. this._disabled = coerceBooleanProperty(value);
  2777. this._dragRef.disabled = this._disabled;
  2778. }
  2779. constructor(
  2780. /** Element that the draggable is attached to. */
  2781. element,
  2782. /** Droppable container that the draggable is a part of. */
  2783. dropContainer,
  2784. /**
  2785. * @deprecated `_document` parameter no longer being used and will be removed.
  2786. * @breaking-change 12.0.0
  2787. */
  2788. _document, _ngZone, _viewContainerRef, config, _dir, dragDrop, _changeDetectorRef, _selfHandle, _parentDrag) {
  2789. this.element = element;
  2790. this.dropContainer = dropContainer;
  2791. this._ngZone = _ngZone;
  2792. this._viewContainerRef = _viewContainerRef;
  2793. this._dir = _dir;
  2794. this._changeDetectorRef = _changeDetectorRef;
  2795. this._selfHandle = _selfHandle;
  2796. this._parentDrag = _parentDrag;
  2797. this._destroyed = new Subject();
  2798. /** Emits when the user starts dragging the item. */
  2799. this.started = new EventEmitter();
  2800. /** Emits when the user has released a drag item, before any animations have started. */
  2801. this.released = new EventEmitter();
  2802. /** Emits when the user stops dragging an item in the container. */
  2803. this.ended = new EventEmitter();
  2804. /** Emits when the user has moved the item into a new container. */
  2805. this.entered = new EventEmitter();
  2806. /** Emits when the user removes the item its container by dragging it into another container. */
  2807. this.exited = new EventEmitter();
  2808. /** Emits when the user drops the item inside a container. */
  2809. this.dropped = new EventEmitter();
  2810. /**
  2811. * Emits as the user is dragging the item. Use with caution,
  2812. * because this event will fire for every pixel that the user has dragged.
  2813. */
  2814. this.moved = new Observable((observer) => {
  2815. const subscription = this._dragRef.moved
  2816. .pipe(map(movedEvent => ({
  2817. source: this,
  2818. pointerPosition: movedEvent.pointerPosition,
  2819. event: movedEvent.event,
  2820. delta: movedEvent.delta,
  2821. distance: movedEvent.distance,
  2822. })))
  2823. .subscribe(observer);
  2824. return () => {
  2825. subscription.unsubscribe();
  2826. };
  2827. });
  2828. this._dragRef = dragDrop.createDrag(element, {
  2829. dragStartThreshold: config && config.dragStartThreshold != null ? config.dragStartThreshold : 5,
  2830. pointerDirectionChangeThreshold: config && config.pointerDirectionChangeThreshold != null
  2831. ? config.pointerDirectionChangeThreshold
  2832. : 5,
  2833. zIndex: config?.zIndex,
  2834. });
  2835. this._dragRef.data = this;
  2836. // We have to keep track of the drag instances in order to be able to match an element to
  2837. // a drag instance. We can't go through the global registry of `DragRef`, because the root
  2838. // element could be different.
  2839. CdkDrag._dragInstances.push(this);
  2840. if (config) {
  2841. this._assignDefaults(config);
  2842. }
  2843. // Note that usually the container is assigned when the drop list is picks up the item, but in
  2844. // some cases (mainly transplanted views with OnPush, see #18341) we may end up in a situation
  2845. // where there are no items on the first change detection pass, but the items get picked up as
  2846. // soon as the user triggers another pass by dragging. This is a problem, because the item would
  2847. // have to switch from standalone mode to drag mode in the middle of the dragging sequence which
  2848. // is too late since the two modes save different kinds of information. We work around it by
  2849. // assigning the drop container both from here and the list.
  2850. if (dropContainer) {
  2851. this._dragRef._withDropContainer(dropContainer._dropListRef);
  2852. dropContainer.addItem(this);
  2853. }
  2854. this._syncInputs(this._dragRef);
  2855. this._handleEvents(this._dragRef);
  2856. }
  2857. /**
  2858. * Returns the element that is being used as a placeholder
  2859. * while the current element is being dragged.
  2860. */
  2861. getPlaceholderElement() {
  2862. return this._dragRef.getPlaceholderElement();
  2863. }
  2864. /** Returns the root draggable element. */
  2865. getRootElement() {
  2866. return this._dragRef.getRootElement();
  2867. }
  2868. /** Resets a standalone drag item to its initial position. */
  2869. reset() {
  2870. this._dragRef.reset();
  2871. }
  2872. /**
  2873. * Gets the pixel coordinates of the draggable outside of a drop container.
  2874. */
  2875. getFreeDragPosition() {
  2876. return this._dragRef.getFreeDragPosition();
  2877. }
  2878. /**
  2879. * Sets the current position in pixels the draggable outside of a drop container.
  2880. * @param value New position to be set.
  2881. */
  2882. setFreeDragPosition(value) {
  2883. this._dragRef.setFreeDragPosition(value);
  2884. }
  2885. ngAfterViewInit() {
  2886. // Normally this isn't in the zone, but it can cause major performance regressions for apps
  2887. // using `zone-patch-rxjs` because it'll trigger a change detection when it unsubscribes.
  2888. this._ngZone.runOutsideAngular(() => {
  2889. // We need to wait for the zone to stabilize, in order for the reference
  2890. // element to be in the proper place in the DOM. This is mostly relevant
  2891. // for draggable elements inside portals since they get stamped out in
  2892. // their original DOM position and then they get transferred to the portal.
  2893. this._ngZone.onStable.pipe(take(1), takeUntil(this._destroyed)).subscribe(() => {
  2894. this._updateRootElement();
  2895. this._setupHandlesListener();
  2896. if (this.freeDragPosition) {
  2897. this._dragRef.setFreeDragPosition(this.freeDragPosition);
  2898. }
  2899. });
  2900. });
  2901. }
  2902. ngOnChanges(changes) {
  2903. const rootSelectorChange = changes['rootElementSelector'];
  2904. const positionChange = changes['freeDragPosition'];
  2905. // We don't have to react to the first change since it's being
  2906. // handled in `ngAfterViewInit` where it needs to be deferred.
  2907. if (rootSelectorChange && !rootSelectorChange.firstChange) {
  2908. this._updateRootElement();
  2909. }
  2910. // Skip the first change since it's being handled in `ngAfterViewInit`.
  2911. if (positionChange && !positionChange.firstChange && this.freeDragPosition) {
  2912. this._dragRef.setFreeDragPosition(this.freeDragPosition);
  2913. }
  2914. }
  2915. ngOnDestroy() {
  2916. if (this.dropContainer) {
  2917. this.dropContainer.removeItem(this);
  2918. }
  2919. const index = CdkDrag._dragInstances.indexOf(this);
  2920. if (index > -1) {
  2921. CdkDrag._dragInstances.splice(index, 1);
  2922. }
  2923. // Unnecessary in most cases, but used to avoid extra change detections with `zone-paths-rxjs`.
  2924. this._ngZone.runOutsideAngular(() => {
  2925. this._destroyed.next();
  2926. this._destroyed.complete();
  2927. this._dragRef.dispose();
  2928. });
  2929. }
  2930. /** Syncs the root element with the `DragRef`. */
  2931. _updateRootElement() {
  2932. const element = this.element.nativeElement;
  2933. let rootElement = element;
  2934. if (this.rootElementSelector) {
  2935. rootElement =
  2936. element.closest !== undefined
  2937. ? element.closest(this.rootElementSelector)
  2938. : // Comment tag doesn't have closest method, so use parent's one.
  2939. element.parentElement?.closest(this.rootElementSelector);
  2940. }
  2941. if (rootElement && (typeof ngDevMode === 'undefined' || ngDevMode)) {
  2942. assertElementNode(rootElement, 'cdkDrag');
  2943. }
  2944. this._dragRef.withRootElement(rootElement || element);
  2945. }
  2946. /** Gets the boundary element, based on the `boundaryElement` value. */
  2947. _getBoundaryElement() {
  2948. const boundary = this.boundaryElement;
  2949. if (!boundary) {
  2950. return null;
  2951. }
  2952. if (typeof boundary === 'string') {
  2953. return this.element.nativeElement.closest(boundary);
  2954. }
  2955. return coerceElement(boundary);
  2956. }
  2957. /** Syncs the inputs of the CdkDrag with the options of the underlying DragRef. */
  2958. _syncInputs(ref) {
  2959. ref.beforeStarted.subscribe(() => {
  2960. if (!ref.isDragging()) {
  2961. const dir = this._dir;
  2962. const dragStartDelay = this.dragStartDelay;
  2963. const placeholder = this._placeholderTemplate
  2964. ? {
  2965. template: this._placeholderTemplate.templateRef,
  2966. context: this._placeholderTemplate.data,
  2967. viewContainer: this._viewContainerRef,
  2968. }
  2969. : null;
  2970. const preview = this._previewTemplate
  2971. ? {
  2972. template: this._previewTemplate.templateRef,
  2973. context: this._previewTemplate.data,
  2974. matchSize: this._previewTemplate.matchSize,
  2975. viewContainer: this._viewContainerRef,
  2976. }
  2977. : null;
  2978. ref.disabled = this.disabled;
  2979. ref.lockAxis = this.lockAxis;
  2980. ref.dragStartDelay =
  2981. typeof dragStartDelay === 'object' && dragStartDelay
  2982. ? dragStartDelay
  2983. : coerceNumberProperty(dragStartDelay);
  2984. ref.constrainPosition = this.constrainPosition;
  2985. ref.previewClass = this.previewClass;
  2986. ref
  2987. .withBoundaryElement(this._getBoundaryElement())
  2988. .withPlaceholderTemplate(placeholder)
  2989. .withPreviewTemplate(preview)
  2990. .withPreviewContainer(this.previewContainer || 'global');
  2991. if (dir) {
  2992. ref.withDirection(dir.value);
  2993. }
  2994. }
  2995. });
  2996. // This only needs to be resolved once.
  2997. ref.beforeStarted.pipe(take(1)).subscribe(() => {
  2998. // If we managed to resolve a parent through DI, use it.
  2999. if (this._parentDrag) {
  3000. ref.withParent(this._parentDrag._dragRef);
  3001. return;
  3002. }
  3003. // Otherwise fall back to resolving the parent by looking up the DOM. This can happen if
  3004. // the item was projected into another item by something like `ngTemplateOutlet`.
  3005. let parent = this.element.nativeElement.parentElement;
  3006. while (parent) {
  3007. if (parent.classList.contains(DRAG_HOST_CLASS)) {
  3008. ref.withParent(CdkDrag._dragInstances.find(drag => {
  3009. return drag.element.nativeElement === parent;
  3010. })?._dragRef || null);
  3011. break;
  3012. }
  3013. parent = parent.parentElement;
  3014. }
  3015. });
  3016. }
  3017. /** Handles the events from the underlying `DragRef`. */
  3018. _handleEvents(ref) {
  3019. ref.started.subscribe(startEvent => {
  3020. this.started.emit({ source: this, event: startEvent.event });
  3021. // Since all of these events run outside of change detection,
  3022. // we need to ensure that everything is marked correctly.
  3023. this._changeDetectorRef.markForCheck();
  3024. });
  3025. ref.released.subscribe(releaseEvent => {
  3026. this.released.emit({ source: this, event: releaseEvent.event });
  3027. });
  3028. ref.ended.subscribe(endEvent => {
  3029. this.ended.emit({
  3030. source: this,
  3031. distance: endEvent.distance,
  3032. dropPoint: endEvent.dropPoint,
  3033. event: endEvent.event,
  3034. });
  3035. // Since all of these events run outside of change detection,
  3036. // we need to ensure that everything is marked correctly.
  3037. this._changeDetectorRef.markForCheck();
  3038. });
  3039. ref.entered.subscribe(enterEvent => {
  3040. this.entered.emit({
  3041. container: enterEvent.container.data,
  3042. item: this,
  3043. currentIndex: enterEvent.currentIndex,
  3044. });
  3045. });
  3046. ref.exited.subscribe(exitEvent => {
  3047. this.exited.emit({
  3048. container: exitEvent.container.data,
  3049. item: this,
  3050. });
  3051. });
  3052. ref.dropped.subscribe(dropEvent => {
  3053. this.dropped.emit({
  3054. previousIndex: dropEvent.previousIndex,
  3055. currentIndex: dropEvent.currentIndex,
  3056. previousContainer: dropEvent.previousContainer.data,
  3057. container: dropEvent.container.data,
  3058. isPointerOverContainer: dropEvent.isPointerOverContainer,
  3059. item: this,
  3060. distance: dropEvent.distance,
  3061. dropPoint: dropEvent.dropPoint,
  3062. event: dropEvent.event,
  3063. });
  3064. });
  3065. }
  3066. /** Assigns the default input values based on a provided config object. */
  3067. _assignDefaults(config) {
  3068. const { lockAxis, dragStartDelay, constrainPosition, previewClass, boundaryElement, draggingDisabled, rootElementSelector, previewContainer, } = config;
  3069. this.disabled = draggingDisabled == null ? false : draggingDisabled;
  3070. this.dragStartDelay = dragStartDelay || 0;
  3071. if (lockAxis) {
  3072. this.lockAxis = lockAxis;
  3073. }
  3074. if (constrainPosition) {
  3075. this.constrainPosition = constrainPosition;
  3076. }
  3077. if (previewClass) {
  3078. this.previewClass = previewClass;
  3079. }
  3080. if (boundaryElement) {
  3081. this.boundaryElement = boundaryElement;
  3082. }
  3083. if (rootElementSelector) {
  3084. this.rootElementSelector = rootElementSelector;
  3085. }
  3086. if (previewContainer) {
  3087. this.previewContainer = previewContainer;
  3088. }
  3089. }
  3090. /** Sets up the listener that syncs the handles with the drag ref. */
  3091. _setupHandlesListener() {
  3092. // Listen for any newly-added handles.
  3093. this._handles.changes
  3094. .pipe(startWith(this._handles),
  3095. // Sync the new handles with the DragRef.
  3096. tap((handles) => {
  3097. const childHandleElements = handles
  3098. .filter(handle => handle._parentDrag === this)
  3099. .map(handle => handle.element);
  3100. // Usually handles are only allowed to be a descendant of the drag element, but if
  3101. // the consumer defined a different drag root, we should allow the drag element
  3102. // itself to be a handle too.
  3103. if (this._selfHandle && this.rootElementSelector) {
  3104. childHandleElements.push(this.element);
  3105. }
  3106. this._dragRef.withHandles(childHandleElements);
  3107. }),
  3108. // Listen if the state of any of the handles changes.
  3109. switchMap((handles) => {
  3110. return merge(...handles.map(item => {
  3111. return item._stateChanges.pipe(startWith(item));
  3112. }));
  3113. }), takeUntil(this._destroyed))
  3114. .subscribe(handleInstance => {
  3115. // Enabled/disable the handle that changed in the DragRef.
  3116. const dragRef = this._dragRef;
  3117. const handle = handleInstance.element.nativeElement;
  3118. handleInstance.disabled ? dragRef.disableHandle(handle) : dragRef.enableHandle(handle);
  3119. });
  3120. }
  3121. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDrag, deps: [{ token: i0.ElementRef }, { token: CDK_DROP_LIST, optional: true, skipSelf: true }, { token: DOCUMENT }, { token: i0.NgZone }, { token: i0.ViewContainerRef }, { token: CDK_DRAG_CONFIG, optional: true }, { token: i1$1.Directionality, optional: true }, { token: DragDrop }, { token: i0.ChangeDetectorRef }, { token: CDK_DRAG_HANDLE, optional: true, self: true }, { token: CDK_DRAG_PARENT, optional: true, skipSelf: true }], target: i0.ɵɵFactoryTarget.Directive }); }
  3122. static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.0", type: CdkDrag, isStandalone: true, selector: "[cdkDrag]", inputs: { data: ["cdkDragData", "data"], lockAxis: ["cdkDragLockAxis", "lockAxis"], rootElementSelector: ["cdkDragRootElement", "rootElementSelector"], boundaryElement: ["cdkDragBoundary", "boundaryElement"], dragStartDelay: ["cdkDragStartDelay", "dragStartDelay"], freeDragPosition: ["cdkDragFreeDragPosition", "freeDragPosition"], disabled: ["cdkDragDisabled", "disabled"], constrainPosition: ["cdkDragConstrainPosition", "constrainPosition"], previewClass: ["cdkDragPreviewClass", "previewClass"], previewContainer: ["cdkDragPreviewContainer", "previewContainer"] }, outputs: { started: "cdkDragStarted", released: "cdkDragReleased", ended: "cdkDragEnded", entered: "cdkDragEntered", exited: "cdkDragExited", dropped: "cdkDragDropped", moved: "cdkDragMoved" }, host: { properties: { "class.cdk-drag-disabled": "disabled", "class.cdk-drag-dragging": "_dragRef.isDragging()" }, classAttribute: "cdk-drag" }, providers: [{ provide: CDK_DRAG_PARENT, useExisting: CdkDrag }], queries: [{ propertyName: "_previewTemplate", first: true, predicate: CDK_DRAG_PREVIEW, descendants: true }, { propertyName: "_placeholderTemplate", first: true, predicate: CDK_DRAG_PLACEHOLDER, descendants: true }, { propertyName: "_handles", predicate: CDK_DRAG_HANDLE, descendants: true }], exportAs: ["cdkDrag"], usesOnChanges: true, ngImport: i0 }); }
  3123. }
  3124. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDrag, decorators: [{
  3125. type: Directive,
  3126. args: [{
  3127. selector: '[cdkDrag]',
  3128. exportAs: 'cdkDrag',
  3129. standalone: true,
  3130. host: {
  3131. 'class': DRAG_HOST_CLASS,
  3132. '[class.cdk-drag-disabled]': 'disabled',
  3133. '[class.cdk-drag-dragging]': '_dragRef.isDragging()',
  3134. },
  3135. providers: [{ provide: CDK_DRAG_PARENT, useExisting: CdkDrag }],
  3136. }]
  3137. }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: undefined, decorators: [{
  3138. type: Inject,
  3139. args: [CDK_DROP_LIST]
  3140. }, {
  3141. type: Optional
  3142. }, {
  3143. type: SkipSelf
  3144. }] }, { type: undefined, decorators: [{
  3145. type: Inject,
  3146. args: [DOCUMENT]
  3147. }] }, { type: i0.NgZone }, { type: i0.ViewContainerRef }, { type: undefined, decorators: [{
  3148. type: Optional
  3149. }, {
  3150. type: Inject,
  3151. args: [CDK_DRAG_CONFIG]
  3152. }] }, { type: i1$1.Directionality, decorators: [{
  3153. type: Optional
  3154. }] }, { type: DragDrop }, { type: i0.ChangeDetectorRef }, { type: CdkDragHandle, decorators: [{
  3155. type: Optional
  3156. }, {
  3157. type: Self
  3158. }, {
  3159. type: Inject,
  3160. args: [CDK_DRAG_HANDLE]
  3161. }] }, { type: CdkDrag, decorators: [{
  3162. type: Optional
  3163. }, {
  3164. type: SkipSelf
  3165. }, {
  3166. type: Inject,
  3167. args: [CDK_DRAG_PARENT]
  3168. }] }]; }, propDecorators: { _handles: [{
  3169. type: ContentChildren,
  3170. args: [CDK_DRAG_HANDLE, { descendants: true }]
  3171. }], _previewTemplate: [{
  3172. type: ContentChild,
  3173. args: [CDK_DRAG_PREVIEW]
  3174. }], _placeholderTemplate: [{
  3175. type: ContentChild,
  3176. args: [CDK_DRAG_PLACEHOLDER]
  3177. }], data: [{
  3178. type: Input,
  3179. args: ['cdkDragData']
  3180. }], lockAxis: [{
  3181. type: Input,
  3182. args: ['cdkDragLockAxis']
  3183. }], rootElementSelector: [{
  3184. type: Input,
  3185. args: ['cdkDragRootElement']
  3186. }], boundaryElement: [{
  3187. type: Input,
  3188. args: ['cdkDragBoundary']
  3189. }], dragStartDelay: [{
  3190. type: Input,
  3191. args: ['cdkDragStartDelay']
  3192. }], freeDragPosition: [{
  3193. type: Input,
  3194. args: ['cdkDragFreeDragPosition']
  3195. }], disabled: [{
  3196. type: Input,
  3197. args: ['cdkDragDisabled']
  3198. }], constrainPosition: [{
  3199. type: Input,
  3200. args: ['cdkDragConstrainPosition']
  3201. }], previewClass: [{
  3202. type: Input,
  3203. args: ['cdkDragPreviewClass']
  3204. }], previewContainer: [{
  3205. type: Input,
  3206. args: ['cdkDragPreviewContainer']
  3207. }], started: [{
  3208. type: Output,
  3209. args: ['cdkDragStarted']
  3210. }], released: [{
  3211. type: Output,
  3212. args: ['cdkDragReleased']
  3213. }], ended: [{
  3214. type: Output,
  3215. args: ['cdkDragEnded']
  3216. }], entered: [{
  3217. type: Output,
  3218. args: ['cdkDragEntered']
  3219. }], exited: [{
  3220. type: Output,
  3221. args: ['cdkDragExited']
  3222. }], dropped: [{
  3223. type: Output,
  3224. args: ['cdkDragDropped']
  3225. }], moved: [{
  3226. type: Output,
  3227. args: ['cdkDragMoved']
  3228. }] } });
  3229. /**
  3230. * Injection token that can be used to reference instances of `CdkDropListGroup`. It serves as
  3231. * alternative token to the actual `CdkDropListGroup` class which could cause unnecessary
  3232. * retention of the class and its directive metadata.
  3233. */
  3234. const CDK_DROP_LIST_GROUP = new InjectionToken('CdkDropListGroup');
  3235. /**
  3236. * Declaratively connects sibling `cdkDropList` instances together. All of the `cdkDropList`
  3237. * elements that are placed inside a `cdkDropListGroup` will be connected to each other
  3238. * automatically. Can be used as an alternative to the `cdkDropListConnectedTo` input
  3239. * from `cdkDropList`.
  3240. */
  3241. class CdkDropListGroup {
  3242. constructor() {
  3243. /** Drop lists registered inside the group. */
  3244. this._items = new Set();
  3245. this._disabled = false;
  3246. }
  3247. /** Whether starting a dragging sequence from inside this group is disabled. */
  3248. get disabled() {
  3249. return this._disabled;
  3250. }
  3251. set disabled(value) {
  3252. this._disabled = coerceBooleanProperty(value);
  3253. }
  3254. ngOnDestroy() {
  3255. this._items.clear();
  3256. }
  3257. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDropListGroup, deps: [], target: i0.ɵɵFactoryTarget.Directive }); }
  3258. static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.0", type: CdkDropListGroup, isStandalone: true, selector: "[cdkDropListGroup]", inputs: { disabled: ["cdkDropListGroupDisabled", "disabled"] }, providers: [{ provide: CDK_DROP_LIST_GROUP, useExisting: CdkDropListGroup }], exportAs: ["cdkDropListGroup"], ngImport: i0 }); }
  3259. }
  3260. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDropListGroup, decorators: [{
  3261. type: Directive,
  3262. args: [{
  3263. selector: '[cdkDropListGroup]',
  3264. exportAs: 'cdkDropListGroup',
  3265. standalone: true,
  3266. providers: [{ provide: CDK_DROP_LIST_GROUP, useExisting: CdkDropListGroup }],
  3267. }]
  3268. }], propDecorators: { disabled: [{
  3269. type: Input,
  3270. args: ['cdkDropListGroupDisabled']
  3271. }] } });
  3272. /** Counter used to generate unique ids for drop zones. */
  3273. let _uniqueIdCounter = 0;
  3274. /** Container that wraps a set of draggable items. */
  3275. class CdkDropList {
  3276. /** Keeps track of the drop lists that are currently on the page. */
  3277. static { this._dropLists = []; }
  3278. /** Whether starting a dragging sequence from this container is disabled. */
  3279. get disabled() {
  3280. return this._disabled || (!!this._group && this._group.disabled);
  3281. }
  3282. set disabled(value) {
  3283. // Usually we sync the directive and ref state right before dragging starts, in order to have
  3284. // a single point of failure and to avoid having to use setters for everything. `disabled` is
  3285. // a special case, because it can prevent the `beforeStarted` event from firing, which can lock
  3286. // the user in a disabled state, so we also need to sync it as it's being set.
  3287. this._dropListRef.disabled = this._disabled = coerceBooleanProperty(value);
  3288. }
  3289. constructor(
  3290. /** Element that the drop list is attached to. */
  3291. element, dragDrop, _changeDetectorRef, _scrollDispatcher, _dir, _group, config) {
  3292. this.element = element;
  3293. this._changeDetectorRef = _changeDetectorRef;
  3294. this._scrollDispatcher = _scrollDispatcher;
  3295. this._dir = _dir;
  3296. this._group = _group;
  3297. /** Emits when the list has been destroyed. */
  3298. this._destroyed = new Subject();
  3299. /**
  3300. * Other draggable containers that this container is connected to and into which the
  3301. * container's items can be transferred. Can either be references to other drop containers,
  3302. * or their unique IDs.
  3303. */
  3304. this.connectedTo = [];
  3305. /**
  3306. * Unique ID for the drop zone. Can be used as a reference
  3307. * in the `connectedTo` of another `CdkDropList`.
  3308. */
  3309. this.id = `cdk-drop-list-${_uniqueIdCounter++}`;
  3310. /**
  3311. * Function that is used to determine whether an item
  3312. * is allowed to be moved into a drop container.
  3313. */
  3314. this.enterPredicate = () => true;
  3315. /** Functions that is used to determine whether an item can be sorted into a particular index. */
  3316. this.sortPredicate = () => true;
  3317. /** Emits when the user drops an item inside the container. */
  3318. this.dropped = new EventEmitter();
  3319. /**
  3320. * Emits when the user has moved a new drag item into this container.
  3321. */
  3322. this.entered = new EventEmitter();
  3323. /**
  3324. * Emits when the user removes an item from the container
  3325. * by dragging it into another container.
  3326. */
  3327. this.exited = new EventEmitter();
  3328. /** Emits as the user is swapping items while actively dragging. */
  3329. this.sorted = new EventEmitter();
  3330. /**
  3331. * Keeps track of the items that are registered with this container. Historically we used to
  3332. * do this with a `ContentChildren` query, however queries don't handle transplanted views very
  3333. * well which means that we can't handle cases like dragging the headers of a `mat-table`
  3334. * correctly. What we do instead is to have the items register themselves with the container
  3335. * and then we sort them based on their position in the DOM.
  3336. */
  3337. this._unsortedItems = new Set();
  3338. if (typeof ngDevMode === 'undefined' || ngDevMode) {
  3339. assertElementNode(element.nativeElement, 'cdkDropList');
  3340. }
  3341. this._dropListRef = dragDrop.createDropList(element);
  3342. this._dropListRef.data = this;
  3343. if (config) {
  3344. this._assignDefaults(config);
  3345. }
  3346. this._dropListRef.enterPredicate = (drag, drop) => {
  3347. return this.enterPredicate(drag.data, drop.data);
  3348. };
  3349. this._dropListRef.sortPredicate = (index, drag, drop) => {
  3350. return this.sortPredicate(index, drag.data, drop.data);
  3351. };
  3352. this._setupInputSyncSubscription(this._dropListRef);
  3353. this._handleEvents(this._dropListRef);
  3354. CdkDropList._dropLists.push(this);
  3355. if (_group) {
  3356. _group._items.add(this);
  3357. }
  3358. }
  3359. /** Registers an items with the drop list. */
  3360. addItem(item) {
  3361. this._unsortedItems.add(item);
  3362. if (this._dropListRef.isDragging()) {
  3363. this._syncItemsWithRef();
  3364. }
  3365. }
  3366. /** Removes an item from the drop list. */
  3367. removeItem(item) {
  3368. this._unsortedItems.delete(item);
  3369. if (this._dropListRef.isDragging()) {
  3370. this._syncItemsWithRef();
  3371. }
  3372. }
  3373. /** Gets the registered items in the list, sorted by their position in the DOM. */
  3374. getSortedItems() {
  3375. return Array.from(this._unsortedItems).sort((a, b) => {
  3376. const documentPosition = a._dragRef
  3377. .getVisibleElement()
  3378. .compareDocumentPosition(b._dragRef.getVisibleElement());
  3379. // `compareDocumentPosition` returns a bitmask so we have to use a bitwise operator.
  3380. // https://developer.mozilla.org/en-US/docs/Web/API/Node/compareDocumentPosition
  3381. // tslint:disable-next-line:no-bitwise
  3382. return documentPosition & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : 1;
  3383. });
  3384. }
  3385. ngOnDestroy() {
  3386. const index = CdkDropList._dropLists.indexOf(this);
  3387. if (index > -1) {
  3388. CdkDropList._dropLists.splice(index, 1);
  3389. }
  3390. if (this._group) {
  3391. this._group._items.delete(this);
  3392. }
  3393. this._unsortedItems.clear();
  3394. this._dropListRef.dispose();
  3395. this._destroyed.next();
  3396. this._destroyed.complete();
  3397. }
  3398. /** Syncs the inputs of the CdkDropList with the options of the underlying DropListRef. */
  3399. _setupInputSyncSubscription(ref) {
  3400. if (this._dir) {
  3401. this._dir.change
  3402. .pipe(startWith(this._dir.value), takeUntil(this._destroyed))
  3403. .subscribe(value => ref.withDirection(value));
  3404. }
  3405. ref.beforeStarted.subscribe(() => {
  3406. const siblings = coerceArray(this.connectedTo).map(drop => {
  3407. if (typeof drop === 'string') {
  3408. const correspondingDropList = CdkDropList._dropLists.find(list => list.id === drop);
  3409. if (!correspondingDropList && (typeof ngDevMode === 'undefined' || ngDevMode)) {
  3410. console.warn(`CdkDropList could not find connected drop list with id "${drop}"`);
  3411. }
  3412. return correspondingDropList;
  3413. }
  3414. return drop;
  3415. });
  3416. if (this._group) {
  3417. this._group._items.forEach(drop => {
  3418. if (siblings.indexOf(drop) === -1) {
  3419. siblings.push(drop);
  3420. }
  3421. });
  3422. }
  3423. // Note that we resolve the scrollable parents here so that we delay the resolution
  3424. // as long as possible, ensuring that the element is in its final place in the DOM.
  3425. if (!this._scrollableParentsResolved) {
  3426. const scrollableParents = this._scrollDispatcher
  3427. .getAncestorScrollContainers(this.element)
  3428. .map(scrollable => scrollable.getElementRef().nativeElement);
  3429. this._dropListRef.withScrollableParents(scrollableParents);
  3430. // Only do this once since it involves traversing the DOM and the parents
  3431. // shouldn't be able to change without the drop list being destroyed.
  3432. this._scrollableParentsResolved = true;
  3433. }
  3434. ref.disabled = this.disabled;
  3435. ref.lockAxis = this.lockAxis;
  3436. ref.sortingDisabled = coerceBooleanProperty(this.sortingDisabled);
  3437. ref.autoScrollDisabled = coerceBooleanProperty(this.autoScrollDisabled);
  3438. ref.autoScrollStep = coerceNumberProperty(this.autoScrollStep, 2);
  3439. ref
  3440. .connectedTo(siblings.filter(drop => drop && drop !== this).map(list => list._dropListRef))
  3441. .withOrientation(this.orientation);
  3442. });
  3443. }
  3444. /** Handles events from the underlying DropListRef. */
  3445. _handleEvents(ref) {
  3446. ref.beforeStarted.subscribe(() => {
  3447. this._syncItemsWithRef();
  3448. this._changeDetectorRef.markForCheck();
  3449. });
  3450. ref.entered.subscribe(event => {
  3451. this.entered.emit({
  3452. container: this,
  3453. item: event.item.data,
  3454. currentIndex: event.currentIndex,
  3455. });
  3456. });
  3457. ref.exited.subscribe(event => {
  3458. this.exited.emit({
  3459. container: this,
  3460. item: event.item.data,
  3461. });
  3462. this._changeDetectorRef.markForCheck();
  3463. });
  3464. ref.sorted.subscribe(event => {
  3465. this.sorted.emit({
  3466. previousIndex: event.previousIndex,
  3467. currentIndex: event.currentIndex,
  3468. container: this,
  3469. item: event.item.data,
  3470. });
  3471. });
  3472. ref.dropped.subscribe(dropEvent => {
  3473. this.dropped.emit({
  3474. previousIndex: dropEvent.previousIndex,
  3475. currentIndex: dropEvent.currentIndex,
  3476. previousContainer: dropEvent.previousContainer.data,
  3477. container: dropEvent.container.data,
  3478. item: dropEvent.item.data,
  3479. isPointerOverContainer: dropEvent.isPointerOverContainer,
  3480. distance: dropEvent.distance,
  3481. dropPoint: dropEvent.dropPoint,
  3482. event: dropEvent.event,
  3483. });
  3484. // Mark for check since all of these events run outside of change
  3485. // detection and we're not guaranteed for something else to have triggered it.
  3486. this._changeDetectorRef.markForCheck();
  3487. });
  3488. merge(ref.receivingStarted, ref.receivingStopped).subscribe(() => this._changeDetectorRef.markForCheck());
  3489. }
  3490. /** Assigns the default input values based on a provided config object. */
  3491. _assignDefaults(config) {
  3492. const { lockAxis, draggingDisabled, sortingDisabled, listAutoScrollDisabled, listOrientation } = config;
  3493. this.disabled = draggingDisabled == null ? false : draggingDisabled;
  3494. this.sortingDisabled = sortingDisabled == null ? false : sortingDisabled;
  3495. this.autoScrollDisabled = listAutoScrollDisabled == null ? false : listAutoScrollDisabled;
  3496. this.orientation = listOrientation || 'vertical';
  3497. if (lockAxis) {
  3498. this.lockAxis = lockAxis;
  3499. }
  3500. }
  3501. /** Syncs up the registered drag items with underlying drop list ref. */
  3502. _syncItemsWithRef() {
  3503. this._dropListRef.withItems(this.getSortedItems().map(item => item._dragRef));
  3504. }
  3505. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDropList, deps: [{ token: i0.ElementRef }, { token: DragDrop }, { token: i0.ChangeDetectorRef }, { token: i1.ScrollDispatcher }, { token: i1$1.Directionality, optional: true }, { token: CDK_DROP_LIST_GROUP, optional: true, skipSelf: true }, { token: CDK_DRAG_CONFIG, optional: true }], target: i0.ɵɵFactoryTarget.Directive }); }
  3506. static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.0.0", type: CdkDropList, isStandalone: true, selector: "[cdkDropList], cdk-drop-list", inputs: { connectedTo: ["cdkDropListConnectedTo", "connectedTo"], data: ["cdkDropListData", "data"], orientation: ["cdkDropListOrientation", "orientation"], id: "id", lockAxis: ["cdkDropListLockAxis", "lockAxis"], disabled: ["cdkDropListDisabled", "disabled"], sortingDisabled: ["cdkDropListSortingDisabled", "sortingDisabled"], enterPredicate: ["cdkDropListEnterPredicate", "enterPredicate"], sortPredicate: ["cdkDropListSortPredicate", "sortPredicate"], autoScrollDisabled: ["cdkDropListAutoScrollDisabled", "autoScrollDisabled"], autoScrollStep: ["cdkDropListAutoScrollStep", "autoScrollStep"] }, outputs: { dropped: "cdkDropListDropped", entered: "cdkDropListEntered", exited: "cdkDropListExited", sorted: "cdkDropListSorted" }, host: { properties: { "attr.id": "id", "class.cdk-drop-list-disabled": "disabled", "class.cdk-drop-list-dragging": "_dropListRef.isDragging()", "class.cdk-drop-list-receiving": "_dropListRef.isReceiving()" }, classAttribute: "cdk-drop-list" }, providers: [
  3507. // Prevent child drop lists from picking up the same group as their parent.
  3508. { provide: CDK_DROP_LIST_GROUP, useValue: undefined },
  3509. { provide: CDK_DROP_LIST, useExisting: CdkDropList },
  3510. ], exportAs: ["cdkDropList"], ngImport: i0 }); }
  3511. }
  3512. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: CdkDropList, decorators: [{
  3513. type: Directive,
  3514. args: [{
  3515. selector: '[cdkDropList], cdk-drop-list',
  3516. exportAs: 'cdkDropList',
  3517. standalone: true,
  3518. providers: [
  3519. // Prevent child drop lists from picking up the same group as their parent.
  3520. { provide: CDK_DROP_LIST_GROUP, useValue: undefined },
  3521. { provide: CDK_DROP_LIST, useExisting: CdkDropList },
  3522. ],
  3523. host: {
  3524. 'class': 'cdk-drop-list',
  3525. '[attr.id]': 'id',
  3526. '[class.cdk-drop-list-disabled]': 'disabled',
  3527. '[class.cdk-drop-list-dragging]': '_dropListRef.isDragging()',
  3528. '[class.cdk-drop-list-receiving]': '_dropListRef.isReceiving()',
  3529. },
  3530. }]
  3531. }], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: DragDrop }, { type: i0.ChangeDetectorRef }, { type: i1.ScrollDispatcher }, { type: i1$1.Directionality, decorators: [{
  3532. type: Optional
  3533. }] }, { type: CdkDropListGroup, decorators: [{
  3534. type: Optional
  3535. }, {
  3536. type: Inject,
  3537. args: [CDK_DROP_LIST_GROUP]
  3538. }, {
  3539. type: SkipSelf
  3540. }] }, { type: undefined, decorators: [{
  3541. type: Optional
  3542. }, {
  3543. type: Inject,
  3544. args: [CDK_DRAG_CONFIG]
  3545. }] }]; }, propDecorators: { connectedTo: [{
  3546. type: Input,
  3547. args: ['cdkDropListConnectedTo']
  3548. }], data: [{
  3549. type: Input,
  3550. args: ['cdkDropListData']
  3551. }], orientation: [{
  3552. type: Input,
  3553. args: ['cdkDropListOrientation']
  3554. }], id: [{
  3555. type: Input
  3556. }], lockAxis: [{
  3557. type: Input,
  3558. args: ['cdkDropListLockAxis']
  3559. }], disabled: [{
  3560. type: Input,
  3561. args: ['cdkDropListDisabled']
  3562. }], sortingDisabled: [{
  3563. type: Input,
  3564. args: ['cdkDropListSortingDisabled']
  3565. }], enterPredicate: [{
  3566. type: Input,
  3567. args: ['cdkDropListEnterPredicate']
  3568. }], sortPredicate: [{
  3569. type: Input,
  3570. args: ['cdkDropListSortPredicate']
  3571. }], autoScrollDisabled: [{
  3572. type: Input,
  3573. args: ['cdkDropListAutoScrollDisabled']
  3574. }], autoScrollStep: [{
  3575. type: Input,
  3576. args: ['cdkDropListAutoScrollStep']
  3577. }], dropped: [{
  3578. type: Output,
  3579. args: ['cdkDropListDropped']
  3580. }], entered: [{
  3581. type: Output,
  3582. args: ['cdkDropListEntered']
  3583. }], exited: [{
  3584. type: Output,
  3585. args: ['cdkDropListExited']
  3586. }], sorted: [{
  3587. type: Output,
  3588. args: ['cdkDropListSorted']
  3589. }] } });
  3590. const DRAG_DROP_DIRECTIVES = [
  3591. CdkDropList,
  3592. CdkDropListGroup,
  3593. CdkDrag,
  3594. CdkDragHandle,
  3595. CdkDragPreview,
  3596. CdkDragPlaceholder,
  3597. ];
  3598. class DragDropModule {
  3599. static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: DragDropModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
  3600. static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.0.0", ngImport: i0, type: DragDropModule, imports: [CdkDropList,
  3601. CdkDropListGroup,
  3602. CdkDrag,
  3603. CdkDragHandle,
  3604. CdkDragPreview,
  3605. CdkDragPlaceholder], exports: [CdkScrollableModule, CdkDropList,
  3606. CdkDropListGroup,
  3607. CdkDrag,
  3608. CdkDragHandle,
  3609. CdkDragPreview,
  3610. CdkDragPlaceholder] }); }
  3611. static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: DragDropModule, providers: [DragDrop], imports: [CdkScrollableModule] }); }
  3612. }
  3613. i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.0.0", ngImport: i0, type: DragDropModule, decorators: [{
  3614. type: NgModule,
  3615. args: [{
  3616. imports: DRAG_DROP_DIRECTIVES,
  3617. exports: [CdkScrollableModule, ...DRAG_DROP_DIRECTIVES],
  3618. providers: [DragDrop],
  3619. }]
  3620. }] });
  3621. /**
  3622. * Generated bundle index. Do not edit.
  3623. */
  3624. export { CDK_DRAG_CONFIG, CDK_DRAG_HANDLE, CDK_DRAG_PARENT, CDK_DRAG_PLACEHOLDER, CDK_DRAG_PREVIEW, CDK_DROP_LIST, CDK_DROP_LIST_GROUP, CdkDrag, CdkDragHandle, CdkDragPlaceholder, CdkDragPreview, CdkDropList, CdkDropListGroup, DragDrop, DragDropModule, DragDropRegistry, DragRef, DropListRef, copyArrayItem, moveItemInArray, transferArrayItem };
  3625. //# sourceMappingURL=drag-drop.mjs.map