bm-core-ui 2.10.5 → 2.11.1

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.
@@ -114,6 +114,39 @@ BMJQueryShim.shimWithDOMNode = function (node) {
114
114
  // When set to YES, this will cause collection view cells to extend view
115
115
  const BM_USE_BMVIEW_SUBCLASS = YES;
116
116
 
117
+ // @type BMCollectionViewCellReuseState
118
+
119
+ /**
120
+ * Constants describing the various reuse states that a collection view cell may be in.
121
+ */
122
+ export const BMCollectionViewCellReuseState = Object.freeze({ // <enum>
123
+
124
+ /**
125
+ * Indicates that this cell is currently bound to a data source object and retained
126
+ * or managed by the collection view.
127
+ */
128
+ InUse: "InUse", // <enum>
129
+
130
+ /**
131
+ * Indicates that this cell is not currently in use, but may be reused in the future.
132
+ */
133
+ InQueue: "InQueue", // <enum>
134
+
135
+ /**
136
+ * Indicates that this cell is in the process of being deleted and is bound to a data
137
+ * source object that no longer has a valid index path.
138
+ */
139
+ Deleting: "Deleting", // <enum>
140
+
141
+ /**
142
+ * Indicates that this cell is not currently in use and cannot be reused in the future.
143
+ * References to cells in this state should be cleared as they are no longer valid.
144
+ */
145
+ Discarded: "Discarded" // <enum>
146
+ });
147
+
148
+ // @endtype
149
+
117
150
  // @type BMCollectionViewCell extends BMView
118
151
 
119
152
  /**
@@ -156,16 +189,13 @@ BMCollectionViewCell.prototype = BMExtend(BM_USE_BMVIEW_SUBCLASS ? Object.create
156
189
  return this.retainCount > 0;
157
190
  },
158
191
 
159
- // #FLAG BM_USE_BMVIEW_SUBCLASS
160
192
  get _frame() {
161
193
  return this._attributes && this._attributes.frame;
162
194
  },
163
195
 
164
- // #FLAG BM_USE_BMVIEW_SUBCLASS
165
196
  get frame() {
166
197
  return this._attributes && this._attributes.frame;
167
198
  },
168
- // #FLAG BM_USE_BMVIEW_SUBCLASS
169
199
  set frame(frame) {
170
200
  // NOTE: A cell's frame is controlled by its layout attributes, therefore this operation is a no-op
171
201
  },
@@ -192,12 +222,6 @@ BMCollectionViewCell.prototype = BMExtend(BM_USE_BMVIEW_SUBCLASS ? Object.create
192
222
  */
193
223
  _element: undefined, // <BMJQueryShim>
194
224
  get element() { return this._element || (this._element = BMJQueryShim.shimWithDOMNode(this.node)); },
195
-
196
- // #FLAG BM_USE_BMVIEW_SUBCLASS
197
- /**
198
- * The DOM node managed by this cell.
199
- */
200
- //node: undefined, // <DOMNode>
201
225
 
202
226
  /**
203
227
  * The type of item this cell represents.
@@ -206,7 +230,8 @@ BMCollectionViewCell.prototype = BMExtend(BM_USE_BMVIEW_SUBCLASS ? Object.create
206
230
  itemType: BMCollectionViewLayoutAttributesType.Cell, // <BMCollectionViewLayoutAttributesType>
207
231
 
208
232
  /**
209
- * The cell's reuse identifier which corresponds to the template's reuse identifier.
233
+ * The cell's reuse identifier which controls when this cell is reused when data source objects
234
+ * request cell instances from the associated collection view.
210
235
  */
211
236
  _reuseIdentifier: undefined, // <String>
212
237
 
@@ -396,10 +421,8 @@ BMCollectionViewCell.prototype = BMExtend(BM_USE_BMVIEW_SUBCLASS ? Object.create
396
421
  recycle: function () {
397
422
  this._unmanage();
398
423
 
399
- this.invalidate();
400
-
401
- //this.collectionView.cellWasUnmanaged(this);
402
424
  if (this.retainCount > 0) {
425
+ this.invalidate();
403
426
  this.collectionView.cellWasInvalidated(this);
404
427
  this.destroy();
405
428
 
@@ -422,9 +445,8 @@ BMCollectionViewCell.prototype = BMExtend(BM_USE_BMVIEW_SUBCLASS ? Object.create
422
445
  releaseRecycledCell: function () {
423
446
  this.release();
424
447
 
425
- this.invalidate();
426
-
427
448
  if (this.retainCount > 0) {
449
+ this.invalidate();
428
450
  this.collectionView.cellWasInvalidated(this);
429
451
  this.destroy();
430
452
 
@@ -438,6 +460,15 @@ BMCollectionViewCell.prototype = BMExtend(BM_USE_BMVIEW_SUBCLASS ? Object.create
438
460
  }
439
461
  },
440
462
 
463
+ /**
464
+ * The cell's current reuse state.
465
+ */
466
+ _reuseState: BMCollectionViewCellReuseState.InQueue, // <BMCollectionViewCellReuseState>
467
+
468
+ get reuseState() {
469
+ return this._reuseState;
470
+ },
471
+
441
472
  /**
442
473
  * Should be set to YES to hide the cell, or NO to reveal it.
443
474
  */
@@ -475,7 +506,6 @@ BMCollectionViewCell.prototype = BMExtend(BM_USE_BMVIEW_SUBCLASS ? Object.create
475
506
  this.node.classList.remove('BMCollectionViewCellHidden');
476
507
  },
477
508
 
478
- // #FLAG BM_USE_BMVIEW_SUBCLASS
479
509
  _prepareForAutomaticIntrinsicSize() {
480
510
  for (let subview of this._subviews) {
481
511
  subview._prepareForAutomaticIntrinsicSize();
@@ -489,27 +519,25 @@ BMCollectionViewCell.prototype = BMExtend(BM_USE_BMVIEW_SUBCLASS ? Object.create
489
519
  }
490
520
  else {
491
521
  if (this._requiredWidth) {
492
- // Otherwise derive the measured size using the instrinsic CSS size
522
+ // Otherwise derive the measured size using the intrinsic CSS size
493
523
  BMCopyProperties(this._node.style, {width: this._requiredWidth + 'px', height: 'auto'});
494
524
  }
495
525
  else {
496
- // Otherwise derive the measured size using the instrinsic CSS size
526
+ // Otherwise derive the measured size using the intrinsic CSS size
497
527
  BMCopyProperties(this._node.style, {width: 'auto', height: 'auto'});
498
528
  }
499
529
  }
500
530
  }
501
531
  },
502
532
 
503
- // #FLAG BM_USE_BMVIEW_SUBCLASS
504
533
  _supportsAutomaticIntrinsicSize: NO,
505
534
 
506
- // #FLAG BM_USE_BMVIEW_SUBCLASS
507
535
  /**
536
+ * @protected
508
537
  * Temporarily set to `YES` while this cell is being measured.
509
538
  */
510
539
  _isMeasuring: NO,
511
540
 
512
- // #FLAG BM_USE_BMVIEW_SUBCLASS
513
541
  internalConstraints() {
514
542
  var constraints = [];
515
543
 
@@ -550,19 +578,19 @@ BMCollectionViewCell.prototype = BMExtend(BM_USE_BMVIEW_SUBCLASS ? Object.create
550
578
  /**
551
579
  * Invoked prior to this cell being collected for reuse by the collection view.
552
580
  * Subclasses can override this method to perform any changes necessary for reusing this cell.
553
- * Subclasses should invoke the superclass method at some point in their implementation.
581
+ * Subclasses must invoke the superclass method at some point in their implementation.
554
582
  */
555
583
  prepareForReuse() {
556
-
584
+ this._reuseState = BMCollectionViewCellReuseState.InQueue;
557
585
  },
558
586
 
559
587
  /**
560
588
  * Invoked prior to this cell being shown on the screen either after being initialized or after having been previously collected.
561
- * Subclasses can override this method to perform any changes necesarry for displaying this cell.
562
- * Subclasses should invoke the superclass method at some point in their implementaiton.
589
+ * Subclasses can override this method to perform any changes necessary for displaying this cell.
590
+ * Subclasses must invoke the superclass method at some point in their implementation.
563
591
  */
564
592
  prepareForDisplay() {
565
-
593
+ this._reuseState = BMCollectionViewCellReuseState.InUse;
566
594
  },
567
595
 
568
596
  /**
@@ -625,7 +653,6 @@ BMCollectionViewCell.prototype = BMExtend(BM_USE_BMVIEW_SUBCLASS ? Object.create
625
653
 
626
654
  this.collectionView = collectionView;
627
655
 
628
- // #FLAG BM_USE_BMVIEW_SUBCLASS
629
656
  if (BM_USE_BMVIEW_SUBCLASS) {
630
657
  BMView.prototype.initWithDOMNode.call(this, args.node);
631
658
  }
@@ -41,6 +41,7 @@ export var BMCollectionViewTableLayoutSupplementaryView = Object.freeze({ // <en
41
41
 
42
42
  /**
43
43
  * @deprecated Use `BMCollectionViewFlowLayout` with the `maximumCellsPerRow` property set to `1`.
44
+ *
44
45
  * --------------------------------------
45
46
  * A basic layout implementation, the BMCollectionViewTableLayout will lay out its elements as a list where each row can have
46
47
  * either a fixed height or a variable height.
@@ -4964,7 +4965,7 @@ BMCollectionViewFlowLayout.prototype = BMExtend(Object.create(BMCollectionViewLa
4964
4965
  * @return <BMCollectionViewFlowLayout> A flow layout.
4965
4966
  */
4966
4967
  BMCollectionViewFlowLayout.flowLayout = function () {
4967
- return new BMCollectionViewFlowLayout();
4968
+ return new this();
4968
4969
  }
4969
4970
 
4970
4971
  // @endtype
@@ -625,7 +625,7 @@ BMCollectionViewMasonryLayout.prototype = BMExtend({}, BMCollectionViewLayout.pr
625
625
  * @returns <BMCollectionViewMasonryLayout> A masonry layout.
626
626
  */
627
627
  BMCollectionViewMasonryLayout.masonryLayout = function () {
628
- return new BMCollectionViewMasonryLayout();
628
+ return new this();
629
629
  };
630
630
 
631
631
  // @endtype
@@ -628,7 +628,7 @@ BMCollectionViewStackLayout.prototype = BMExtend({}, BMCollectionViewLayout.prot
628
628
  * @returns <BMCollectionViewStackLayout> A stack layout.
629
629
  */
630
630
  BMCollectionViewStackLayout.stackLayout = function () {
631
- return new BMCollectionViewStackLayout();
631
+ return new this();
632
632
  }
633
633
 
634
634
  // @endtype
@@ -1260,7 +1260,7 @@ BMCollectionViewTileLayout.prototype = BMExtend({}, BMCollectionViewLayout.proto
1260
1260
  * @return <BMCollectionViewTileLayout> A tile layout.
1261
1261
  */
1262
1262
  BMCollectionViewTileLayout.tileLayout = function () {
1263
- return new BMCollectionViewTileLayout();
1263
+ return new this();
1264
1264
  };
1265
1265
 
1266
1266
  // @endtype
@@ -1075,6 +1075,147 @@ BMWindow.prototype = BMExtend(Object.create(BMView.prototype), {
1075
1075
  return this;
1076
1076
  },
1077
1077
 
1078
+ /**
1079
+ * Starts a drag event from the specified `mousedown` or `touchstart` event that moves this window.
1080
+ * When this method is invoked, further mouse and touch events will be blocked until the drag finishes.
1081
+ *
1082
+ * If there is already a drag operation in progress or if this window is full screen or a modal window,
1083
+ * this method has no effect.
1084
+ * @param event <MouseEvent | TouchEvent> The `mousedown` or `touchstart` event from which to begin
1085
+ * the window movement.
1086
+ */
1087
+ performDragWithEvent(event) {
1088
+ if (event.type == 'mousedown') {
1089
+ this.performDragWithMouseEvent(event);
1090
+ }
1091
+ else if (event.type == 'touchstart') {
1092
+ this.performDragWithTouchEvent(event);
1093
+ }
1094
+ },
1095
+
1096
+ /**
1097
+ * Starts a drag event from the specified `mousedown` event that will move this window.
1098
+ * When this method is invoked, further mouse events will be blocked until the drag finishes.
1099
+ *
1100
+ * If there is already a drag operation in progress or if this window is full screen or a modal window,
1101
+ * this method has no effect.
1102
+ * @param event <MouseEvent | TouchEvent> The `mousedown` event from which to begin the window movement.
1103
+ */
1104
+ performDragWithMouseEvent(event) {
1105
+ // Full screen and modal windows cannot be moved
1106
+ if (this._fullScreen) return;
1107
+
1108
+ this._dragged = YES;
1109
+
1110
+ this._position = BMPointMake(this.node.offsetLeft, this.node.offsetTop);
1111
+ let lastPosition = BMPointMake(event.clientX, event.clientY);
1112
+
1113
+ let mouseMoveEventListener = event => {
1114
+ let position = BMPointMake(event.clientX, event.clientY);
1115
+ const newPosition = BMPointMake(this._position.x + position.x - lastPosition.x, this._position.y + position.y - lastPosition.y);
1116
+
1117
+ // Check if the window can move
1118
+ let canMove = YES;
1119
+ if (this.delegate && this.delegate.windowShouldMove) {
1120
+ canMove = this.delegate.windowShouldMove(this, newPosition);
1121
+ }
1122
+ if (!canMove) return;
1123
+
1124
+ this.leftConstraint.constant = this._position.x + position.x - lastPosition.x;
1125
+ this.topConstraint.constant = this._position.y + position.y - lastPosition.y;
1126
+ this._position = newPosition;
1127
+ lastPosition = position;
1128
+ this.layout();
1129
+ event.preventDefault();
1130
+ };
1131
+
1132
+ let mouseUpEventListener = event => {
1133
+ window.removeEventListener('mousemove', mouseMoveEventListener, YES);
1134
+ window.removeEventListener('mouseup', mouseUpEventListener, YES);
1135
+ }
1136
+
1137
+ window.addEventListener('mousemove', mouseMoveEventListener, YES);
1138
+ window.addEventListener('mouseup', mouseUpEventListener, YES);
1139
+
1140
+ event.preventDefault();
1141
+ },
1142
+
1143
+ /**
1144
+ * The identifier of the touch point that is tracked during a drag operation. `undefined`
1145
+ * when not tracking a drag.
1146
+ */
1147
+ _touchDragPoint: undefined, // <String>
1148
+
1149
+ /**
1150
+ * Starts a drag event from the specified `touchstart` event that will move this window.
1151
+ * When this method is invoked, further touch events will be blocked until the drag finishes.
1152
+ *
1153
+ *
1154
+ * If there is already a drag operation in progress or if this window is full screen or a modal window,
1155
+ * this method has no effect.
1156
+ * @param event <MouseEvent | TouchEvent> The `touchstart` event from which to begin the window movement.
1157
+ */
1158
+ performDragWithTouchEvent(event) {
1159
+ // If there is already a drag in progress, don't process this new event
1160
+ if (typeof this._touchDragPoint !== 'undefined') {
1161
+ return;
1162
+ }
1163
+
1164
+ // Full screen windows cannot be moved
1165
+ if (this._fullScreen) return;
1166
+
1167
+ // Only use the first touch point
1168
+ this._touchDragPoint = event.changedTouches[0].identifier;
1169
+ this._dragged = YES;
1170
+
1171
+ this._position = BMPointMake(this.node.offsetLeft, this.node.offsetTop);
1172
+ let lastPosition = BMPointMake(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
1173
+
1174
+ let mouseMoveEventListener = event => {
1175
+ // Look for the actively tracked touch point
1176
+ let touch;
1177
+ for (let changedTouch of event.changedTouches) {
1178
+ if (changedTouch.identifier == this._touchDragPoint) {
1179
+ touch = changedTouch;
1180
+ break;
1181
+ }
1182
+ }
1183
+
1184
+ // If the actively tracked touch point did not move, do not process this event
1185
+ if (!touch) return;
1186
+
1187
+ let position = BMPointMake(touch.clientX, touch.clientY);
1188
+ const newPosition = BMPointMake(this._position.x + position.x - lastPosition.x, this._position.y + position.y - lastPosition.y);
1189
+
1190
+ // Check if the window can move
1191
+ let canMove = YES;
1192
+ if (this.delegate && this.delegate.windowShouldMove) {
1193
+ canMove = this.delegate.windowShouldMove(this, newPosition);
1194
+ }
1195
+ if (!canMove) return;
1196
+
1197
+ this.leftConstraint.constant = this._position.x + position.x - lastPosition.x;
1198
+ this.topConstraint.constant = this._position.y + position.y - lastPosition.y;
1199
+ this._position = newPosition;
1200
+ lastPosition = position;
1201
+ this.layout();
1202
+ event.preventDefault();
1203
+ };
1204
+
1205
+ let mouseUpEventListener = event => {
1206
+ this._touchDragPoint = undefined;
1207
+ window.removeEventListener('touchmove', mouseMoveEventListener);
1208
+ window.removeEventListener('touchend', mouseUpEventListener);
1209
+ window.removeEventListener('touchcancel', mouseUpEventListener);
1210
+ }
1211
+
1212
+ window.addEventListener('touchmove', mouseMoveEventListener);
1213
+ window.addEventListener('touchend', mouseUpEventListener);
1214
+ window.removeEventListener('touchcancel', mouseUpEventListener);
1215
+
1216
+ event.preventDefault();
1217
+ },
1218
+
1078
1219
  // @override - BMView
1079
1220
  colorSchemeDidChange(scheme) {
1080
1221
  BMView.prototype.colorSchemeDidChange.apply(this, arguments);
@@ -625,6 +625,11 @@ BMAnimationContext.prototype = {
625
625
 
626
626
  }
627
627
 
628
+ /**
629
+ * A map having DOM nodes as keys which contains delayed animations that have yet to start.
630
+ */
631
+ BMAnimationContext._pendingAnimations = new Map; // Map<DOMNode, unknown[]>
632
+
628
633
  /**
629
634
  * The animation stack holds all animations with their attributes.
630
635
  * Whenever an attribute property is changed while there is an active animation, that property
@@ -756,6 +761,37 @@ export function BMAnimationApply() {
756
761
  // Set to `YES` if the environment does not fully support web animations
757
762
  var BM_WEB_ANIMATIONS_DISABLED = NO;
758
763
 
764
+
765
+ /**
766
+ * Stops all current animations for the specified node on the properties that
767
+ * are specified in an animation that is about to start.
768
+ * @param node <DOMNode> The DOM node on which the animation is about to start.
769
+ * {
770
+ * @param withProperties <Dictionary<unknown>> The properties that are about to be animated.
771
+ * @param options <Dictionary<unknown>> The options of the animation that is about to start.
772
+ * }
773
+ */
774
+ BMAnimationContext._prepareAnimationForNode = function (node, {withProperties: properties, options}) {
775
+ const velocity = window.Velocity || window.$?.Velocity;
776
+ if (!velocity) {
777
+ return;
778
+ }
779
+
780
+ // Find all animations targeting the specified node
781
+ const nodeAnimations = velocity.State.calls.filter(c => c[1]?.[0] == node);
782
+
783
+ // For each animation clear out the properties that are about to be animated, except for the tween property
784
+ for (const call of nodeAnimations) {
785
+ for (const property in properties) {
786
+ if (property == 'element' || property == 'tween') {
787
+ continue;
788
+ }
789
+
790
+ delete call[0]?.[0]?.[property];
791
+ }
792
+ }
793
+ }
794
+
759
795
  /**
760
796
  * Must be invoked after a <code>BMAnimationBeginWithDuration()</code> call to apply the pending animation.
761
797
  * Applying the animation in this way will stop all other running animations on the animation targets.
@@ -849,7 +885,13 @@ export function BMAnimationApplyBlocking(blocking) {
849
885
  for (var i = 0; i < animationTargetsLength; i++) {
850
886
  var element = animationTargets[i].element;
851
887
 
852
- if (blocking) velocity(element, 'stop', true);
888
+ if (blocking && velocity) {
889
+ // velocity(element, 'stop', true);
890
+ }
891
+
892
+ // Web animations are used if enabled specifically for this animation context, enabled
893
+ // globally via the build flag and fully supported by the browser
894
+ const useWebAnimations = (context._useWebAnimations || BM_USE_WEB_ANIMATIONS) && document.body.animate && !BM_WEB_ANIMATIONS_DISABLED;
853
895
 
854
896
  // Allow each target to set its own options
855
897
  var options = animationTargets[i].options ? BMCopyProperties({}, animation.options, animationTargets[i].options) : BMCopyProperties({}, animation.options);
@@ -865,9 +907,15 @@ export function BMAnimationApplyBlocking(blocking) {
865
907
  delay += stride;
866
908
  }
867
909
 
910
+ if (velocity && !useWebAnimations && !delay) {
911
+ // When velocity is used, if an element is already part of the velocity calls, clear out any properties
912
+ // that are part of this new animation
913
+ BMAnimationContext._prepareAnimationForNode(element, {withProperties: animationTargets[i].properties, options});
914
+ }
915
+
868
916
  delete options.complete;
869
917
 
870
- if ((context._useWebAnimations || BM_USE_WEB_ANIMATIONS) && document.body.animate && !BM_WEB_ANIMATIONS_DISABLED) {
918
+ if (useWebAnimations) {
871
919
  if (options.queue) {
872
920
  console.warn('[BMCoreUI] Using the queue animation option with web animations which is not supported. This argument will be ignored.');
873
921
  }
@@ -1031,10 +1079,50 @@ export function BMAnimationApplyBlocking(blocking) {
1031
1079
  }
1032
1080
  }
1033
1081
  else {
1034
- promises.push(velocity.animate(element, animationTargets[i].properties, options));
1035
-
1036
1082
  if (options.queue) {
1037
- velocity.Utilities.dequeue(element, options.queue);
1083
+ console.warn(`Ignoring deprecated property "queue" for animation context.`);
1084
+ }
1085
+
1086
+ options.queue = false;
1087
+
1088
+ // If a delay is specified, use setTimeout as velocity leaks delayed animations
1089
+ if (options.delay) {
1090
+ const node = element;
1091
+ const properties = animationTargets[i].properties;
1092
+ const animationOptions = options;
1093
+
1094
+ promises.push(new Promise(async resolve => {
1095
+ const delay = options.delay;
1096
+ options.delay = undefined;
1097
+ const pendingAnimation = {node, properties, options: animationOptions, animationContext: context};
1098
+
1099
+ const timeout = setTimeout(async () => {
1100
+ // When the timeout expires, start the animation and remove it from the pending list for the element
1101
+ const pendingAnimations = BMAnimationContext._pendingAnimations.get(element) || [];
1102
+ const index = pendingAnimations?.indexOf(pendingAnimation) ?? -1;
1103
+ if (index != -1) {
1104
+ pendingAnimations.splice(index, 1);
1105
+ }
1106
+
1107
+ // If there are no further pending animations remove the element from the map
1108
+ if (!pendingAnimation?.length) {
1109
+ BMAnimationContext._pendingAnimations.delete(element);
1110
+ }
1111
+
1112
+ // Before starting the animation, ensure that no other animations affect this node's properties
1113
+ BMAnimationContext._prepareAnimationForNode(node, {withProperties: properties, options: animationOptions});
1114
+ const animationResult = await velocity.animate(node, properties, animationOptions);
1115
+ resolve(animationResult);
1116
+ }, delay);
1117
+
1118
+ // Store this animation as pending
1119
+ const pendingAnimations = BMAnimationContext._pendingAnimations.get(element) || [];
1120
+ pendingAnimations.push(pendingAnimation);
1121
+ BMAnimationContext._pendingAnimations.set(element, pendingAnimations);
1122
+ }));
1123
+ }
1124
+ else {
1125
+ promises.push(velocity.animate(element, animationTargets[i].properties, options));
1038
1126
  }
1039
1127
  }
1040
1128
  }