bm-core-ui 2.11.9-beta.2 → 2.12.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +1 -1
  2. package/build/@types/index.d.ts +2268 -71
  3. package/build/BMCollectionView/BMCollectionView.js +1070 -235
  4. package/build/BMCollectionView/BMCollectionViewFlowLayout.js +11 -4
  5. package/build/BMCollectionView/BMCollectionViewLayout.js +19 -0
  6. package/build/BMCoreUI.css +213 -44
  7. package/build/BMView/BMDragSession.js +1243 -0
  8. package/build/BMView/BMDragSessionActions.js +121 -0
  9. package/build/BMView/BMDragSessionPreview.js +1650 -0
  10. package/build/BMView/BMLayoutConstraint_v2.5.js +14 -59
  11. package/build/BMView/BMMenu.js +50 -20
  12. package/build/BMView/BMView_v2.5.js +34 -3
  13. package/build/BMWindow/BMPopover/BMPopover.js +35 -20
  14. package/build/BMWindow/BMToolWindow.js +29 -1
  15. package/build/BMWindow/BMWindow.js +128 -65
  16. package/build/images/AlignBottom.png +0 -0
  17. package/build/images/AlignCenterX.png +0 -0
  18. package/build/images/AlignCenterY.png +0 -0
  19. package/build/images/AlignLeading.png +0 -0
  20. package/build/images/AlignTop.png +0 -0
  21. package/build/images/AlignTrailing.png +0 -0
  22. package/build/images/AllConstraints.png +0 -0
  23. package/build/images/BottomConstraint.png +0 -0
  24. package/build/images/CenterXConstraint.png +0 -0
  25. package/build/images/CenterYConstraint.png +0 -0
  26. package/build/images/CoreUI2.png +0 -0
  27. package/build/images/CoreUI2@2x.png +0 -0
  28. package/build/images/Desktop.png +0 -0
  29. package/build/images/DesktopMini.png +0 -0
  30. package/build/images/EqualHeight.png +0 -0
  31. package/build/images/EqualHorizontalSpacing.png +0 -0
  32. package/build/images/EqualHorizontalSpacingInSuperview.png +0 -0
  33. package/build/images/EqualVerticalSpacing.png +0 -0
  34. package/build/images/EqualVerticalSpacingInSuperview.png +0 -0
  35. package/build/images/EqualWidth.png +0 -0
  36. package/build/images/HeightConstraint.png +0 -0
  37. package/build/images/InactiveConstraints.png +0 -0
  38. package/build/images/Layout.png +0 -0
  39. package/build/images/LayoutVariables.png +0 -0
  40. package/build/images/LeftConstraint.png +0 -0
  41. package/build/images/OwnConstraints.png +0 -0
  42. package/build/images/Phone.png +0 -0
  43. package/build/images/PhoneLandscape.png +0 -0
  44. package/build/images/PhoneLandscapeMini.png +0 -0
  45. package/build/images/PhoneMini.png +0 -0
  46. package/build/images/PhonePortrait.png +0 -0
  47. package/build/images/PhonePortraitMini.png +0 -0
  48. package/build/images/Properties.png +0 -0
  49. package/build/images/RightConstraint.png +0 -0
  50. package/build/images/SubviewConstraints.png +0 -0
  51. package/build/images/Tablet.png +0 -0
  52. package/build/images/TabletLandscape.png +0 -0
  53. package/build/images/TabletLandscapeMini.png +0 -0
  54. package/build/images/TabletMini.png +0 -0
  55. package/build/images/TabletPortrait.png +0 -0
  56. package/build/images/TabletPortraitMini.png +0 -0
  57. package/build/images/TopConstraint.png +0 -0
  58. package/build/images/WidthConstraint.png +0 -0
  59. package/build/index.js +4 -0
  60. package/lib/@types/BMCoreUI.min.d.ts +2268 -71
  61. package/lib/BMCoreUI.min.js +1 -1
  62. package/package.json +3 -3
@@ -5,8 +5,8 @@ import {BMInset} from '../Core/BMInset'
5
5
  import {BMPointMake} from '../Core/BMPoint'
6
6
  import {BMSizeMake} from '../Core/BMSize'
7
7
  import {BMRect, BMRectMake, BMRectMakeWithNodeFrame, BMRectMakeWithOrigin, BMRectByInterpolatingRect} from '../Core/BMRect'
8
- import {BMIndexPathNone} from '../Core/BMIndexPath'
9
- import {BMAnimateWithBlock, BMAnimationContextBeginStatic, BMAnimationContextGetCurrent, BMAnimationContextAddCompletionHandler, BMAnimationApply, __BMVelocityAnimate, BMHook} from '../Core/BMAnimationContext'
8
+ import {BMIndexPathMakeWithRow, BMIndexPathNone} from '../Core/BMIndexPath'
9
+ import {BMAnimateWithBlock, BMAnimationContextBeginStatic, BMAnimationContextGetCurrent, BMAnimationContextAddCompletionHandler, BMAnimationApply, __BMVelocityAnimate, BMHook, BMAnimationBeginWithDuration} from '../Core/BMAnimationContext'
10
10
  import {BMView, BMViewLayoutQueue} from '../BMView/BMView_v2.5'
11
11
  import {BMCollectionViewLayoutAttributesMakeForCellAtIndexPath, BMCollectionViewLayoutAttributesType, BMCollectionViewLayoutAttributesStyleDefaults, _BMCollectionViewTransitionLayoutAttributesMakeWithSourceAttributes} from './BMCollectionViewLayoutAttributes'
12
12
  import {BMJQueryShim, BMCollectionViewCell, BMCollectionViewCellReuseState} from './BMCollectionViewCell'
@@ -16,6 +16,9 @@ import {IScroll} from '../iScroll/iscroll-probe'
16
16
  import { BMLayoutAttribute } from '../BMView/BMLayoutConstraint_v2.5'
17
17
  import { BMMenuSourceNodeCSSClass } from '../BMView/BMMenu'
18
18
  import { BMKeyboardShortcut, BMKeyboardShortcutModifier } from '../BMWindow/BMKeyboardShortcut'
19
+ import { BMDragSession, BMDragSessionAction, BMDragTransferKind, BMDropSessionAction } from '../BMView/BMDragSession'
20
+ import { BMDragPreview } from '../BMView/BMDragSessionPreview'
21
+ import { BMDragSessionActionKind, BMDropSessionActionKind } from '../BMView/BMDragSessionActions'
19
22
 
20
23
  // When set to YES, this will cause collection view to use static animation contexts when setting up animations
21
24
  const BM_COLLECTION_VIEW_USE_STATIC_CONTEXT = YES;
