ion-reorder-group.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306
  1. /*!
  2. * (C) Ionic http://ionicframework.com - MIT License
  3. */
  4. import { proxyCustomElement, HTMLElement, createEvent, h, Host } from '@stencil/core/internal/client';
  5. import { a as findClosestIonContent, g as getScrollElement } from './index8.js';
  6. import { r as raf } from './helpers.js';
  7. import { a as hapticSelectionStart, b as hapticSelectionChanged, h as hapticSelectionEnd } from './haptic.js';
  8. import { b as getIonMode } from './ionic-global.js';
  9. const reorderGroupCss = ".reorder-list-active>*{display:block;-webkit-transition:-webkit-transform 300ms;transition:-webkit-transform 300ms;transition:transform 300ms;transition:transform 300ms, -webkit-transform 300ms;will-change:transform}.reorder-enabled{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.reorder-enabled ion-reorder{display:block;cursor:-webkit-grab;cursor:grab;pointer-events:all;-ms-touch-action:none;touch-action:none}.reorder-selected,.reorder-selected ion-reorder{cursor:-webkit-grabbing;cursor:grabbing}.reorder-selected{position:relative;-webkit-transition:none !important;transition:none !important;-webkit-box-shadow:0 0 10px rgba(0, 0, 0, 0.4);box-shadow:0 0 10px rgba(0, 0, 0, 0.4);opacity:0.8;z-index:100}.reorder-visible ion-reorder .reorder-icon{-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0)}";
  10. const IonReorderGroupStyle0 = reorderGroupCss;
  11. const ReorderGroup = /*@__PURE__*/ proxyCustomElement(class ReorderGroup extends HTMLElement {
  12. constructor() {
  13. super();
  14. this.__registerHost();
  15. this.ionItemReorder = createEvent(this, "ionItemReorder", 7);
  16. this.lastToIndex = -1;
  17. this.cachedHeights = [];
  18. this.scrollElTop = 0;
  19. this.scrollElBottom = 0;
  20. this.scrollElInitial = 0;
  21. this.containerTop = 0;
  22. this.containerBottom = 0;
  23. this.state = 0 /* ReorderGroupState.Idle */;
  24. this.disabled = true;
  25. }
  26. disabledChanged() {
  27. if (this.gesture) {
  28. this.gesture.enable(!this.disabled);
  29. }
  30. }
  31. async connectedCallback() {
  32. const contentEl = findClosestIonContent(this.el);
  33. if (contentEl) {
  34. this.scrollEl = await getScrollElement(contentEl);
  35. }
  36. this.gesture = (await import('./index3.js')).createGesture({
  37. el: this.el,
  38. gestureName: 'reorder',
  39. gesturePriority: 110,
  40. threshold: 0,
  41. direction: 'y',
  42. passive: false,
  43. canStart: (detail) => this.canStart(detail),
  44. onStart: (ev) => this.onStart(ev),
  45. onMove: (ev) => this.onMove(ev),
  46. onEnd: () => this.onEnd(),
  47. });
  48. this.disabledChanged();
  49. }
  50. disconnectedCallback() {
  51. this.onEnd();
  52. if (this.gesture) {
  53. this.gesture.destroy();
  54. this.gesture = undefined;
  55. }
  56. }
  57. /**
  58. * Completes the reorder operation. Must be called by the `ionItemReorder` event.
  59. *
  60. * If a list of items is passed, the list will be reordered and returned in the
  61. * proper order.
  62. *
  63. * If no parameters are passed or if `true` is passed in, the reorder will complete
  64. * and the item will remain in the position it was dragged to. If `false` is passed,
  65. * the reorder will complete and the item will bounce back to its original position.
  66. *
  67. * @param listOrReorder A list of items to be sorted and returned in the new order or a
  68. * boolean of whether or not the reorder should reposition the item.
  69. */
  70. complete(listOrReorder) {
  71. return Promise.resolve(this.completeReorder(listOrReorder));
  72. }
  73. canStart(ev) {
  74. if (this.selectedItemEl || this.state !== 0 /* ReorderGroupState.Idle */) {
  75. return false;
  76. }
  77. const target = ev.event.target;
  78. const reorderEl = target.closest('ion-reorder');
  79. if (!reorderEl) {
  80. return false;
  81. }
  82. const item = findReorderItem(reorderEl, this.el);
  83. if (!item) {
  84. return false;
  85. }
  86. ev.data = item;
  87. return true;
  88. }
  89. onStart(ev) {
  90. ev.event.preventDefault();
  91. const item = (this.selectedItemEl = ev.data);
  92. const heights = this.cachedHeights;
  93. heights.length = 0;
  94. const el = this.el;
  95. const children = el.children;
  96. if (!children || children.length === 0) {
  97. return;
  98. }
  99. let sum = 0;
  100. for (let i = 0; i < children.length; i++) {
  101. const child = children[i];
  102. sum += child.offsetHeight;
  103. heights.push(sum);
  104. child.$ionIndex = i;
  105. }
  106. const box = el.getBoundingClientRect();
  107. this.containerTop = box.top;
  108. this.containerBottom = box.bottom;
  109. if (this.scrollEl) {
  110. const scrollBox = this.scrollEl.getBoundingClientRect();
  111. this.scrollElInitial = this.scrollEl.scrollTop;
  112. this.scrollElTop = scrollBox.top + AUTO_SCROLL_MARGIN;
  113. this.scrollElBottom = scrollBox.bottom - AUTO_SCROLL_MARGIN;
  114. }
  115. else {
  116. this.scrollElInitial = 0;
  117. this.scrollElTop = 0;
  118. this.scrollElBottom = 0;
  119. }
  120. this.lastToIndex = indexForItem(item);
  121. this.selectedItemHeight = item.offsetHeight;
  122. this.state = 1 /* ReorderGroupState.Active */;
  123. item.classList.add(ITEM_REORDER_SELECTED);
  124. hapticSelectionStart();
  125. }
  126. onMove(ev) {
  127. const selectedItem = this.selectedItemEl;
  128. if (!selectedItem) {
  129. return;
  130. }
  131. // Scroll if we reach the scroll margins
  132. const scroll = this.autoscroll(ev.currentY);
  133. // // Get coordinate
  134. const top = this.containerTop - scroll;
  135. const bottom = this.containerBottom - scroll;
  136. const currentY = Math.max(top, Math.min(ev.currentY, bottom));
  137. const deltaY = scroll + currentY - ev.startY;
  138. const normalizedY = currentY - top;
  139. const toIndex = this.itemIndexForTop(normalizedY);
  140. if (toIndex !== this.lastToIndex) {
  141. const fromIndex = indexForItem(selectedItem);
  142. this.lastToIndex = toIndex;
  143. hapticSelectionChanged();
  144. this.reorderMove(fromIndex, toIndex);
  145. }
  146. // Update selected item position
  147. selectedItem.style.transform = `translateY(${deltaY}px)`;
  148. }
  149. onEnd() {
  150. const selectedItemEl = this.selectedItemEl;
  151. this.state = 2 /* ReorderGroupState.Complete */;
  152. if (!selectedItemEl) {
  153. this.state = 0 /* ReorderGroupState.Idle */;
  154. return;
  155. }
  156. const toIndex = this.lastToIndex;
  157. const fromIndex = indexForItem(selectedItemEl);
  158. if (toIndex === fromIndex) {
  159. this.completeReorder();
  160. }
  161. else {
  162. this.ionItemReorder.emit({
  163. from: fromIndex,
  164. to: toIndex,
  165. complete: this.completeReorder.bind(this),
  166. });
  167. }
  168. hapticSelectionEnd();
  169. }
  170. completeReorder(listOrReorder) {
  171. const selectedItemEl = this.selectedItemEl;
  172. if (selectedItemEl && this.state === 2 /* ReorderGroupState.Complete */) {
  173. const children = this.el.children;
  174. const len = children.length;
  175. const toIndex = this.lastToIndex;
  176. const fromIndex = indexForItem(selectedItemEl);
  177. /**
  178. * insertBefore and setting the transform
  179. * needs to happen in the same frame otherwise
  180. * there will be a duplicate transition. This primarily
  181. * impacts Firefox where insertBefore and transform operations
  182. * are happening in two separate frames.
  183. */
  184. raf(() => {
  185. if (toIndex !== fromIndex && (listOrReorder === undefined || listOrReorder === true)) {
  186. const ref = fromIndex < toIndex ? children[toIndex + 1] : children[toIndex];
  187. this.el.insertBefore(selectedItemEl, ref);
  188. }
  189. for (let i = 0; i < len; i++) {
  190. children[i].style['transform'] = '';
  191. }
  192. });
  193. if (Array.isArray(listOrReorder)) {
  194. listOrReorder = reorderArray(listOrReorder, fromIndex, toIndex);
  195. }
  196. selectedItemEl.style.transition = '';
  197. selectedItemEl.classList.remove(ITEM_REORDER_SELECTED);
  198. this.selectedItemEl = undefined;
  199. this.state = 0 /* ReorderGroupState.Idle */;
  200. }
  201. return listOrReorder;
  202. }
  203. itemIndexForTop(deltaY) {
  204. const heights = this.cachedHeights;
  205. for (let i = 0; i < heights.length; i++) {
  206. if (heights[i] > deltaY) {
  207. return i;
  208. }
  209. }
  210. return heights.length - 1;
  211. }
  212. /********* DOM WRITE ********* */
  213. reorderMove(fromIndex, toIndex) {
  214. const itemHeight = this.selectedItemHeight;
  215. const children = this.el.children;
  216. for (let i = 0; i < children.length; i++) {
  217. const style = children[i].style;
  218. let value = '';
  219. if (i > fromIndex && i <= toIndex) {
  220. value = `translateY(${-itemHeight}px)`;
  221. }
  222. else if (i < fromIndex && i >= toIndex) {
  223. value = `translateY(${itemHeight}px)`;
  224. }
  225. style['transform'] = value;
  226. }
  227. }
  228. autoscroll(posY) {
  229. if (!this.scrollEl) {
  230. return 0;
  231. }
  232. let amount = 0;
  233. if (posY < this.scrollElTop) {
  234. amount = -SCROLL_JUMP;
  235. }
  236. else if (posY > this.scrollElBottom) {
  237. amount = SCROLL_JUMP;
  238. }
  239. if (amount !== 0) {
  240. this.scrollEl.scrollBy(0, amount);
  241. }
  242. return this.scrollEl.scrollTop - this.scrollElInitial;
  243. }
  244. render() {
  245. const mode = getIonMode(this);
  246. return (h(Host, { key: '6ca009dd65302a914d459aec638e62977440db20', class: {
  247. [mode]: true,
  248. 'reorder-enabled': !this.disabled,
  249. 'reorder-list-active': this.state !== 0 /* ReorderGroupState.Idle */,
  250. } }));
  251. }
  252. get el() { return this; }
  253. static get watchers() { return {
  254. "disabled": ["disabledChanged"]
  255. }; }
  256. static get style() { return IonReorderGroupStyle0; }
  257. }, [0, "ion-reorder-group", {
  258. "disabled": [4],
  259. "state": [32],
  260. "complete": [64]
  261. }, undefined, {
  262. "disabled": ["disabledChanged"]
  263. }]);
  264. const indexForItem = (element) => {
  265. return element['$ionIndex'];
  266. };
  267. const findReorderItem = (node, container) => {
  268. let parent;
  269. while (node) {
  270. parent = node.parentElement;
  271. if (parent === container) {
  272. return node;
  273. }
  274. node = parent;
  275. }
  276. return undefined;
  277. };
  278. const AUTO_SCROLL_MARGIN = 60;
  279. const SCROLL_JUMP = 10;
  280. const ITEM_REORDER_SELECTED = 'reorder-selected';
  281. const reorderArray = (array, from, to) => {
  282. const element = array[from];
  283. array.splice(from, 1);
  284. array.splice(to, 0, element);
  285. return array.slice();
  286. };
  287. function defineCustomElement$1() {
  288. if (typeof customElements === "undefined") {
  289. return;
  290. }
  291. const components = ["ion-reorder-group"];
  292. components.forEach(tagName => { switch (tagName) {
  293. case "ion-reorder-group":
  294. if (!customElements.get(tagName)) {
  295. customElements.define(tagName, ReorderGroup);
  296. }
  297. break;
  298. } });
  299. }
  300. const IonReorderGroup = ReorderGroup;
  301. const defineCustomElement = defineCustomElement$1;
  302. export { IonReorderGroup, defineCustomElement };