@@ -78,6 +81,45 @@ export var BMCollectionViewAcceptPolicy = Object.freeze({ // <enum>
78
81
 
79
82
  // @endtype
80
83
 
84
+ // @type BMCollectionViewAcceptRegion
85
+
86
+ /**
87
+ * Constants which describe the region by which a drag and drop gesture can be accepted as it
88
+ * enters the collection view's frame.
89
+ */
90
+ export const BMCollectionViewAcceptRegion = Object.freeze({ // <enum>
91
+
92
+ /**
93
+ * Indicates that the drop session will have the same drop action regardless of where
94
+ * in the collection view's frame it is.
95
+ */
96
+ Anywhere: 'Accept', // <enum>
97
+
98
+ /**
99
+ * Indicates that the drop session's drop action changes based on the cell in which the
100
+ * drop occurs. Drops outside of any cell's frame are rejected. The delegate object is
101
+ * expected to implement the {@link BMCollectionViewDelegate.collectionViewDropSessionDidEnterCell} method
102
+ * provide the appropriate drop action for each cell.
103
+ */
104
+ Cell: 'Cell', // <enum>
105
+
106
+ /**
107
+ * Indicates the drop session's drop action depends on the specific position in which the drop occurs.
108
+ * The delegate object is expected to implement the {@link BMCollectionViewDelegate.collectionViewDropSessionDidUpdate}
109
+ * method to provide the appropriate action as the drop session's position updates.
110
+ */
111
+ Position: 'Position', // <enum>
112
+
113
+ /**
114
+ * Indicates that the collection view cannot process the session's items and no further updates will
115
+ * be provided regarding this drop session.
116
+ */
117
+ Nowhere: 'Reject', // <enum>
118
+
119
+ });
120
+
121
+ // @endtype
122
+
81
123
  // @type BMCollectionView Global Symbols
82
124
 
83
125
  // Set to YES during a drag & drop operation
@@ -128,6 +170,24 @@ var _BMCollectionViewSnappingScrollThreshold = 200; // <Number>
128
170
  */
129
171
  var _BMCollectionViewIdentityComparator = function (o1, o2) { return o1 == o2; }; // <Boolean ^ (AnyObject, AnyObject)>
130
172
 
173
+ /**
174
+ * The number of pixels determining how close to an edge the drag session position must be
175
+ * for a collection view to start scrolling.
176
+ */
177
+ const _BMCollectionViewDragScrollDistance = 32;
178
+
179
+ /**
180
+ * How much to multiply the distance to the edge to obtain the final scrolling speed during
181
+ * a drag and drop gesture.
182
+ */
183
+ const _BMCollectionViewDragScrollMultiplier = 20;
184
+
185
+ /**
186
+ * The number of milliseconds to wait for a data source to insert items asynchronously after a drop
187
+ * before playing the generic drop animation for the drop previews.
188
+ */
189
+ const _BMCollectionViewAsynchronousDropDelay = 200;
190
+
131
191
  // @endtype
132
192
 
133
193
  // @type BMCollectionViewScrollingDirection
@@ -157,7 +217,7 @@ export var BMCollectionViewScrollingDirection = Object.freeze({ // <enum>
157
217
  // @type BMCollectionViewScrollingGravityHorizontal
158
218
 
159
219
  /**
160
- * Controls the final horizontal scrolling position of a programatic scroll.
220
+ * Controls the final horizontal scrolling position of a programmatic scroll.
161
221
  */
162
222
  export var BMCollectionViewScrollingGravityHorizontal = Object.freeze({ // <enum>
163
223
  /**
@@ -181,7 +241,7 @@ export var BMCollectionViewScrollingGravityHorizontal = Object.freeze({ // <enum
181
241
  // @type BMCollectionViewScrollingGravityVertical
182
242
 
183
243
  /**
184
- * Controls the final vertical scrolling position of a programatic scroll.
244
+ * Controls the final vertical scrolling position of a programmatic scroll.
185
245
  */
186
246
  export var BMCollectionViewScrollingGravityVertical = Object.freeze({ // <enum>
187
247
  /**
@@ -202,7 +262,7 @@ export var BMCollectionViewScrollingGravityVertical = Object.freeze({ // <enum>
202
262
 
203
263
  // @endtype
204
264
 
205
- // @type BMCollectionView<T = any> extends BMView
265
+ // @type BMCollectionView<T = any> extends BMView implements BMDragDelegate, BMDropDelegate
206
266
 
207
267
  // Contains all active collection view instances
208
268
  var _BMCollectionViews = new Map;
@@ -239,7 +299,6 @@ export function BMCollectionView() { // <constructor>
239
299
  BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? Object.create(BMView.prototype) : {}, {
240
300
  constructor: BMCollectionView,
241
301
 
242
- // MARK: BMView overrides
243
302
  //#region BMView overrides
244
303
 
245
304
  // @override - BMView
@@ -809,6 +868,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
809
868
  var customScrollRequired = args && args.customScroll;
810
869
 
811
870
  _BMCollectionViews.set(this, true);
871
+ BMDragSession._dropTargets.set(this, this);
812
872
 
813
873
  this._container = BMJQueryShim.shimWithDOMNode(node);
814
874
  this.layout = new BMCollectionViewFlowLayout();
@@ -1158,9 +1218,10 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
1158
1218
  for (var i = 0; i < retainedCellsLength; i++) {
1159
1219
  // The retained cells array resizes itself when cells are removed
1160
1220
  this.retainedCells[0]._unmanage();
1161
- }*/
1221
+ }
1162
1222
 
1163
1223
  this.retainedCells = [];
1224
+ */
1164
1225
 
1165
1226
  // Reload the layout
1166
1227
  this._reloadLayout();
@@ -1537,7 +1598,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
1537
1598
  // Compute the new bounds
1538
1599
  var containerNode = this._container[0];
1539
1600
 
1540
- // When snapping, retain the previous scoll position so that the scroll direction can be computed
1601
+ // When snapping, retain the previous scroll position so that the scroll direction can be computed
1541
1602
  var snapsScrollPosition = (!this._isPerformingAnimatedScrolling) && this._layout.snapsScrollPosition;
1542
1603
  var previousBoundsOffset;
1543
1604
  previousBoundsOffset = this._bounds.origin.copy();
@@ -2071,6 +2132,30 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
2071
2132
  * @param indexPaths <[BMIndexPath<T>]> The index paths of the cell whose size should be measured.
2072
2133
  */
2073
2134
  measureSizesOfCellsAtIndexPaths(indexPaths) {
2135
+ // Skip index paths that have already been measured
2136
+ indexPaths = indexPaths.filter(indexPath => {
2137
+ // Return the cached size if it is available.
2138
+ let identifier;
2139
+ if (this._dataSet.identifierForIndexPath) {
2140
+ identifier = this._dataSet.identifierForIndexPath(indexPath);
2141
+
2142
+ if (this._measures[identifier]) {
2143
+ return NO;
2144
+ }
2145
+ }
2146
+ else {
2147
+ let length = this._measuredIndexPaths.length;
2148
+ for (let i = 0; i < length; i++) {
2149
+ let measure = this._measuredIndexPaths[i];
2150
+ if (measure.indexPath.isEqualToIndexPath(indexPath, {usingComparator: this.identityComparator})) {
2151
+ return NO;
2152
+ }
2153
+ }
2154
+ }
2155
+
2156
+ return YES;
2157
+ });
2158
+
2074
2159
  const queue = BMViewLayoutQueue.layoutQueue();
2075
2160
  const iterators = indexPaths.map(p => this._measuredSizeOfCellAtIndexPathGenerator(p, {layoutQueue: queue, dequeue: NO}));
2076
2161
 
@@ -2094,23 +2179,6 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
2094
2179
  if (result.done) return result.value;
2095
2180
  }
2096
2181
 
2097
- // Return the cached size if it is available.
2098
- let identifier;
2099
- if (this._dataSet.identifierForIndexPath) {
2100
- identifier = this._dataSet.identifierForIndexPath(indexPath);
2101
-
2102
- if (this._measures[identifier]) return this._measures[identifier];
2103
- }
2104
- else {
2105
- let length = this._measuredIndexPaths.length;
2106
- for (let i = 0; i < length; i++) {
2107
- let measure = this._measuredIndexPaths[i];
2108
- if (measure.indexPath.isEqualToIndexPath(indexPath, {usingComparator: this.identityComparator})) {
2109
- return this._measuredIndexPaths[i].size;
2110
- }
2111
- }
2112
- }
2113
-
2114
2182
  if (BM_COLLECTION_VIEW_DEBUG_MEASURE) {
2115
2183
  if (!this.__debug__measuring) {
2116
2184
  this.__debug__measuring = YES;
@@ -2603,8 +2671,8 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
2603
2671
  event.target.classList.contains('BMCollectionViewCellEventHandler') || event.target.parentNode.classList.contains('BMCollectionViewCellEventHandler') ||
2604
2672
  event.target.classList.contains('widget-foldingpanel-header') ||
2605
2673
  event.target.hasAttribute('contenteditable') ||
2606
- // This a specific Thingworx workaround that depends on jQuery
2607
- (window.$ ? window.$(event.target).parents('.widget-dhxdropdown').length : false)) {
2674
+ // This a specific Thingworx workaround
2675
+ event.target.closest('.widget-dhxdropdown')) {
2608
2676
  return;
2609
2677
  }
2610
2678
 
@@ -2682,7 +2750,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
2682
2750
 
2683
2751
  cellEventCanDoubleClick = NO;
2684
2752
  if (self.delegate && self.delegate.collectionViewCanDoubleClickCell) {
2685
- cellEventCanDoubleClick = self.delegate.collectionViewCanDoubleClickCell(self, cell);
2753
+ cellEventCanDoubleClick = self.delegate.collectionViewCanDoubleClickCell(self, cell, {withEvent: event});
2686
2754
  }
2687
2755
 
2688
2756
  if (cellEventCanDoubleClick) cellEventDoubleClickTimeout = window.setTimeout(function () {
@@ -2909,6 +2977,8 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
2909
2977
  return cell;
2910
2978
 
2911
2979
  },
2980
+
2981
+ // #region Drag and drop
2912
2982
 
2913
2983
  isDragging: NO,
2914
2984
 
@@ -2954,6 +3024,8 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
2954
3024
  * @param toIndexPath <BMIndexPath<T>> The suggested index path at which to add the items.
2955
3025
  * @param withDropShadows <Map<AnyObject, DOMNode>> A map containing the link between drop shadows and the items.
2956
3026
  * }
3027
+ * @return <Promise<void>> A promise that resolves when the associated data update
3028
+ * has completed.
2957
3029
  */
2958
3030
  _insertItems: async function (items, args) {
2959
3031
  this._droppedShadows = args.withDropShadows;
@@ -2965,6 +3037,615 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
2965
3037
  });
2966
3038
  },
2967
3039
 
3040
+ /**
3041
+ * An array containing the cells whose index paths are part of the current drag session, or
3042
+ * `undefined` while there is no drag session in progress for this collection view.
3043
+ */
3044
+ _draggingCells: undefined, // <[BMCollectionViewCell], nullable>
3045
+
3046
+ /**
3047
+ * A map that keeps track of the association between drag items in a drag session and the cells
3048
+ * whose items they represent.
3049
+ */
3050
+ _cellItemMap: undefined, // <Map<BMDragItem, BMCollectionViewCell>>
3051
+
3052
+ /**
3053
+ * Begins a drag gesture from the specified event. The drag event will move the cell from which
3054
+ * the event originates, or all selected cells if that cell is selected.
3055
+ * @param event <Event> The event triggering this action.
3056
+ * {
3057
+ * @param forCell <BMCollectionViewCell> The cell from which this event originates.
3058
+ * @param touchIdentifier <AnyObject> If this event is a `TouchEvent`, this represents the identifier
3059
+ * of the touch point that will control this drag & drop operation.
3060
+ * }
3061
+ */
3062
+ beginDragWithEvent(event, args) {
3063
+ const cell = args.forCell;
3064
+
3065
+ let cells;
3066
+
3067
+ if (this.isCellAtIndexPathSelected(cell.indexPath)) {
3068
+ cells = [cell];
3069
+ // Sort the index paths in ascending order
3070
+ this._selectedIndexPaths.sort((i1, i2) => i2.section == i1.section ? i1.row - i2.row : i1.section - i2.section).forEach(indexPath => {
3071
+ let retainedCell = this.retainCellForIndexPath(indexPath);
3072
+
3073
+ if (retainedCell != cell) {
3074
+ cells.push(retainedCell);
3075
+ }
3076
+ retainedCell.isDragging = YES;
3077
+
3078
+ retainedCell.node.classList.add('BMCollectionViewCellDragging');
3079
+ });
3080
+ }
3081
+ else {
3082
+ cells = [cell];
3083
+ cell.isDragging = YES;
3084
+ cell.retain();
3085
+
3086
+ cell.node.classList.add('BMCollectionViewCellDragging');
3087
+ }
3088
+
3089
+ this._draggingCells = cells;
3090
+ this._cellItemMap = new Map();
3091
+
3092
+ BMDragSession._beginDragWithEvent(event, {dragDelegate: this, view: this, touchIdentifier: args.touchIdentifier});
3093
+ },
3094
+
3095
+ /**
3096
+ * Creates and returns a fallback drag item for the specified index path if the data source
3097
+ * object cannot provide a customized drag item.
3098
+ * @param indexPath <BMIndexPath> The index path for which to return a drag item.
3099
+ * @return <BMDragItem> A drag item;
3100
+ */
3101
+ _defaultDragItemForIndexPath(indexPath) {
3102
+ const object = this.dataSet.copyOfItem?.(indexPath.object) ?? JSON.parse(JSON.stringify(indexPath.object));
3103
+
3104
+ return {
3105
+ canConformToType(type) {
3106
+ return type == 'default' || type == 'object';
3107
+ },
3108
+
3109
+ itemOfType(type) {
3110
+ if (type != 'default' && type != 'object') {
3111
+ throw new Error(`Cannot provide an item representation of type "${type}"`);
3112
+ }
3113
+
3114
+ return object;
3115
+ }
3116
+ }
3117
+ },
3118
+
3119
+ dragSessionInitialItems(session) {
3120
+ this._draggingIndexPaths = [];
3121
+
3122
+ const items = this._draggingCells.map(cell => {
3123
+ const item = this.dataSet.dragItemForIndexPath?.(cell.indexPath, {session}) ?? this._defaultDragItemForIndexPath(cell.indexPath);
3124
+ this._cellItemMap.set(item, cell);
3125
+ this._draggingIndexPaths.push(cell.indexPath);
3126
+ return item;
3127
+ });
3128
+
3129
+ this._draggingIndexPaths.sort((i1, i2) => i1.section == i2.section ? i1.row - i2.row : i1.section - i2.section);
3130
+
3131
+ return items;
3132
+ },
3133
+
3134
+ dragSessionPreviewForItem(session, item) {
3135
+ const cell = this._cellItemMap.get(item);
3136
+
3137
+ cell.node.classList.remove('BMCollectionViewCellDragging');
3138
+ const preview = BMDragPreview.dragPreviewWithCopyOfSourceNode(cell.node, {forItem: item});
3139
+ cell.node.classList.add('BMCollectionViewCellDragging');
3140
+
3141
+ return preview;
3142
+ },
3143
+
3144
+ /**
3145
+ * The current drag action to use based on the current drag position.
3146
+ */
3147
+ _dragAction: undefined, // <BMDragSessionAction, nullable>
3148
+
3149
+ /**
3150
+ * Set to `YES` while a drag session started by this collection view is in its frame.
3151
+ */
3152
+ _dragSessionInFrame: NO, // <Boolean>
3153
+
3154
+ /**
3155
+ * The amount by which to scroll on the Y axis during the current drag session.
3156
+ */
3157
+ _dragScrollDirectionY: 0, // <Number>
3158
+
3159
+ /**
3160
+ * The amount by which to scroll on the X axis during the current drag session.
3161
+ */
3162
+ _dragScrollDirectionX: 0, // <Number>
3163
+
3164
+ /**
3165
+ * Whenever the pointer moves to the edges of this collection view during a drag session,
3166
+ * this method periodically scrolls the collection view's contents appropriately.
3167
+ */
3168
+ _dragScroll() {
3169
+ // This callback runs continuously during the drag & drop operation - it is removed upon the operation finishing
3170
+ this._scrollFrameIdentifier = window.requestAnimationFrame(() => this._dragScroll());
3171
+
3172
+ // Don't perform the scroll during data updates
3173
+ if (this.isUpdatingData) {
3174
+ return;
3175
+ }
3176
+
3177
+ if (this._dragScrollDirectionX || this._dragScrollDirectionY) {
3178
+ const offset = this.scrollOffset;
3179
+ offset.x = offset.x + this._dragScrollDirectionX;
3180
+ offset.y = offset.y + this._dragScrollDirectionY;
3181
+
3182
+ offset.x = Math.max(0, Math.min(offset.x, this.size.width - this.frame.size.width));
3183
+ offset.y = Math.max(0, Math.min(offset.y, this.size.height - this.frame.size.height));
3184
+
3185
+ this.scrollOffset = offset;
3186
+ }
3187
+ },
3188
+
3189
+ /**
3190
+ * The identifier of the animation frame callback used to scroll this collection during a drag session while the
3191
+ * mouse pointer
3192
+ */
3193
+ _scrollFrameIdentifier: undefined, // <Number, nullable>
3194
+
3195
+ dragSessionDidEnter(session) {
3196
+ this._dragSessionInFrame = YES;
3197
+ this._dragAction = BMDragSessionAction.actionWithKind(BMDragSessionActionKind.Ignore);
3198
+ this._scrollFrameIdentifier = window.requestAnimationFrame(() => this._dragScroll());
3199
+ },
3200
+
3201
+ dragSessionDidUpdate(session) {
3202
+ if (this._dragSessionInFrame) {
3203
+ if (this.delegate?.collectionViewCanReorderItemsAtIndexPaths?.(this, this._draggingIndexPaths, {session}) ?? YES) {
3204
+ // If item movement is supported, determine the new index path to which the items should move
3205
+ // Instruct the data set to move the item, if the new index path does not match the index path of any dragging item
3206
+ const localPoint = this.positionOfDragSession(session);
3207
+ const targetIndexPath = this.indexPathAtPoint(localPoint);
3208
+
3209
+ let canMoveItem = YES;
3210
+ if (!targetIndexPath) {
3211
+ return;
3212
+ }
3213
+
3214
+ // If the target index path is an index path of an item that's part of the session, don't perform the reorder
3215
+ this._draggingIndexPaths.forEach(indexPath => {
3216
+ if (targetIndexPath.row == indexPath.row && targetIndexPath.section == indexPath.section) {
3217
+ canMoveItem = NO;
3218
+ }
3219
+ });
3220
+
3221
+ if (canMoveItem) {
3222
+ if (this.dataSet.moveItemsFromIndexPaths && this._draggingIndexPaths.length > 1) {
3223
+ // Instruct the data set to move the items in bulk if possible
3224
+ const newIndexPaths = this.dataSet.moveItemsFromIndexPaths(this._draggingIndexPaths, {toIndexPath: targetIndexPath});
3225
+ this._draggingIndexPaths = newIndexPaths.sort((i1, i2) => i1.section == i2.section ? i1.row - i2.row : i1.section - i2.section);
3226
+ }
3227
+ else {
3228
+ // Otherwise move them one by one
3229
+ this._draggingIndexPaths.forEach((indexPath, index) => {
3230
+ let destinationIndexPath = targetIndexPath.copy();
3231
+ destinationIndexPath.object = indexPath.object;
3232
+ destinationIndexPath.row += index;
3233
+ if (this.dataSet.moveItemFromIndexPath(indexPath, {toIndexPath: destinationIndexPath})) {
3234
+ this._draggingIndexPaths[index] = destinationIndexPath;
3235
+ }
3236
+ });
3237
+ }
3238
+ }
3239
+ }
3240
+
3241
+ // Determine the scrolling direction
3242
+ const viewportFrame = BMRectMakeWithNodeFrame(this.node);
3243
+ const slope = _BMCollectionViewDragScrollDistance;
3244
+ const multi = _BMCollectionViewDragScrollMultiplier;
3245
+ if (session.position.x - viewportFrame.origin.x < slope) {
3246
+ this._dragScrollDirectionX =
3247
+ (session.position.x - viewportFrame.origin.x - slope) / slope * multi;
3248
+ }
3249
+ else if (viewportFrame.right - session.position.x < slope) {
3250
+ this._dragScrollDirectionX = (slope - viewportFrame.right + session.position.x) / slope * multi;
3251
+ }
3252
+ else {
3253
+ this._dragScrollDirectionX = 0;
3254
+ }
3255
+
3256
+ if (session.position.y - viewportFrame.origin.y < slope) {
3257
+ this._dragScrollDirectionY = (session.position.y - viewportFrame.origin.y - slope) / slope * multi;
3258
+ }
3259
+ else if (viewportFrame.bottom - session.position.y < slope) {
3260
+ this._dragScrollDirectionY = (slope - viewportFrame.bottom + session.position.y) / slope * multi;
3261
+ }
3262
+ else {
3263
+ this._dragScrollDirectionY = 0;
3264
+ }
3265
+ }
3266
+
3267
+ return this._dragAction;
3268
+ },
3269
+
3270
+ dragSessionDidExit(session) {
3271
+ this._dragSessionInFrame = NO;
3272
+ if (this.delegate?.collectionViewCanRemoveItemsAtIndexPaths?.(this, this._draggingIndexPaths, {session})) {
3273
+ const message = this.delegate?.collectionDeleteMessageForIndexPaths?.(this, this._draggingIndexPaths, {session}) ?? 'Remove';
3274
+ this._dragAction = BMDragSessionAction.actionWithKind(BMDragSessionActionKind.Delete, {message});
3275
+ }
3276
+
3277
+ if (this._scrollFrameIdentifier) {
3278
+ this._dragScrollDirectionX = 0;
3279
+ this._dragScrollDirectionY = 0;
3280
+ window.cancelAnimationFrame(this._scrollFrameIdentifier);
3281
+ this._scrollFrameIdentifier = undefined;
3282
+ }
3283
+ },
3284
+
3285
+ dragSessionWillFinish(session) {
3286
+ this.delegate?.collectionViewWillFinishInteractiveMovementForCell?.(this, this._draggingCells[0], {atIndexPath: this._draggingCells[0].indexPath, session});
3287
+ },
3288
+
3289
+ dragSessionPerformMoveForItems(session, items) {
3290
+ const indexPaths = items.map(i => this._cellItemMap.get(i).indexPath).sort((i1, i2) => {
3291
+ return i1.section == i2.section ? i1.row - i2.row : i1.section - i2.section;
3292
+ });
3293
+
3294
+ this.dataSet.removeItemsAtIndexPaths(indexPaths, {session});
3295
+ },
3296
+
3297
+ dragSessionPerformDelete(session) {
3298
+ this.dataSet.removeItemsAtIndexPaths(this._draggingIndexPaths, {session});
3299
+ },
3300
+
3301
+ dragSessionTransferKind(session) {
3302
+ const acceptPolicy = this.delegate?.collectionViewTransferPolicyForItemsAtIndexPaths?.(this, this._draggingIndexPaths, {session});
3303
+ switch (acceptPolicy) {
3304
+ case BMCollectionViewTransferPolicy.Move:
3305
+ return BMDragTransferKind.Move;
3306
+ case BMCollectionViewTransferPolicy.Copy:
3307
+ default:
3308
+ return BMDragTransferKind.Copy;
3309
+ }
3310
+ },
3311
+
3312
+ dragSessionDidFinish(session) {
3313
+ // Remove the dragging class from the cell
3314
+ this._draggingCells.forEach(cell => {
3315
+ cell.node.classList.remove('BMCollectionViewCellDragging');
3316
+ cell.isDragging = NO;
3317
+
3318
+ // Release the cell
3319
+ cell.release();
3320
+ });
3321
+
3322
+ this._draggingCells = undefined;
3323
+ this._cellItemMap = undefined;
3324
+ this._draggingIndexPaths = undefined;
3325
+
3326
+ this.delegate?.collectionViewDidFinishInteractiveMovementForCell?.(this, this._draggingCells[0], {atIndexPath: this._draggingCells[0].indexPath, session});
3327
+ },
3328
+
3329
+ dragSessionRequiresCustomDropAnimationForItems(session, items) {
3330
+ // Collection view only needs to customize the drop animation for partial transfers
3331
+ // where the transfer kind is move
3332
+ if (session.transferKind == BMDragTransferKind.Move && session.action._action == BMDropSessionActionKind.AcceptPartially) {
3333
+ return YES;
3334
+ }
3335
+ return NO;
3336
+ },
3337
+
3338
+ dragSessionAnimateDropWithPreviews(session, previews) {
3339
+ // If this collection view was already waiting for the data set to insert some data from a drop,
3340
+ // play the generic drop animation for those previews
3341
+ if (this._dropPreviews) {
3342
+ for (const preview of this._dropPreviews) {
3343
+ preview.performDrop();
3344
+ }
3345
+ }
3346
+
3347
+ this._dropPreviews = previews.slice();
3348
+ const dropPreviews = this._dropPreviews;
3349
+
3350
+ // After this method returns, the drag session will instruct collection view to perform the move for the
3351
+ // affected items, which should trigger a synchronous data update to use and clear the drag previews
3352
+ queueMicrotask(() => {
3353
+ if (this._dropPreviews == dropPreviews) {
3354
+ this._dropPreviews = undefined;
3355
+ }
3356
+ });
3357
+ },
3358
+
3359
+ /**
3360
+ * The kind of region being tracked during a drop session. `undefined` if a drop session is
3361
+ * not in progress.
3362
+ */
3363
+ _dropSessionRegionKind: undefined, // <BMCollectionViewAcceptRegion, nullable>
3364
+
3365
+ /**
3366
+ * The current drop action to use based on the current drop session position.
3367
+ */
3368
+ _dropAction: undefined, // <BMDropSessionAction, nullable>
3369
+
3370
+ /**
3371
+ * The index path associated with the current drop session, if any.
3372
+ */
3373
+ _dropIndexPath: undefined, // <BMIndexPath, nullable>
3374
+
3375
+ dropSessionCanBegin(session) {
3376
+ this._lastDropIndexPath = undefined;
3377
+ this._dropIndexPath = undefined;
3378
+ this._dropAction = undefined;
3379
+
3380
+ const items = session.items.map(i => i.itemOfType('default'));
3381
+ const canBegin = this.delegate?.collectionViewCanAcceptItems?.(this, items, {session}) ?? NO;
3382
+
3383
+ // The drop session can begin if the response is not `NO` or `.Nowhere`.
3384
+ if (canBegin != NO && canBegin != BMCollectionViewAcceptRegion.Nowhere) {
3385
+ if (canBegin == YES || canBegin == BMCollectionViewAcceptRegion.Anywhere) {
3386
+ // If the drop session can be accepted anywhere, request the drop action from the delegate
3387
+ this._dropAction = this.delegate?.collectionViewDropActionForDropSession?.(this, session);
3388
+ this._dropAction ??= BMDropSessionAction.actionWithKind(BMDropSessionActionKind.Accept);
3389
+ this._dropSessionRegionKind = BMCollectionViewAcceptRegion.Anywhere;
3390
+ }
3391
+ else {
3392
+ this._dropSessionRegionKind = canBegin;
3393
+ }
3394
+
3395
+ return YES;
3396
+ }
3397
+
3398
+ return NO;
3399
+ },
3400
+
3401
+ dropSessionDidEnter(session) {
3402
+ this._dragScrollDirectionX = 0;
3403
+ this._dragScrollDirectionY = 0;
3404
+ this._scrollFrameIdentifier = window.requestAnimationFrame(() => this._dragScroll());
3405
+ },
3406
+
3407
+ dropSessionDidExit(session) {
3408
+ if (this._scrollFrameIdentifier) {
3409
+ this._dragScrollDirectionX = 0;
3410
+ this._dragScrollDirectionY = 0;
3411
+ window.cancelAnimationFrame(this._scrollFrameIdentifier);
3412
+ this._scrollFrameIdentifier = undefined;
3413
+ }
3414
+ },
3415
+
3416
+ /**
3417
+ * Returns the position of the specified drag or drop session relative to the collection view's bounds.
3418
+ * @param session <BMDragSession or BMDropSession> The drag or drop session.
3419
+ * @return <BMPoint> The coordinates relative to the bounds.
3420
+ */
3421
+ positionOfDragSession(session) {
3422
+ const point = session.position;
3423
+
3424
+ // Determine the index path over which this session currently is
3425
+ const viewportFrame = BMRectMakeWithNodeFrame(this.node);
3426
+
3427
+ // Discover the coordinates of the point that are relative to the collection view's bounds
3428
+ let localPoint = BMPointMake(
3429
+ point.x - viewportFrame.origin.x,
3430
+ point.y - viewportFrame.origin.y,
3431
+ );
3432
+ localPoint.x += this.scrollOffset.x;
3433
+ localPoint.y += this.scrollOffset.y;
3434
+
3435
+ return localPoint;
3436
+ },
3437
+
3438
+ /**
3439
+ * Determines the index path at the specified point whose coordinates are relative to the bounds.
3440
+ * @param point <BMPoint> The point.
3441
+ * @return <BMIndexPath, nullable> The index path, if any cell's frame intersects the point,
3442
+ * or `undefined` otherwise.
3443
+ */
3444
+ indexPathAtPoint(point) {
3445
+ // Create a rect around this point with a size of 64 by 64 pixels.
3446
+ const rect = BMRectMake(0, 0, 64, 64);
3447
+ rect.center = point;
3448
+
3449
+ // Request the attributes within the given rect
3450
+ const attributes = this.layout.attributesForElementsInRect(rect);
3451
+
3452
+ // Find the index path to which the cell should move
3453
+ let targetIndexPath;
3454
+ attributes.forEach(attribute => {
3455
+ if (attribute.itemType != BMCollectionViewLayoutAttributesType.Cell) {
3456
+ return;
3457
+ }
3458
+
3459
+ if (attribute.frame.containsPoint(point)) {
3460
+ targetIndexPath = attribute.indexPath.copy();
3461
+ }
3462
+ });
3463
+
3464
+ return targetIndexPath;
3465
+ },
3466
+
3467
+ /**
3468
+ * The most recent index path the current drop session has been over.
3469
+ */
3470
+ _lastDropIndexPath: undefined, // <BMIndexPath>
3471
+
3472
+ dropSessionDidUpdate(session) {
3473
+ const localPoint = this.positionOfDragSession(session);
3474
+ const targetIndexPath = this.indexPathAtPoint(localPoint);
3475
+
3476
+ if (targetIndexPath) {
3477
+ this._lastDropIndexPath = targetIndexPath;
3478
+ }
3479
+
3480
+ if (this._dropSessionRegionKind == BMCollectionViewAcceptRegion.Anywhere) {
3481
+ return this._dropAction;
3482
+ }
3483
+
3484
+ // Determine the scrolling direction
3485
+ const viewportFrame = BMRectMakeWithNodeFrame(this.node);
3486
+ const slope = _BMCollectionViewDragScrollDistance;
3487
+ const multi = _BMCollectionViewDragScrollMultiplier;
3488
+ if (session.position.x - viewportFrame.origin.x < slope) {
3489
+ this._dragScrollDirectionX =
3490
+ (session.position.x - viewportFrame.origin.x - slope) / slope * multi;
3491
+ }
3492
+ else if (viewportFrame.right - session.position.x < slope) {
3493
+ this._dragScrollDirectionX = (slope - viewportFrame.right + session.position.x) / slope * multi;
3494
+ }
3495
+ else {
3496
+ this._dragScrollDirectionX = 0;
3497
+ }
3498
+
3499
+ if (session.position.y - viewportFrame.origin.y < slope) {
3500
+ this._dragScrollDirectionY = (session.position.y - viewportFrame.origin.y - slope) / slope * multi;
3501
+ }
3502
+ else if (viewportFrame.bottom - session.position.y < slope) {
3503
+ this._dragScrollDirectionY = (slope - viewportFrame.bottom + session.position.y) / slope * multi;
3504
+ }
3505
+ else {
3506
+ this._dragScrollDirectionY = 0;
3507
+ }
3508
+
3509
+ // If the index path changes, notify the delegate using the appropriate methods
3510
+ if (!targetIndexPath) {
3511
+ if (this._dropIndexPath) {
3512
+ this._dropIndexPath = undefined;
3513
+ this.delegate?.collectionViewDropSessionDidExitIndexPath?.(this, session, {indexPath: this._dropIndexPath});
3514
+
3515
+ if (this._dropSessionRegionKind == BMCollectionViewAcceptRegion.Cell) {
3516
+ return BMDropSessionAction.actionWithKind(BMDropSessionActionKind.Ignore);
3517
+ }
3518
+ }
3519
+ }
3520
+ else {
3521
+ let isNewIndexPath = NO;
3522
+ if (!this._dropIndexPath) {
3523
+ isNewIndexPath = YES;
3524
+ }
3525
+ else {
3526
+ if (!this._dropIndexPath.isEqualToIndexPath(targetIndexPath, {usingComparator: this.identityComparator})) {
3527
+ isNewIndexPath = YES;
3528
+ this.delegate?.collectionViewDropSessionDidExitIndexPath?.(this, session, {indexPath: this._dropIndexPath});
3529
+ }
3530
+ }
3531
+
3532
+ this._dropIndexPath = targetIndexPath;
3533
+
3534
+ if (isNewIndexPath) {
3535
+ const action = this.delegate?.collectionViewDropSessionDidEnterIndexPath?.(this, session, {indexPath: this._dropIndexPath});
3536
+
3537
+ if (this._dropSessionRegionKind == BMCollectionViewAcceptRegion.Cell) {
3538
+ return action;
3539
+ }
3540
+ }
3541
+ }
3542
+
3543
+ if (this._dropSessionRegionKind == BMCollectionViewAcceptRegion.Position) {
3544
+ const action = this.delegate?.collectionViewDropSessionDidUpdate?.(this, session, {position: localPoint});
3545
+ return action;
3546
+ }
3547
+ },
3548
+
3549
+ dropSessionPreviewForItem(session, item) {
3550
+ const node = this.delegate?.collectionViewPreviewForDropSession?.(this, session, {item});
3551
+ if (node) {
3552
+ return BMDragPreview.dragPreviewWithPreviewNode(node, {forItem: item});
3553
+ }
3554
+ },
3555
+
3556
+ dropSessionWillFinish(session) {
3557
+ this.delegate?.collectionViewDropSessionWillFinish?.(this, session);
3558
+ },
3559
+
3560
+ dropSessionRequiresCustomDropAnimationForItems(session, items) {
3561
+ return YES;
3562
+ },
3563
+
3564
+ dropSessionTransferKind(session) {
3565
+ const acceptPolicy = this.delegate?.collectionViewAcceptPolicyForItems?.(this, session.items.map(i => i.itemOfType('default')), {session});
3566
+ switch (acceptPolicy) {
3567
+ case BMCollectionViewAcceptPolicy.Move:
3568
+ return BMDragTransferKind.Move;
3569
+ case BMCollectionViewAcceptPolicy.Copy:
3570
+ default:
3571
+ return BMDragTransferKind.Copy;
3572
+ }
3573
+ },
3574
+
3575
+ /**
3576
+ * The drop previews that must be animated at the end of a successful drop session. Only set while performing
3577
+ * the data update associated with accepting items via a drop session.
3578
+ */
3579
+ _dropPreviews: undefined, // <[BMDropPreview], nullable>
3580
+
3581
+ dropSessionPerformDrop(session) {
3582
+ // If this collection view was already waiting for the data set to insert some data from a drop,
3583
+ // play the generic drop animation for those previews
3584
+ if (this._dropPreviews) {
3585
+ BMAnimateWithBlock(() => {
3586
+ for (const preview of this._dropPreviews) {
3587
+ preview.performDrop();
3588
+ }
3589
+ }, {duration: 300, easing: 'easeInQuad'});
3590
+ }
3591
+
3592
+ // If there are no items to accept, don't perform any other changes
3593
+ if (!session.dropItems.length) {
3594
+ BMAnimateWithBlock(() => {
3595
+ for (const preview of session.dropPreviews) {
3596
+ preview.performDrop();
3597
+ }
3598
+ }, {duration: 300, easing: 'easeInQuad'});
3599
+ return;
3600
+ }
3601
+
3602
+ let toIndexPath = this._lastDropIndexPath;
3603
+ if (!toIndexPath) {
3604
+ const sectionCount = this.numberOfSections();
3605
+ const row = this.numberOfObjectsInSectionAtIndex(sectionCount - 1) - 1;
3606
+ const section = sectionCount - 1;
3607
+ toIndexPath = this.indexPathForObjectAtRow(row, {inSectionAtIndex: section}) ?? BMIndexPathMakeWithRow(row, {section});
3608
+ }
3609
+
3610
+ // Instruct the data set to insert the items to the appropriate index path
3611
+ this._dropPreviews = session.dropPreviews.slice();
3612
+
3613
+ const items = session.dropItems.map(i => i.itemOfType('default'));
3614
+ const result = this.dataSet.insertItems(items, {toIndexPath, session});
3615
+
3616
+ if (result) {
3617
+ // If the data set performs the change asynchronously, give it 200ms to trigger the data update, otherwise
3618
+ // play the generic drop animation for the previews
3619
+ const dropPreviews = this._dropPreviews;
3620
+ let resolved = NO;
3621
+ result.then(() => {
3622
+ resolved = YES;
3623
+
3624
+ if (this._dropPreviews == dropPreviews) {
3625
+ this._dropPreviews = undefined;
3626
+ }
3627
+ });
3628
+
3629
+ setTimeout(() => {
3630
+ if (!resolved) {
3631
+ if (this._dropPreviews == dropPreviews) {
3632
+ this._dropPreviews = undefined;
3633
+ }
3634
+
3635
+ BMAnimateWithBlock(() => {
3636
+ for (const preview of dropPreviews) {
3637
+ preview.performDrop();
3638
+ }
3639
+ }, {duration: 300, easing: 'easeInQuad'});
3640
+
3641
+ }
3642
+ }, _BMCollectionViewAsynchronousDropDelay);
3643
+ }
3644
+ else {
3645
+ this._dropPreviews = undefined;
3646
+ }
3647
+ },
3648
+
2968
3649
  /**
2969
3650
  * Begins a drag gesture from the given event. The drag event will move the cell from which
2970
3651
  * the event originates.
@@ -2975,7 +3656,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
2975
3656
  * of the touch point that will control this drag & drop operation.
2976
3657
  * }
2977
3658
  */
2978
- beginDragWithEvent(event, args) {
3659
+ _beginDragWithEvent(event, args) {
2979
3660
  if (this.isDragging) return;
2980
3661
  // TODO Consider splitting up this giant method
2981
3662
 
@@ -3018,7 +3699,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
3018
3699
  }
3019
3700
  else {
3020
3701
  cells = [cell];
3021
- cell.isDraggging = YES;
3702
+ cell.isDragging = YES;
3022
3703
  cell.retain();
3023
3704
 
3024
3705
  cell.node.classList.add('BMCollectionViewCellDragging');
@@ -3082,18 +3763,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
3082
3763
 
3083
3764
  draggingShadows.push(otherDraggingShadow);
3084
3765
  let index = draggingShadows.length;
3085
-
3086
- // __BMVelocityAnimate(otherDraggingShadow, {
3087
- // rotateZ: ((index) * 60 / cells.length) + 'deg',
3088
- // left: [draggingShadowTargetPoint.x + 'px', sourceRect.origin.x + 'px'],
3089
- // top: [draggingShadowTargetPoint.y + 'py', sourceRect.origin.x + 'px']
3090
- // }, {
3091
- // easing: 'easeInOutQuad',
3092
- // duration: 300,
3093
- // complete() {
3094
- // areDraggingShadowsAnimating = NO;
3095
- // }
3096
- // });
3766
+
3097
3767
  (window.Velocity || $.Velocity).animate(otherDraggingShadow, {tween: 1}, {
3098
3768
  easing: 'easeInOutQuad',
3099
3769
  duration: 300,
@@ -3123,15 +3793,6 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
3123
3793
  draggingShadowsToAdd.forEach(shadow => document.body.appendChild(shadow));
3124
3794
  document.body.appendChild(draggingIndicator);
3125
3795
 
3126
- // (window.Velocity || $.Velocity).animate(draggingIndicator, {
3127
- // opacity: [1, 0],
3128
- // scaleX: [1, 0],
3129
- // scaleY: [1, 0]
3130
- // }, {
3131
- // duration: 300,
3132
- // easing: 'easeOutQuad'
3133
- // });
3134
-
3135
3796
  __BMVelocityAnimate(draggingIndicator, {
3136
3797
  opacity: [1, 0],
3137
3798
  scaleX: [1, 0],
@@ -3342,10 +4003,14 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
3342
4003
  }
3343
4004
 
3344
4005
  // Temporarily suspend these updates while a data update is in progress
3345
- if (this.isUpdatingData) return event.preventDefault(), event.stopPropagation();
4006
+ if (this.isUpdatingData) {
4007
+ event.preventDefault();
4008
+ event.stopPropagation();
4009
+ return;
4010
+ }
3346
4011
 
3347
4012
 
3348
- // 3. Enabling scrolling while close to the collection view's edges.
4013
+ // 3. Enable scrolling while close to the collection view's edges.
3349
4014
  if (clientX - collectionViewViewportFrame.origin.x < 32) {
3350
4015
  scrollDirectionX = (clientX - collectionViewViewportFrame.origin.x - 32) / 32 * 20;
3351
4016
  }
@@ -3773,7 +4438,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
3773
4438
  // Remove the dragging class from the cell
3774
4439
  cells.forEach(cell => {
3775
4440
  cell.node.classList.remove('BMCollectionViewCellDragging');
3776
- cell.isDraggging = NO;
4441
+ cell.isDragging = NO;
3777
4442
 
3778
4443
  // Release the cell
3779
4444
  cell.release();
@@ -4381,7 +5046,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
4381
5046
  * Highlights the index path to the specified direction of the currently highlighted index path.
4382
5047
  * @param arrow <String> The key code of the keyboard arrow that was pressed.
4383
5048
  * {
4384
- * @param withEvent <KeyboardEvent> The event that triggerred this action.
5049
+ * @param withEvent <KeyboardEvent> The event that triggered this action.
4385
5050
  * }
4386
5051
  */
4387
5052
  keyboardArrowPressed(arrow, args) {
@@ -4872,8 +5537,66 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
4872
5537
  // regardless of where the new or old positions of cells might be
4873
5538
  let size = layout.contentSize();
4874
5539
  this._size = BMSizeMake(Math.max(this._size.width, size.width), Math.max(this._size.height, size.height));
5540
+
5541
+ /**
5542
+ * Cleans up the changes performed by the current transition.
5543
+ */
5544
+ function cleanupTransition() {
5545
+ if (self.iScroll) self.iScroll.refresh();
5546
+
5547
+ // Unmanage all outgoing supplementary views
5548
+ for (var i = 0; i < outgoingSupplementaryViewAttributes.length; i++) {
5549
+ var attribute = outgoingSupplementaryViewAttributes[i];
5550
+ var cell = self.supplementaryViewWithIdentifier(attribute.identifier, {atIndexPath: attribute.indexPath});
5551
+
5552
+ if (cell && cell.isManaged) {
5553
+ cell._unmanage();
5554
+ }
5555
+ }
5556
+
5557
+ self._applyOverflows();
5558
+
5559
+ // Restore collection and invoke the data completion callbacks unless another animation is in progress
5560
+ if (self._dataUpdatePromise == dataUpdatePromise) {
5561
+ self._collectionEnabled = YES;
5562
+ self.isUpdatingData = NO;
5563
+
5564
+ self._executeDataCompletionCallbacks();
5565
+ }
5566
+
5567
+ // If the delegate animation options contains a complete handler, invoke it here
5568
+ if (delegateOptions.complete) delegateOptions.complete();
5569
+
5570
+ // Run the completion handler if it was specified
5571
+ if (options && options.completionHandler) options.completionHandler();
5572
+
5573
+ resolveLayoutUpdate();
5574
+ }
4875
5575
 
4876
5576
  animationOptions.complete = function () {
5577
+ // If the transition was stopped, don't continue with the remaining changes
5578
+ if (self._layout != transitionLayout) {
5579
+ self._size = size.copy();
5580
+
5581
+ if (self.iScroll) {
5582
+ // Apply the new scroll offset internally to iScroll, if it changed
5583
+ if (oldBounds.origin.x != newBounds.origin.x || oldBounds.origin.y != newBounds.origin.y) {
5584
+ var targetOffset = newBounds.origin.copy();
5585
+
5586
+ targetOffset.x += self._offscreenBufferSize;
5587
+ targetOffset.y += self._offscreenBufferSize;
5588
+
5589
+ self.iScroll._translate(-targetOffset.x, -targetOffset.y);
5590
+ }
5591
+ }
5592
+
5593
+ animationCells.forEach(cell => cell.release());
5594
+ retainedCells.forEach(cell => cell.release());
5595
+
5596
+ cleanupTransition();
5597
+ return;
5598
+ }
5599
+
4877
5600
  // Request the layout page
4878
5601
  var attributes = transitionLayout.attributesForElementsInRect();
4879
5602
 
@@ -4908,35 +5631,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
4908
5631
  self._layout = layout;
4909
5632
  self._transitionLayout = undefined;
4910
5633
 
4911
- if (self.iScroll) self.iScroll.refresh();
4912
-
4913
- // Unmanage all outgoing supplementary views
4914
- for (var i = 0; i < outgoingSupplementaryViewAttributes.length; i++) {
4915
- var attribute = outgoingSupplementaryViewAttributes[i];
4916
- var cell = self.supplementaryViewWithIdentifier(attribute.identifier, {atIndexPath: attribute.indexPath});
4917
-
4918
- if (cell && cell.isManaged) {
4919
- cell._unmanage();
4920
- }
4921
- }
4922
-
4923
- self._applyOverflows();
4924
-
4925
- // Restore collection invoke the data completion callbacks unless another animation is in progress
4926
- if (self._dataUpdatePromise == dataUpdatePromise) {
4927
- self._collectionEnabled = YES;
4928
- self.isUpdatingData = NO;
4929
-
4930
- self._executeDataCompletionCallbacks();
4931
- }
4932
-
4933
- // If the delegate animation options contains a complete handler, invoke it here
4934
- if (delegateOptions.complete) delegateOptions.complete();
4935
-
4936
- // Run the completion handler if it was specified
4937
- if (options && options.completionHandler) options.completionHandler();
4938
-
4939
- resolveLayoutUpdate();
5634
+ cleanupTransition();
4940
5635
  };
4941
5636
 
4942
5637
  animationOptions.queue = NO;
@@ -5034,9 +5729,16 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5034
5729
 
5035
5730
  },
5036
5731
 
5037
-
5038
-
5039
-
5732
+ /**
5733
+ * If a layout transition is currently in progress it is stopped, allowing data updates to
5734
+ * start without affecting the retained cells.
5735
+ */
5736
+ _stopLayoutTransition() {
5737
+ if (this._transitionLayout && this._layout == this._transitionLayout) {
5738
+ this._layout._stopTransition();
5739
+ this._layout = this._layout.targetLayout;
5740
+ }
5741
+ },
5040
5742
 
5041
5743
  /************************************* DATA SET UPDATE ************************************/
5042
5744
 
@@ -5133,6 +5835,49 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5133
5835
  get dataUpdated() { // <Promise<void>, nullable>
5134
5836
  return this._dataUpdatePromise;
5135
5837
  },
5838
+
5839
+ /**
5840
+ * Finds and returns the drop preview associated with the specified layout attributes, if any exists.
5841
+ * @param attributes <BMCollectionViewLayoutAttributes> Tha attributes for which to find the drop preview.
5842
+ * {
5843
+ * @param previewMap <Map<BMIndexPath<T>, BMDropPreview>, nullable>
5844
+ * An optional mapping between index paths and drop items
5845
+ * used to find the drop preview for the item representation
5846
+ * that the data set actually inserted. The default item
5847
+ * representation will be used if this is not provided.
5848
+ * }
5849
+ * @returns <BMDropPreview, nullable> The drop preview if it was found, `undefined` otherwise.
5850
+ */
5851
+ _dropPreviewForLayoutAttributes: function (attributes, {previewMap}) {
5852
+ if (!this._dropPreviews) {
5853
+ return;
5854
+ }
5855
+
5856
+ let dropPreview;
5857
+
5858
+ if (this._cellItemMap) {
5859
+ for (const [item, cell] of this._cellItemMap.entries()) {
5860
+ if (cell.indexPath.isLooselyEqualToIndexPath(attributes.indexPath, {usingComparator: this.identityComparator})) {
5861
+ return this._dropPreviews.find(p => p.item == item);
5862
+ }
5863
+ }
5864
+ }
5865
+ else if (previewMap) {
5866
+ for (const [indexPath, preview] of previewMap.entries()) {
5867
+ if (indexPath.isEqualToIndexPath(attributes.indexPath, {usingComparator: this.identityComparator})) {
5868
+ dropPreview = preview;
5869
+ break;
5870
+ }
5871
+ }
5872
+ }
5873
+ else {
5874
+ // If the data set did not provide the new index paths, assume that the default representation
5875
+ // was used and instead look for the object
5876
+ dropPreview = this._dropPreviews.find(p => p.item.itemOfType('default') == attributes.indexPath.object);
5877
+ }
5878
+
5879
+ return dropPreview;
5880
+ },
5136
5881
 
5137
5882
  /**
5138
5883
  * Should be invoked when the entire data set is updated in bulk.
@@ -5163,6 +5908,9 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5163
5908
  let resolveUpdateData;
5164
5909
  const dataUpdatePromise = new Promise(function (resolve, reject) {resolveUpdateData = resolve});
5165
5910
  this._dataUpdatePromise = dataUpdatePromise;
5911
+
5912
+ // Stop any in progress layout updates
5913
+ this._stopLayoutTransition();
5166
5914
 
5167
5915
  // Update the selection index paths
5168
5916
  this._updateSelectionIndexPaths();
@@ -5272,8 +6020,14 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5272
6020
  // Disable scrolling and interaction during update
5273
6021
  this._container[0].style.pointerEvents = 'none';
5274
6022
  var containerScrollOffset = this.scrollOffset;
5275
- if (!this.iScroll) this._container[0].scrollTo(containerScrollOffset.x, containerScrollOffset.y);
5276
- if (this.scrollView) this.scrollView.scrollingEnabled = NO;
6023
+
6024
+ if (!this.iScroll) {
6025
+ this._container[0].scrollTo(containerScrollOffset.x, containerScrollOffset.y);
6026
+ }
6027
+
6028
+ if (this.scrollView) {
6029
+ this.scrollView.scrollingEnabled = NO;
6030
+ }
5277
6031
 
5278
6032
  // Get the new content
5279
6033
  var attributes = this.layout.attributesForElementsInRect(this._bounds);
@@ -5284,6 +6038,12 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5284
6038
  var deletedCells = [];
5285
6039
  var insertedCells = [];
5286
6040
  var movingCells = [];
6041
+
6042
+ // Contains cells that are temporarily hidden due to a drop animation
6043
+ const sessionDropCells = [];
6044
+
6045
+ // Contains moving cells that have associated drop previews
6046
+ const dropMovingCells = [];
5287
6047
 
5288
6048
  var supplementaryViewsToInsert = this.layout.supplementaryViewsToInsert();
5289
6049
  var supplementaryViewsToDelete = this.layout.supplementaryViewsToDelete();
@@ -5297,6 +6057,20 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5297
6057
  var visibleRect = new BMRect(BMPointMake(this._bounds.origin.x + this._offscreenBufferSize, this._bounds.origin.y + this._offscreenBufferSize), this._frame.size.copy());
5298
6058
  var currentVisibleRect = new BMRect(BMPointMake(oldBounds.origin.x + this._offscreenBufferSize, oldBounds.origin.y + this._offscreenBufferSize), this._frame.size.copy());
5299
6059
 
6060
+ // If this update occurs because of a drag & drop operation, try to obtain the associated index paths from the data set
6061
+ let dropPreviewMap;
6062
+ if (this._dropPreviews && this.dataSet.indexPathForDragItem) {
6063
+ dropPreviewMap = new Map();
6064
+
6065
+ for (const preview of this._dropPreviews) {
6066
+ const indexPath = this.dataSet.indexPathForDragItem(preview.item);
6067
+
6068
+ if (indexPath) {
6069
+ dropPreviewMap.set(indexPath, preview);
6070
+ }
6071
+ }
6072
+ }
6073
+
5300
6074
  // Compare the current content to the new content and data set
5301
6075
  for (var i = 0; i < oldCells.length; i++) {
5302
6076
  var cell = oldCells[i];
@@ -5314,13 +6088,17 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5314
6088
  var attribute = attributes[j];
5315
6089
 
5316
6090
  // Skip cells with different item types
5317
- if (cell.itemType !== attribute.itemType) continue;
6091
+ if (cell.itemType !== attribute.itemType) {
6092
+ continue;
6093
+ }
5318
6094
 
5319
6095
  // Two cells refer to the same object if they are the same type and their index paths are loosely equal
5320
6096
  if (cell.indexPath.isLooselyEqualToIndexPath(attribute.indexPath, {usingComparator: this.identityComparator})) {
5321
6097
 
5322
6098
  // For supplementary views, it is also required that they have the same type identifier
5323
- if (cell.itemType === BMCollectionViewLayoutAttributesType.SupplementaryView && cell.reuseIdentifier !== attribute.identifier) continue;
6099
+ if (cell.itemType === BMCollectionViewLayoutAttributesType.SupplementaryView && cell.reuseIdentifier !== attribute.identifier) {
6100
+ continue;
6101
+ }
5324
6102
 
5325
6103
  // If they are equal, mark the cell as moving and continue
5326
6104
  cell.targetAttributes = attribute;
@@ -5330,17 +6108,35 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5330
6108
 
5331
6109
  // Instruct the data set to update this cell's content
5332
6110
  if (cell.itemType === BMCollectionViewLayoutAttributesType.Cell) {
5333
- if (this.dataSet.updateCell) this.dataSet.updateCell(cell, {atIndexPath: attribute.indexPath});
6111
+
6112
+ // If a drop preview exists for this cell, store it so that the drop is performed
6113
+ // at the appropriate position
6114
+ if (this._dropPreviews) {
6115
+ const dropPreview = this._dropPreviewForLayoutAttributes(cell.targetAttributes, {previewMap: dropPreviewMap});
6116
+
6117
+ if (dropPreview) {
6118
+ cell._dropPreview = dropPreview;
6119
+ dropMovingCells.push(cell);
6120
+ }
6121
+ }
6122
+
6123
+ if (this.dataSet.updateCell) {
6124
+ this.dataSet.updateCell(cell, {atIndexPath: attribute.indexPath});
6125
+ }
5334
6126
  }
5335
6127
  else if (cell.itemType === BMCollectionViewLayoutAttributesType.SupplementaryView) {
5336
- if (this.dataSet.updateSupplementaryView) this.dataSet.updateSupplementaryView(cell, {withIdentifier: cell.reuseIdentifier, atIndexPath: attribute.indexPath});
6128
+ if (this.dataSet.updateSupplementaryView) {
6129
+ this.dataSet.updateSupplementaryView(cell, {withIdentifier: cell.reuseIdentifier, atIndexPath: attribute.indexPath});
6130
+ }
5337
6131
  }
5338
6132
 
5339
6133
  break;
5340
6134
  }
5341
6135
  }
5342
6136
 
5343
- if (foundCell) continue;
6137
+ if (foundCell) {
6138
+ continue;
6139
+ }
5344
6140
 
5345
6141
  // If the cell wasn't found in the new layout, check to see if its object still exists in the data set
5346
6142
  if (cell.itemType === BMCollectionViewLayoutAttributesType.Cell) {
@@ -5351,9 +6147,22 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5351
6147
  var attribute = this.layout.attributesForCellAtIndexPath(indexPath);
5352
6148
  cell.targetAttributes = attribute;
5353
6149
  movingCells.push(cell);
6150
+
6151
+ // If a drop preview exists for this cell, store it so that the drop is performed
6152
+ // at the appropriate position
6153
+ if (this._dropPreviews) {
6154
+ const dropPreview = this._dropPreviewForLayoutAttributes(cell.targetAttributes, {previewMap: dropPreviewMap});
6155
+
6156
+ if (dropPreview) {
6157
+ cell._dropPreview = dropPreview;
6158
+ dropMovingCells.push(cell);
6159
+ }
6160
+ }
5354
6161
 
5355
6162
  // Instruct the data set to update this cell's content
5356
- if (this.dataSet.updateCell) this.dataSet.updateCell(cell, {atIndexPath: attribute.indexPath});
6163
+ if (this.dataSet.updateCell) {
6164
+ this.dataSet.updateCell(cell, {atIndexPath: attribute.indexPath});
6165
+ }
5357
6166
  }
5358
6167
  else {
5359
6168
  // Otherwise the cell was removed from the data set
@@ -5381,7 +6190,9 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5381
6190
  movingCells.push(cell);
5382
6191
 
5383
6192
  // Instruct the data set to update this supplementary view's content
5384
- if (this.dataSet.updateSupplementaryView) this.dataSet.updateSupplementaryView(cell, {withIdentifier: cell.reuseIdentifier, atIndexPath: attribute.indexPath});
6193
+ if (this.dataSet.updateSupplementaryView) {
6194
+ this.dataSet.updateSupplementaryView(cell, {withIdentifier: cell.reuseIdentifier, atIndexPath: attribute.indexPath});
6195
+ }
5385
6196
  }
5386
6197
  else {
5387
6198
  // Otherwise find its final attributes
@@ -5396,12 +6207,6 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5396
6207
 
5397
6208
  // Construct the cells for the new attributes
5398
6209
  var newCells = [];
5399
-
5400
- // If this update occurs because of a drag & drop operation, the collection view's viewport frame will be needed
5401
- var viewportFrame;
5402
- if (this._droppedShadows) {
5403
- viewportFrame = BMRectMakeWithNodeFrame(this._container[0]);
5404
- }
5405
6210
 
5406
6211
  // Compare the remaining new content to the old data set
5407
6212
  for (var i = 0; i < attributes.length; i++) {
@@ -5424,53 +6229,55 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5424
6229
  var oldAttribute = this.layout.initialAttributesForMovingCellFromIndexPath(oldIndexPath, {toIndexPath: attribute.indexPath, withTargetAttributes: attribute});
5425
6230
  attribute.initialAttributes = oldAttribute;
5426
6231
  //oldAttribute.indexPath = attribute.indexPath;
6232
+
6233
+ // If a drop preview exists for this cell, store it so that the drop is performed
6234
+ // at the appropriate position
6235
+ if (this._dropPreviews) {
6236
+ const dropPreview = this._dropPreviewForLayoutAttributes(cell.targetAttributes, {previewMap: dropPreviewMap});
6237
+
6238
+ if (dropPreview) {
6239
+ cell._dropPreview = dropPreview;
6240
+ dropMovingCells.push(cell);
6241
+ }
6242
+ }
5427
6243
  }
5428
6244
  else {
5429
6245
  // Otherwise this is a new element
5430
6246
 
5431
- // Check if there is a drop shadow associated with this element and transition from that shadow
5432
- let droppedShadow;
5433
- if (this._droppedShadows && (droppedShadow = this._droppedShadows.get(attribute.indexPath.object))) {
5434
- var initialAttributes = attribute.copy();
5435
-
5436
- // Figure out the viewport coordinates of the frame
5437
- let frame = attribute.frame.copy();
5438
- frame.offset(-this.scrollOffset.x, -this.scrollOffset.y);
5439
- frame.offset(viewportFrame.left, viewportFrame.top);
5440
-
5441
- // Figure out the frame of the drop shadow
5442
- // Because BMRectMakeWithNodeFrame takes transforms (including rotation) into account,
5443
- // the rotation is temporarily removed to improve the fidelity of the animation
5444
- BMHook(droppedShadow.node, {rotateZ: '0deg'});
5445
- let dropFrame = BMRectMakeWithNodeFrame(droppedShadow.node);
5446
- BMHook(droppedShadow.node, {rotateZ: droppedShadow.rotation + 'deg'});
5447
-
5448
- // Find the transform to be applied to the cell
5449
- let cellTransform = frame.rectWithTransformToRect(dropFrame);
5450
- // Find the reverse transform to be applied to the drop shadow and save it for the frame
5451
- let shadowTransform = dropFrame.rectWithTransformToRect(frame);
5452
- droppedShadow.transform = shadowTransform;
5453
-
5454
- // Offset the frame back into collection view coordinates
5455
- frame.offset(this.scrollOffset.x, this.scrollOffset.y);
5456
- frame.offset(-viewportFrame.left, -viewportFrame.top);
5457
-
5458
- // Apply the transform to the initial attributes
5459
- frame.origin.x += cellTransform.origin.x;
5460
- frame.origin.y += cellTransform.origin.y;
5461
-
5462
- initialAttributes.frame = frame;
5463
- initialAttributes.style.rotateZ = droppedShadow.rotation + 'deg';
5464
- initialAttributes.style.scaleX = cellTransform.size.width;
5465
- initialAttributes.style.scaleY = cellTransform.size.height;
5466
- initialAttributes.style.opacity = 0;
6247
+ // Check if there is a drop preview associated with this element and transition from that preview
6248
+ if (this._dropPreviews) {
6249
+ // Find if a newly inserted index path matches this cell's index path
6250
+ const dropPreview = this._dropPreviewForLayoutAttributes(attribute, {previewMap: dropPreviewMap});
6251
+
6252
+ let initialAttributes = attribute;
6253
+
6254
+ // If a preferred scroll offset is provided by the layout, displace the cell initially so that it
6255
+ // starts out at the position it will be in at the end of the scroll animation, relative to the viewport
6256
+ const displacement = BMPointMake(
6257
+ preferredScrollOffset.x - scrollOffset.x,
6258
+ preferredScrollOffset.y - scrollOffset.y
6259
+ );
6260
+
6261
+ if (displacement.x || displacement.y) {
6262
+ initialAttributes = attribute.copy();
6263
+ initialAttributes.frame = initialAttributes.frame.copy();
6264
+ initialAttributes.frame.offset(displacement.x, displacement.y);
6265
+ }
6266
+
6267
+ if (dropPreview) {
6268
+ newCell._dropPreview = dropPreview;
6269
+ attribute.initialAttributes = initialAttributes;
6270
+ insertedCells.push(newCell);
6271
+ }
6272
+ else {
6273
+ const initialAttributes = this.layout.initialAttributesForAppearingCellAtIndexPath(attribute.indexPath, {withTargetAttributes: attribute});
6274
+ attribute.initialAttributes = initialAttributes;
6275
+ }
5467
6276
  }
5468
6277
  else {
5469
6278
  var initialAttributes = this.layout.initialAttributesForAppearingCellAtIndexPath(attribute.indexPath, {withTargetAttributes: attribute});
6279
+ attribute.initialAttributes = initialAttributes;
5470
6280
  }
5471
-
5472
- attribute.initialAttributes = initialAttributes;
5473
- //initialAttributes.indexPath = attribute.indexPath;
5474
6281
  }
5475
6282
 
5476
6283
  newCell.attributes = attribute.initialAttributes;
@@ -5596,7 +6403,14 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5596
6403
  animationCells[i].release();
5597
6404
 
5598
6405
  // Additionally, unmanage hidden cells
5599
- if (animationCells[i].attributes.isHidden) animationCells[i]._unmanage();
6406
+ if (animationCells[i].attributes.isHidden) {
6407
+ animationCells[i]._unmanage();
6408
+ }
6409
+ }
6410
+
6411
+ // Remove the hiding class from inserted cells
6412
+ for (const cell of sessionDropCells) {
6413
+ cell.node.classList.remove('BMCollectionViewCellInserting');
5600
6414
  }
5601
6415
 
5602
6416
  // Destroy the deleted cells
@@ -5629,56 +6443,6 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5629
6443
 
5630
6444
  };
5631
6445
 
5632
-
5633
-
5634
- // Apply the new scroll offset, if it changed
5635
- /*if (oldBounds.origin.x != self._bounds.origin.x || oldBounds.origin.y != self._bounds.origin.y) {
5636
-
5637
- if (self.scrollView) {
5638
- var currentOffset = self.scrollView.offset.copy();
5639
- var targetOffset = self._bounds.origin.copy();
5640
-
5641
- targetOffset.x += self._offscreenBufferSize;
5642
- targetOffset.y += self._offscreenBufferSize;
5643
-
5644
- animationOptions.progress = function (elements, fraction) {
5645
- var offsetX = currentOffset.x + fraction * (targetOffset.x - currentOffset.x);
5646
- var offsetY = currentOffset.y + fraction * (targetOffset.y - currentOffset.y);
5647
-
5648
- self.scrollView.offset = BMPointMake(offsetX, offsetY);
5649
- }
5650
- }
5651
- else if (self.iScroll) {
5652
- var currentOffset = scrollOffset;
5653
- var targetOffset = self._bounds.origin.copy();
5654
-
5655
- targetOffset.x += self._offscreenBufferSize;
5656
- targetOffset.y += self._offscreenBufferSize;
5657
-
5658
- animationOptions.progress = function (elements, fraction) {
5659
- var offsetX = currentOffset.x + fraction * (targetOffset.x - currentOffset.x);
5660
- var offsetY = currentOffset.y + fraction * (targetOffset.y - currentOffset.y);
5661
-
5662
- self.iScroll._translate(-offsetX, -offsetY);
5663
- }
5664
- }
5665
- else {
5666
- var currentOffset = scrollOffset;
5667
- var targetOffset = self._bounds.origin.copy();
5668
-
5669
- targetOffset.x += self._offscreenBufferSize;
5670
- targetOffset.y += self._offscreenBufferSize;
5671
-
5672
- animationOptions.progress = function (elements, fraction) {
5673
- var offsetX = currentOffset.x + fraction * (targetOffset.x - currentOffset.x);
5674
- var offsetY = currentOffset.y + fraction * (targetOffset.y - currentOffset.y);
5675
-
5676
- self._container[0].scrollTo(offsetX, offsetY);
5677
- }
5678
- }
5679
-
5680
- }*/
5681
-
5682
6446
  // All cells involved in the animation will be retained for the duration of the animation
5683
6447
  // And then released at the end
5684
6448
  var animationCells = [];
@@ -5696,26 +6460,65 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5696
6460
  }
5697
6461
 
5698
6462
  // Apply the drop shadow animations if needed
5699
- if (self._droppedShadows) {
5700
- self._droppedShadows.forEach(shadow => {
5701
- if (shadow.transform) {
5702
- BMHook(shadow.node, {rotateZ: shadow.rotation + 'deg', translateX: '0px', translateY: '0px'});
5703
- let controller = BMAnimationContextGetCurrent().controllerForObject(shadow, {node: shadow.node});
5704
- controller.registerBuiltInProperty('translateX', {withValue: shadow.transform.origin.x + 'px'});
5705
- controller.registerBuiltInProperty('translateY', {withValue: shadow.transform.origin.y + 'px'});
5706
- controller.registerBuiltInProperty('scaleX', {withValue: shadow.transform.size.width});
5707
- controller.registerBuiltInProperty('scaleY', {withValue: shadow.transform.size.height});
5708
- controller.registerBuiltInProperty('rotateZ', {withValue: '0deg'});
5709
- controller.registerBuiltInProperty('opacity', {withValue: 0});
6463
+ if (self._dropPreviews) {
6464
+ for (const cell of insertedCells) {
6465
+ cell._dropPreview.performDropToNode(cell.node);
6466
+
6467
+ const index = self._dropPreviews.indexOf(cell._dropPreview);
6468
+
6469
+ if (index != -1) {
6470
+ self._dropPreviews.splice(index, 1);
5710
6471
  }
5711
- else {
5712
- BMHook(shadow.node, {rotateZ: shadow.rotation + 'deg'});
5713
- let controller = BMAnimationContextGetCurrent().controllerForObject(shadow, {node: shadow.node});
5714
- controller.registerBuiltInProperty('scaleX', {withValue: .33});
5715
- controller.registerBuiltInProperty('scaleY', {withValue: .33});
5716
- controller.registerBuiltInProperty('opacity', {withValue: 0});
6472
+ }
6473
+
6474
+ if (dropMovingCells.length) {
6475
+ // For the items that move, move them instantly to their final positions start the drop
6476
+ // animations, then move them back for the regular animation to play for them
6477
+ BMAnimationContextBeginStatic();
6478
+ const initialAttributeMap = new Map();
6479
+ for (const cell of dropMovingCells) {
6480
+ // Instantly move each cell to its final attributes
6481
+ initialAttributeMap.set(cell, cell.attributes);
6482
+ cell.attributes = cell.targetAttributes;
5717
6483
  }
5718
- });
6484
+ BMAnimationApply();
6485
+
6486
+ // Play all drop animations
6487
+ for (const cell of dropMovingCells) {
6488
+ cell._dropPreview.performDropToNode(cell.node);
6489
+ const index = self._dropPreviews.indexOf(cell._dropPreview);
6490
+
6491
+ if (index != -1) {
6492
+ self._dropPreviews.splice(index, 1);
6493
+ }
6494
+ }
6495
+
6496
+ // Revert each cell to its staging attributes
6497
+ BMAnimationContextBeginStatic();
6498
+ for (const [cell, attributes] of initialAttributeMap) {
6499
+ cell.attributes = attributes;
6500
+ }
6501
+ BMAnimationApply();
6502
+ }
6503
+
6504
+ // For remaining drop previews, play an animation to the target cell if available
6505
+ if (self._cellItemMap) {
6506
+ for (let i = 0; i < self._dropPreviews.length; i++) {
6507
+ const item = self._dropPreviews[i];
6508
+ const cell = self._cellItemMap.get(item);
6509
+
6510
+ if (cell) {
6511
+ self._dropPreviews.performDropToNode(cell.node);
6512
+ self._dropPreviews.splice(i, 1);
6513
+ i--;
6514
+ }
6515
+ }
6516
+ }
6517
+
6518
+ // Play a generic animation for all remaining drop previews without associated cells
6519
+ for (const preview of self._dropPreviews) {
6520
+ preview.performDrop();
6521
+ }
5719
6522
  }
5720
6523
 
5721
6524
  // Apply the new scroll offset, if it changed
@@ -5766,7 +6569,15 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5766
6569
  var cell = newCells[i];
5767
6570
  if (!cell.animatable) continue;
5768
6571
 
5769
- cell.attributes = cell.targetAttributes;
6572
+ if (cell._dropPreview) {
6573
+ cell._dropPreview = undefined;
6574
+ cell.node.classList.add('BMCollectionViewCellInserting');
6575
+ sessionDropCells.push(cell);
6576
+ }
6577
+ else {
6578
+ cell.attributes = cell.targetAttributes;
6579
+ }
6580
+
5770
6581
  cell.animatable = NO;
5771
6582
 
5772
6583
  animationCells.push(cell.retain());
@@ -5803,6 +6614,11 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5803
6614
  if (BMAnimationContextGetCurrent()) {
5804
6615
  animationBlock();
5805
6616
  BMAnimationContextAddCompletionHandler(animationOptions.complete);
6617
+
6618
+ // Clear out all target attributes on all cell after the animation starts
6619
+ for (const cell of this.allCells) {
6620
+ cell.targetAttributes = undefined;
6621
+ }
5806
6622
  }
5807
6623
  else {
5808
6624
  BMAnimateWithBlock(animationBlock, animationOptions);
@@ -5812,7 +6628,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5812
6628
  this.oldBounds = undefined;
5813
6629
 
5814
6630
  // If no cells should be changed, immediately run the completion handler
5815
- if (newCells.length == 0 && oldCells.length == 0) {
6631
+ if (newCells.length == 0 && oldCells.length == 0 && !this._dropPreviews) {
5816
6632
  animationOptions.complete();
5817
6633
  }
5818
6634
 
@@ -5997,9 +6813,19 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
5997
6813
  // Scroll to the offset point
5998
6814
  if (options.animated) {
5999
6815
  var self = this;
6000
- return BMAnimateWithBlock(_ => {
6001
- self.scrollOffset = offset;
6002
- }, {duration: 300});
6816
+ const currentContext = BMAnimationContextGetCurrent();
6817
+ if (!currentContext) {
6818
+ BMAnimationBeginWithDuration(300);
6819
+ }
6820
+
6821
+ self.scrollOffset = offset;
6822
+
6823
+ if (!currentContext) {
6824
+ return BMAnimationApply();
6825
+ }
6826
+ else {
6827
+ return new Promise(resolve => BMAnimationContextAddCompletionHandler(resolve));
6828
+ }
6003
6829
  }
6004
6830
  else {
6005
6831
  // Scrolling is performed differently based on whether iScroll is used or not
@@ -6282,6 +7108,7 @@ BMCollectionView.prototype = BMExtend(BM_COLLECTION_VIEW_USE_BMVIEW_SUBCLASS ? O
6282
7108
  var self = this;
6283
7109
 
6284
7110
  _BMCollectionViews.delete(this);
7111
+ BMDragSession._dropTargets.delete(this);
6285
7112
 
6286
7113
  // Unbind the scroll event listener
6287
7114
  if (this.initialized && !this.iScroll) {
@@ -6419,8 +7246,8 @@ class BMCollectionViewDataSourceAdapter {
6419
7246
  return this._dataSet.moveItemsFromIndexPaths(indexPaths, args);
6420
7247
  }
6421
7248
 
6422
- collectionViewRemoveItemsAtIndexPaths(collectionView, indexPaths) {
6423
- return this._dataSet.removeItemsAtIndexPaths(indexPaths);
7249
+ collectionViewRemoveItemsAtIndexPaths(collectionView, indexPaths, args) {
7250
+ return this._dataSet.removeItemsAtIndexPaths(indexPaths, args);
6424
7251
  }
6425
7252
 
6426
7253
  collectionViewInsertItems(collectionView, items, args) {
@@ -6436,6 +7263,18 @@ class BMCollectionViewDataSourceAdapter {
6436
7263
  };
6437
7264
  }
6438
7265
 
7266
+ if (dataSet.dragItemForIndexPath) {
7267
+ adapter.collectionViewDragItemForIndexPath = function (collectionView, indexPath, args) {
7268
+ return dataSet.dragItemForIndexPath(indexPath, args);
7269
+ };
7270
+ }
7271
+
7272
+ if (dataSet.indexPathForDragItem) {
7273
+ adapter.collectionViewIndexPathForDragItem = function (collectionView, item, args) {
7274
+ return dataSet.indexPathForDragItem(item, args);
7275
+ };
7276
+ }
7277
+
6439
7278
  return adapter;
6440
7279
  }
6441
7280
  }
@@ -6472,30 +7311,14 @@ class BMCollectionViewDataSetAdapter {
6472
7311
  return this._dataSource.collectionViewIndexPathForObject(this._collectionView, object);
6473
7312
  }
6474
7313
 
6475
- contentsForCellWithReuseIdentifier(identifier) {
6476
- throw new Error(`contentsForCellWithReuseIdentifier is unsupported when using data source objects`);
6477
- }
6478
-
6479
7314
  cellForItemAtIndexPath(indexPath) {
6480
7315
  return this._dataSource.collectionViewCellForItemAtIndexPath(this._collectionView, indexPath);
6481
7316
  }
6482
7317
 
6483
- contentsForSupplementaryViewWithIdentifier(identifier) {
6484
- throw new Error(`contentsForSupplementaryViewWithIdentifier is unsupported when using data source objects`);
6485
- }
6486
-
6487
7318
  cellForSupplementaryViewWithIdentifier(identifier, args) {
6488
7319
  return this._dataSource.collectionViewCellForSupplementaryViewWithIdentifier(this._collectionView, identifier, args);
6489
7320
  }
6490
7321
 
6491
- updateCell(cell, {atIndexPath: indexPath}) {
6492
- throw new Error(`updateCell is unsupported when using data source objects`);
6493
- }
6494
-
6495
- updateSupplementaryView(view, {withIdentifier: identifier, atIndexPath: indexPath}) {
6496
- throw new Error(`updateSupplementaryView is unsupported when using data source objects`);
6497
- }
6498
-
6499
7322
  useOldData(use) {
6500
7323
  return this._dataSource.collectionViewUseOldData(this._collectionView, use);
6501
7324
  }
@@ -6512,8 +7335,8 @@ class BMCollectionViewDataSetAdapter {
6512
7335
  return this._dataSource.collectionViewMoveItemsFromIndexPaths(this._collectionView, indexPaths, args);
6513
7336
  }
6514
7337
 
6515
- removeItemsAtIndexPaths(indexPaths) {
6516
- return this._dataSource.collectionViewRemoveItemsAtIndexPaths(this._collectionView, indexPaths);
7338
+ removeItemsAtIndexPaths(indexPaths, args) {
7339
+ return this._dataSource.collectionViewRemoveItemsAtIndexPaths(this._collectionView, indexPaths, args);
6517
7340
  }
6518
7341
 
6519
7342
  insertItems(items, args) {
@@ -6529,6 +7352,18 @@ class BMCollectionViewDataSetAdapter {
6529
7352
  };
6530
7353
  }
6531
7354
 
7355
+ if (dataSource.collectionViewDragItemForIndexPath) {
7356
+ adapter.dragItemForIndexPath = function (indexPath, args) {
7357
+ return dataSource.collectionViewDragItemForIndexPath(adapter._collectionView, indexPath, args);
7358
+ };
7359
+ }
7360
+
7361
+ if (dataSource.collectionViewIndexPathForDragItem) {
7362
+ adapter.indexPathForDragItem = function (item, args) {
7363
+ return dataSource.collectionViewIndexPathForDragItem(adapter._collectionView, item, args);
7364
+ };
7365
+ }
7366
+
6532
7367
  return adapter;
6533
7368
  }
6534
7369
  }