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

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 (60) hide show
  1. package/README.md +1 -1
  2. package/build/@types/index.d.ts +2194 -94
  3. package/build/BMCollectionView/BMCollectionView.js +977 -196
  4. package/build/BMCollectionView/BMCollectionViewFlowLayout.js +11 -4
  5. package/build/BMCoreUI.css +133 -2
  6. package/build/BMView/BMDragSession.js +1150 -0
  7. package/build/BMView/BMDragSessionActions.js +121 -0
  8. package/build/BMView/BMDragSessionPreview.js +1631 -0
  9. package/build/BMView/BMLayoutConstraint_v2.5.js +14 -59
  10. package/build/BMView/BMView_v2.5.js +34 -3
  11. package/build/BMWindow/BMPopover/BMPopover.js +35 -20
  12. package/build/BMWindow/BMToolWindow.js +29 -1
  13. package/build/BMWindow/BMWindow.js +128 -65
  14. package/build/images/AlignBottom.png +0 -0
  15. package/build/images/AlignCenterX.png +0 -0
  16. package/build/images/AlignCenterY.png +0 -0
  17. package/build/images/AlignLeading.png +0 -0
  18. package/build/images/AlignTop.png +0 -0
  19. package/build/images/AlignTrailing.png +0 -0
  20. package/build/images/AllConstraints.png +0 -0
  21. package/build/images/BottomConstraint.png +0 -0
  22. package/build/images/CenterXConstraint.png +0 -0
  23. package/build/images/CenterYConstraint.png +0 -0
  24. package/build/images/CoreUI2.png +0 -0
  25. package/build/images/CoreUI2@2x.png +0 -0
  26. package/build/images/Desktop.png +0 -0
  27. package/build/images/DesktopMini.png +0 -0
  28. package/build/images/EqualHeight.png +0 -0
  29. package/build/images/EqualHorizontalSpacing.png +0 -0
  30. package/build/images/EqualHorizontalSpacingInSuperview.png +0 -0
  31. package/build/images/EqualVerticalSpacing.png +0 -0
  32. package/build/images/EqualVerticalSpacingInSuperview.png +0 -0
  33. package/build/images/EqualWidth.png +0 -0
  34. package/build/images/HeightConstraint.png +0 -0
  35. package/build/images/InactiveConstraints.png +0 -0
  36. package/build/images/Layout.png +0 -0
  37. package/build/images/LayoutVariables.png +0 -0
  38. package/build/images/LeftConstraint.png +0 -0
  39. package/build/images/OwnConstraints.png +0 -0
  40. package/build/images/Phone.png +0 -0
  41. package/build/images/PhoneLandscape.png +0 -0
  42. package/build/images/PhoneLandscapeMini.png +0 -0
  43. package/build/images/PhoneMini.png +0 -0
  44. package/build/images/PhonePortrait.png +0 -0
  45. package/build/images/PhonePortraitMini.png +0 -0
  46. package/build/images/Properties.png +0 -0
  47. package/build/images/RightConstraint.png +0 -0
  48. package/build/images/SubviewConstraints.png +0 -0
  49. package/build/images/Tablet.png +0 -0
  50. package/build/images/TabletLandscape.png +0 -0
  51. package/build/images/TabletLandscapeMini.png +0 -0
  52. package/build/images/TabletMini.png +0 -0
  53. package/build/images/TabletPortrait.png +0 -0
  54. package/build/images/TabletPortraitMini.png +0 -0
  55. package/build/images/TopConstraint.png +0 -0
  56. package/build/images/WidthConstraint.png +0 -0
  57. package/build/index.js +4 -0
  58. package/lib/@types/BMCoreUI.min.d.ts +2194 -94
  59. package/lib/BMCoreUI.min.js +1 -1
  60. package/package.json +2 -2
@@ -0,0 +1,1631 @@
1
+ import { NO, YES } from "../Core/BMCoreUI";
2
+ import { BMPointMake } from "../Core/BMPoint";
3
+ import { BMRectMake, BMRectMakeWithNodeFrame, BMRectMakeWithOrigin } from "../Core/BMRect";
4
+ import { BMAnimateWithBlock, BMAnimationApply, BMAnimationBeginWithDuration, BMAnimationContextAddCompletionHandler, BMAnimationContextGetCurrent, BMHook } from "../Core/BMAnimationContext";
5
+ import { BMSizeMake } from "../Core/BMSize";
6
+ import { _BMDragSessionDropActionMap, _BMDragSessionIconMap, BMDragSessionActionKind, BMDropSessionActionKind } from "./BMDragSessionActions";
7
+
8
+ // @type BMDragPreview
9
+
10
+ /**
11
+ * The maximum rotation to apply to drag previews, in degrees.
12
+ */
13
+ const _BMDragPreviewMaxRotation = 15;
14
+
15
+ /**
16
+ * The distance between the acceptable and unacceptable items, in pixels.
17
+ */
18
+ const _BMDragPreviewRejectDistance = 64;
19
+
20
+ /**
21
+ * The amount by which to multiply the largest size to determine the distance between
22
+ * acceptable and unacceptable items.
23
+ */
24
+ const _BMDragPreviewRejectMultiplier = 1.2;
25
+
26
+ /**
27
+ * The distance to the right edge of the viewport after which the indicator should move to the
28
+ * left corner of the drag previews.
29
+ */
30
+ const _BMDragIndicatorMaxEdgeDistance = 128;
31
+
32
+ /**
33
+ * An object that represents a preview of a drag item and is displayed during a drag session.
34
+ */
35
+ export function BMDragPreview() {} // <constructor>
36
+
37
+ BMDragPreview.prototype = {
38
+
39
+ /**
40
+ * The drag item represented by this drag preview.
41
+ */
42
+ _dragItem: undefined, // <BMDragItem>
43
+
44
+ /**
45
+ * Initializes this drag preview by creating a copy of the specified source node.
46
+ * @param node <DOMNode> The source node.
47
+ * {
48
+ * @param forItem <BMDragItem> The drag item for which a preview is created.
49
+ * }
50
+ * @returns <BMDragPreview> This drag preview.
51
+ */
52
+ initWithCopyOfSourceNode(node, {forItem}) {
53
+ const previewNode = node.cloneNode(YES);
54
+ const result = this.initWithPreviewNode(previewNode, {sourceNode: node, forItem});
55
+ result._isCopyOfSourceNode = YES;
56
+ return result;
57
+ },
58
+
59
+ /**
60
+ * Designated initializer. Initializes this drag preview with the specified preview node
61
+ * and optionally a source node.
62
+ * @param node <DOMNode> The node representing the preview.
63
+ * {
64
+ * @param forItem <BMDragItem> The drag item for which a preview is created.
65
+ * @param sourceNode <DOMNode, nullable> If specified, the node that the drag item represents.
66
+ * }
67
+ * @returns <BMDragPreview> This drag preview.
68
+ */
69
+ initWithPreviewNode(node, {forItem, sourceNode}) {
70
+ this._previewNode = node;
71
+ this._sourceNode = sourceNode;
72
+ this._dragItem = forItem;
73
+ this._transform = {translateX: 0, translateY: 0};
74
+ return this;
75
+ },
76
+
77
+ /**
78
+ * The source node for which a preview is generated. This is used to run an appropriate animation
79
+ * from the node corresponding to the drag item when the drag session starts or finishes.
80
+ *
81
+ * If the source node is not provided, a generic animation will typically play instead for the preview node.
82
+ */
83
+ _sourceNode: undefined, // <DOMNode, nullable>
84
+
85
+ /**
86
+ * The node representing the preview. This node should not be modified while a drag
87
+ * session is in progress.
88
+ */
89
+ _previewNode: undefined, // <DOMNode>
90
+
91
+ get previewNode() {
92
+ return this._previewNode;
93
+ },
94
+
95
+ /**
96
+ * When set to `YES`, this indicates that the preview node is an exact copy of the source node
97
+ * and a transition between the preview and source node is not required.
98
+ */
99
+ _isCopyOfSourceNode: NO, // <Boolean>
100
+
101
+ /**
102
+ * The current frame of the drag preview, before any transforms are applied.
103
+ */
104
+ _frame: BMRectMake(), // <BMRect>
105
+
106
+ get frame() {
107
+ return this._frame.copy();
108
+ },
109
+
110
+ /**
111
+ * Updates this preview's position on screen.
112
+ * @param position <BMPoint> The new position, relative to the center of this preview's frame.
113
+ */
114
+ _setPosition(position) {
115
+ if (this._animationSourceNode) {
116
+ // If this is changed in the middle of the lift animation that plays from a non-copy source node,
117
+ // the displacement must be applied to the source node as well
118
+ const center = this._frame.center;
119
+ const dX = position.x - center.x;
120
+ const dY = position.y - center.y;
121
+
122
+ this._animationSourceNode.style.left = this._animationSourceNode.offsetLeft + dX + 'px';
123
+ this._animationSourceNode.style.top = this._animationSourceNode.offsetTop + dY + 'px';
124
+ }
125
+
126
+ this._frame.center = position;
127
+
128
+ if (!this._animatingFrame) {
129
+ this._applyFrame(this._frame);
130
+ }
131
+
132
+ if (this._transitionPreview) {
133
+ this._transitionPreview._setPosition(position);
134
+ }
135
+ },
136
+
137
+ /**
138
+ * Applies the specified frame to the preview node.
139
+ * @param frame <BMRect> The frame to apply.
140
+ */
141
+ _applyFrame(frame) {
142
+ Object.assign(this._previewNode.style, {
143
+ left: `${frame.origin.x}px`,
144
+ top: `${frame.origin.y}px`,
145
+ width: `${frame.size.width}px`,
146
+ height: `${frame.size.height}px`,
147
+ });
148
+ },
149
+
150
+ /**
151
+ * A dictionary containing transform property names as keys and their applied values, expressed
152
+ * in pixels as the value. The contents of object should not be modified while the drag session
153
+ * is in progress.
154
+ */
155
+ _transform: undefined, // <Dictionary<number>>
156
+
157
+ get transform() {
158
+ return this._transform;
159
+ },
160
+
161
+ /**
162
+ * Applies the transform to the preview node.
163
+ * @param transform <Dictionary<number>> The transform dictionary. See {@link BMDragPreview.transform}.
164
+ */
165
+ _applyTransform() {
166
+ // Add the appropriate units to the transform values
167
+ const transform = {};
168
+ for (const key in this._transform) {
169
+ switch (key) {
170
+ case 'translateX':
171
+ case 'translateY':
172
+ transform[key] = (this._transform[key] ?? 0) + 'px';
173
+ break;
174
+ case 'rotateZ':
175
+ case 'rotate':
176
+ transform[key] = (this._transform[key] ?? 0) + 'deg';
177
+ break;
178
+ default:
179
+ transform[key] = this._transform[key];
180
+ }
181
+ }
182
+
183
+ BMHook(this._previewNode, transform);
184
+ },
185
+
186
+ /**
187
+ * The total amount of displacement to apply for this preview when rejected.
188
+ */
189
+ _rejectionDistance: 0, // <Number>
190
+
191
+ /**
192
+ * Set to YES when this preview is displaced to indicate that it is rejected.
193
+ */
194
+ _rejected: NO, // <Boolean>
195
+
196
+ /**
197
+ * Updates the rejection distance to the specified number of pixels. If this preview is
198
+ * currently rejected, its position will be animated to the new distance.
199
+ * @param distance <Number> The number of pixels to displace this preview by when rejected.
200
+ */
201
+ _setRejectionDistance(distance) {
202
+ if (this._rejectionDistance == distance) {
203
+ return;
204
+ }
205
+
206
+ this._rejectionDistance = distance;
207
+
208
+ if (this._rejected) {
209
+ // If this preview's item is reject, update the transform accordingly
210
+ this._transform.translateX = distance;
211
+
212
+ // Further, if this preview is attached, animate this change
213
+ if (this._attached) {
214
+ BMAnimateWithBlock(() => {
215
+ const context = BMAnimationContextGetCurrent();
216
+ const controller = context.controllerForObject(this, {node: this._previewNode});
217
+ controller.registerBuiltInPropertiesWithDictionary({
218
+ translateX: this._transform.translateX + 'px',
219
+ });
220
+
221
+ // If additionally this is playing a transition from another preview, update that preview as well
222
+ if (this._transitionPreview) {
223
+ const controller = context.controllerForObject(this._transitionPreview, {node: this._transitionPreview._previewNode});
224
+ this._transitionPreview._transform.translateX = this._transform.translateX;
225
+ controller.registerBuiltInPropertiesWithDictionary({
226
+ translateX: this._transform.translateX + 'px',
227
+ });
228
+ }
229
+ }, {duration: 300, easing: 'easeInOutQuart'});
230
+ }
231
+ }
232
+ },
233
+
234
+ /**
235
+ * Set to `YES` while this preview is playing the rejection animation.
236
+ */
237
+ _rejecting: NO, // <Boolean>
238
+
239
+ /**
240
+ * Updates the rejection state of this drag preview.
241
+ * @param rejected <Boolean> `YES` if this preview's item is rejected, `NO` otherwise.
242
+ */
243
+ _setRejected(rejected) {
244
+ if (this._rejected == rejected) {
245
+ return;
246
+ }
247
+
248
+ this._rejected = rejected;
249
+
250
+ // Update the transform accordingly
251
+ if (rejected) {
252
+ this._transform.translateX = this._rejectionDistance;
253
+ }
254
+ else {
255
+ this._transform.translateX = 0;
256
+ }
257
+
258
+ // Animate this change if attached
259
+ if (this._attached) {
260
+ this._rejecting = YES;
261
+ BMAnimateWithBlock(() => {
262
+ const controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this._previewNode});
263
+ controller.registerBuiltInPropertiesWithDictionary({
264
+ translateX: this._transform.translateX + 'px',
265
+ });
266
+ }, {duration: 300, easing: 'easeInOutQuart'}).then(() => {
267
+ this._rejecting = NO;
268
+ });
269
+ }
270
+ },
271
+
272
+ /**
273
+ * Set to `YES` while this preview's frame is animating.
274
+ */
275
+ _animatingFrame: NO, // <Boolean>
276
+
277
+ /**
278
+ * Set to `YES` after the preview node has been measured.
279
+ */
280
+ _measured: NO, // <Boolean>
281
+
282
+ /**
283
+ * Measures the preview node and updates the frame to the measured size.
284
+ */
285
+ _measure() {
286
+ const size = BMSizeMake(this._previewNode.offsetWidth, this._previewNode.offsetHeight);
287
+ this._frame = BMRectMakeWithOrigin(this._frame.origin.copy(), {size});
288
+
289
+ this._measured = YES;
290
+ },
291
+
292
+ /**
293
+ * Attaches this drag session preview to the document and applies its frame and transform properties.
294
+ * @param position <BMPoint, nullable> If specified, the point at which the preview will be attached,
295
+ * relative to the viewport.
296
+ * {
297
+ * @param before <DOMNode, nullable> If specified, a node before which the preview node will be attached.
298
+ * }
299
+ * @return <Iterator<void>> An iterator that must be iterated to sync DOM reads and writes
300
+ * when multiple previews are attached at the same time.
301
+ */
302
+ *_attachAtPosition(position, args) {
303
+ this._attached = YES;
304
+
305
+ // If a before node is specified and is the same as the preview node, don't take any further action
306
+ if (args?.before == this._previewNode) {
307
+ yield;
308
+ return;
309
+ }
310
+
311
+ this._previewNode.classList.add('BMAnimationNode');
312
+
313
+ // If the preview node was never measured, measure it now
314
+ let attached = NO;
315
+ if (!this._measured) {
316
+ attached = YES;
317
+
318
+ if (args?.before) {
319
+ document.body.insertBefore(this._previewNode, args.before)
320
+ }
321
+ else {
322
+ document.body.appendChild(this._previewNode);
323
+ }
324
+ this._measure();
325
+ }
326
+
327
+ yield;
328
+
329
+ // If a position was specified, move the frame accordingly
330
+ if (position) {
331
+ this._frame.center = position;
332
+ }
333
+
334
+ this._applyTransform();
335
+ this._applyFrame(this._frame);
336
+
337
+ // If the preview node was not measured and attached previously, attach it now
338
+ if (!attached) {
339
+ if (args?.before) {
340
+ document.body.insertBefore(this._previewNode, args.before)
341
+ }
342
+ else {
343
+ document.body.appendChild(this._previewNode);
344
+ }
345
+ }
346
+ },
347
+
348
+ /**
349
+ * Set to the source animation during the lift animation.
350
+ */
351
+ _animationSourceNode: undefined, // <DOMNode, nullable>
352
+
353
+ /**
354
+ * Plays the lift animation for this drag preview at the beginning of a drag session or when the associated
355
+ * item is added to an in-progress drag session.
356
+ * @return <Promise<void>> A promise that resolves when the associated animation completes.
357
+ */
358
+ _performLift() {
359
+ if (!this._sourceNode) {
360
+ this._applyFrame(this._frame);
361
+
362
+ BMHook(this._previewNode, this._transform);
363
+
364
+ // Play a generic animation if the source node is not provided
365
+ return BMAnimateWithBlock(() => {
366
+ BMAnimationContextGetCurrent().controllerForObject(this, {node: this._previewNode}).registerBuiltInPropertiesWithDictionary({
367
+ scaleX: [this._transform.scaleX ?? 1, (this._transform.scaleX ?? 1) * 0.5],
368
+ scaleY: [this._transform.scaleY ?? 1, (this._transform.scaleY ?? 1) * 0.5],
369
+ opacity: [1, 0],
370
+ });
371
+ }, {duration: 300, easing: 'easeInOutQuad'});
372
+ }
373
+ else {
374
+ if (this._isCopyOfSourceNode) {
375
+ // If the preview is a direct copy of the source node, only animate from the position of the
376
+ // source view, to the target frame
377
+ const sourceFrame = BMRectMakeWithNodeFrame(this._sourceNode);
378
+
379
+ this._applyFrame(sourceFrame);
380
+
381
+ this._animatingFrame = YES;
382
+ return BMAnimateWithBlock(() => {
383
+ const controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this._previewNode});
384
+ BMHook(this._previewNode, {rotateZ: '0deg'});
385
+ controller.registerBuiltInPropertiesWithDictionary({
386
+ scaleX: [this._transform.scaleX ?? 1, 1],
387
+ scaleY: [this._transform.scaleY ?? 1, 1],
388
+ rotateZ: `${this._transform.rotateZ ?? 0}deg`,
389
+ });
390
+
391
+ controller.registerCustomProperty('frame', {
392
+ withHandler: (fraction) => {
393
+ const newFrame = sourceFrame.interpolatedValueWithFraction(fraction, {toValue: this._frame});
394
+ this._applyFrame(newFrame);
395
+ },
396
+ });
397
+ }, {duration: 300, easing: 'easeInOutQuad'}).then(() => {
398
+ this._animatingFrame = NO;
399
+ });
400
+ }
401
+ else {
402
+ // If the preview node is different from the source node, create a transition between the source
403
+ // node and the preview node
404
+ const sourceNodeCopy = this._sourceNode.cloneNode(YES);
405
+ const sourceFrame = BMRectMakeWithNodeFrame(this._sourceNode);
406
+
407
+ sourceNodeCopy.classList.add('BMAnimationNode');
408
+ Object.assign(sourceNodeCopy.style, {
409
+ left: `${sourceFrame.origin.x}px`,
410
+ top: `${sourceFrame.origin.y}px`,
411
+ width: `${sourceFrame.size.width}px`,
412
+ height: `${sourceFrame.size.height}px`,
413
+ });
414
+ document.body.insertBefore(sourceNodeCopy, this._previewNode);
415
+
416
+ this._animationSourceNode = sourceNodeCopy;
417
+
418
+ const transformRect = this._frame.rectWithTransformToRect(sourceFrame);
419
+
420
+ return BMAnimateWithBlock(() => {
421
+ const controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this._previewNode});
422
+ BMHook(this._previewNode, {
423
+ rotateZ: '0deg',
424
+ translateX: `${transformRect.origin.x}px`,
425
+ translateY: `${transformRect.origin.y}px`,
426
+ });
427
+
428
+ controller.registerBuiltInPropertiesWithDictionary({
429
+ scaleX: [this._transform.scaleX ?? 1, transformRect.size.width],
430
+ scaleY: [this._transform.scaleY ?? 1, transformRect.size.height],
431
+ translateX: '0px',
432
+ translateY: '0px',
433
+ rotateZ: `${this._transform.rotateZ ?? 0}deg`,
434
+ opacity: [1, 0],
435
+ });
436
+
437
+ const targetController = BMAnimationContextGetCurrent().controllerForObject(sourceNodeCopy, {node: sourceNodeCopy});
438
+ BMHook(sourceNodeCopy, {
439
+ translateX: '0px',
440
+ translateY: '0px',
441
+ rotateZ: '0deg',
442
+ });
443
+ targetController.registerBuiltInPropertiesWithDictionary({
444
+ scaleX: [1 / transformRect.size.width, 1],
445
+ scaleY: [1 / transformRect.size.height, 1],
446
+ translateX: `${-transformRect.origin.x}px`,
447
+ translateY: `${-transformRect.origin.y}px`,
448
+ rotateZ: `${this._transform.rotateZ ?? 0}deg`,
449
+ opacity: [0, 1],
450
+ });
451
+
452
+ }, {duration: 300, easing: 'easeInOutQuad'}).then(() => {
453
+ sourceNodeCopy.remove();
454
+ this._animationSourceNode = sourceNodeCopy;
455
+ });
456
+ }
457
+ }
458
+ },
459
+
460
+ /**
461
+ * Set to `YES` if this preview's drop animation is handled by the drop delegate.
462
+ */
463
+ _dropHandled: NO, // <Boolean>
464
+
465
+ /**
466
+ * Plays the drop animation for this drag preview at the end of the drag session, if the current
467
+ * drop target did not handle the drop animation on its own.
468
+ * @return <Promise<void>> A promise that resolves when the associated animation completes.
469
+ */
470
+ _performDrop() {
471
+ if (!this._sourceNode) {
472
+ // Play a generic animation if the source node is not provided
473
+ return BMAnimateWithBlock(() => {
474
+ BMAnimationContextGetCurrent().controllerForObject(this, {node: this._previewNode}).registerBuiltInPropertiesWithDictionary({
475
+ scaleX: [this._transform.scaleX ?? 1 * 0.5, (this._transform.scaleX ?? 1)],
476
+ scaleY: [this._transform.scaleY ?? 1 * 0.5, (this._transform.scaleY ?? 1)],
477
+ opacity: [0, 1],
478
+ });
479
+ }, {duration: 300, easing: 'easeInOutQuad'});
480
+ }
481
+ else {
482
+ if (this._isCopyOfSourceNode) {
483
+ // If the preview is a direct copy of the source node, only animate from the position of the
484
+ // preview, to the source view
485
+ const sourceFrame = BMRectMakeWithNodeFrame(this._sourceNode);
486
+
487
+ return BMAnimateWithBlock(() => {
488
+ const controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this._previewNode});
489
+ BMHook(this._previewNode, {rotateZ: `${this._transform.rotateZ ?? 0}deg`});
490
+ controller.registerBuiltInPropertiesWithDictionary({
491
+ scaleX: [1, this._transform.scaleX ?? 1, 1],
492
+ scaleY: [1, this._transform.scaleY ?? 1, 1],
493
+ rotateZ: '0deg',
494
+ });
495
+
496
+ controller.registerCustomProperty('frame', {
497
+ withHandler: (fraction) => {
498
+ const newFrame = this._frame.interpolatedValueWithFraction(fraction, {toValue: sourceFrame});
499
+ this._applyFrame(newFrame);
500
+ },
501
+ });
502
+ }, {duration: 300, easing: 'easeInOutQuad'});
503
+ }
504
+ else {
505
+ // If the preview node is different from the source node, create a transition between the source
506
+ // node and the preview node
507
+ const sourceNodeCopy = this._sourceNode.cloneNode(YES);
508
+ const sourceFrame = BMRectMakeWithNodeFrame(this._sourceNode);
509
+
510
+ sourceNodeCopy.classList.add('BMAnimationNode');
511
+ Object.assign(sourceNodeCopy.style, {
512
+ left: `${sourceFrame.origin.x}px`,
513
+ top: `${sourceFrame.origin.y}px`,
514
+ width: `${sourceFrame.size.width}px`,
515
+ height: `${sourceFrame.size.height}px`,
516
+ });
517
+ document.body.insertBefore(sourceNodeCopy, this._previewNode);
518
+
519
+ const transformRect = this._frame.rectWithTransformToRect(sourceFrame);
520
+
521
+ return BMAnimateWithBlock(() => {
522
+ const controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this._previewNode});
523
+ BMHook(this._previewNode, {
524
+ rotateZ: `${this._transform.rotateZ ?? 0}deg`,
525
+ });
526
+ controller.registerBuiltInPropertiesWithDictionary({
527
+ scaleX: [transformRect.size.width, this._transform.scaleX ?? 1],
528
+ scaleY: [transformRect.size.height, this._transform.scaleY ?? 1],
529
+ translateX: `${transformRect.origin.x}px`,
530
+ translateY: `${transformRect.origin.y}px`,
531
+ rotateZ: `0deg`,
532
+ opacity: [0, 1],
533
+ });
534
+
535
+ const targetController = BMAnimationContextGetCurrent().controllerForObject(sourceNodeCopy, {node: sourceNodeCopy});
536
+ BMHook(sourceNodeCopy, {
537
+ translateX: `${-transformRect.origin.x + (this._transform.translateX ?? 0)}px`,
538
+ translateY: `${-transformRect.origin.y + (this._transform.translateY ?? 0)}px`,
539
+ rotateZ: `${this._transform.rotateZ ?? 0}deg`,
540
+ });
541
+ targetController.registerBuiltInPropertiesWithDictionary({
542
+ scaleX: [1, 1 / transformRect.size.width],
543
+ scaleY: [1, 1 / transformRect.size.height],
544
+ translateX: '0px',
545
+ translateY: '0px',
546
+ rotateZ: '0deg',
547
+ opacity: [1, 0],
548
+ });
549
+
550
+ }, {duration: 300, easing: 'easeInOutQuad'}).then(() => {
551
+ sourceNodeCopy.remove();
552
+ });
553
+ }
554
+ }
555
+ },
556
+
557
+ /**
558
+ * Plays the delete animation for this drag preview at the end of the drag session, if the drop
559
+ * action was set to `.Delete`.
560
+ * @return <Promise<void>> A promise that resolves when the associated animation completes.
561
+ */
562
+ _performDelete() {
563
+ // If an animation context isn't already started, start it now
564
+ let hasAnimationContext = YES;
565
+ if (!BMAnimationContextGetCurrent()) {
566
+ hasAnimationContext = NO;
567
+ BMAnimationBeginWithDuration(300, {easing: 'easeInQuart'});
568
+ }
569
+
570
+ BMAnimationContextGetCurrent().controllerForObject(this, {node: this._previewNode}).registerBuiltInPropertiesWithDictionary({
571
+ scaleX: [this._transform.scaleX ?? 1 * 1.5, (this._transform.scaleX ?? 1)],
572
+ scaleY: [this._transform.scaleY ?? 1 * 1.5, (this._transform.scaleY ?? 1)],
573
+ opacity: [0, 1],
574
+ });
575
+
576
+ if (!hasAnimationContext) {
577
+ return BMAnimationApply();
578
+ }
579
+ else {
580
+ return new Promise(r => BMAnimationContextAddCompletionHandler(r));
581
+ }
582
+ },
583
+
584
+ /**
585
+ * If specified, the preview this preview is transitioning from, while the transition
586
+ * is in progress. `undefined` in all other cases.
587
+ */
588
+ _transitionPreview: undefined, // <BMDragPreview>
589
+
590
+ /**
591
+ * A unique identifier for transitions, used to perform the appropriate cleanup at the end
592
+ * of the transition only when needed.
593
+ */
594
+ _transitionUID: 0, // <Number>
595
+
596
+ /**
597
+ * Plays a transition animation from the specified drag preview to this drag preview, when the drag
598
+ * session transitions to a new drop target. Detaches the specified drag preview from the document
599
+ * if different from the current one.
600
+ * @param preview <BMDragPreview> The drag preview from which to play a transition.
601
+ * {
602
+ * @param fromRejectionDistance <Number> The reject distance prior to this transition taking place.
603
+ * }
604
+ * @return <Promise<void>> A promise that resolves when the associated animation completes.
605
+ */
606
+ _performTransitionFromDragPreview(preview, args) {
607
+ // If the preview node is the same between the two views, don't take any further action
608
+ if (this._previewNode == preview._previewNode) {
609
+ preview._attached = NO;
610
+ return Promise.resolve();
611
+ }
612
+
613
+ const transitionUID = this._transitionUID + 1;
614
+ this._transitionUID = transitionUID;
615
+
616
+ // If the target preview was already performing a transition, finish it immediately
617
+ if (preview._transitionPreview) {
618
+ preview._transitionPreview._detach();
619
+ preview._transitionPreview = undefined;
620
+ }
621
+
622
+ const transformRect = this._frame.rectWithTransformToRect(preview._frame);
623
+
624
+ this._transitionPreview = preview;
625
+
626
+ return BMAnimateWithBlock(() => {
627
+ const controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this._previewNode});
628
+ const properties = {
629
+ scaleX: [this._transform.scaleX ?? 1, transformRect.size.width * (preview._transform.scaleX ?? 1)],
630
+ scaleY: [this._transform.scaleY ?? 1, transformRect.size.height * (preview._transform.scaleY ?? 1)],
631
+ opacity: [1, 0],
632
+ };
633
+
634
+ // If the source controller is performing the rejecting animation, also play it for this preview
635
+ if (preview._rejecting) {
636
+ Object.assign(properties, {
637
+ translateX: this._transform.translateX ? [this._transform.translateX, 0] : [0, args.fromRejectionDistance],
638
+ });
639
+ }
640
+
641
+ controller.registerBuiltInPropertiesWithDictionary(properties);
642
+
643
+ const targetController = BMAnimationContextGetCurrent().controllerForObject(preview, {node: preview._previewNode});
644
+ const targetProperties = {
645
+ scaleX: [(1 / transformRect.size.width) * (this._transform.scaleX ?? 1), preview._transform.scaleX ?? 1],
646
+ scaleY: [(1 / transformRect.size.height) * (this._transform.scaleY ?? 1), preview._transform.scaleY ?? 1],
647
+ opacity: [0, 1],
648
+ };
649
+ targetController.registerBuiltInPropertiesWithDictionary(targetProperties);
650
+
651
+ }, {duration: 300, easing: 'easeInOutQuart'}).then(() => {
652
+ if (preview == this._transitionPreview && this._transitionUID == transitionUID) {
653
+ preview._detach();
654
+ }
655
+
656
+ this._transitionPreview = undefined;
657
+ });
658
+ },
659
+
660
+ /**
661
+ * Controls whether this drag preview is detachable. Active drag previews are not detachable.
662
+ */
663
+ _detachable: YES, // <Boolean>
664
+
665
+ /**
666
+ * Detaches this drag preview from the document and resets its transform to the values of the `_transform` property.
667
+ * The preview should be reattached to the document using {@link BMDragPreview._attach} before being reused.
668
+ */
669
+ _detach() {
670
+ if (!this._detachable) {
671
+ return;
672
+ }
673
+
674
+ this._attached = NO;
675
+ this._previewNode.remove();
676
+ },
677
+
678
+ }
679
+
680
+
681
+ /**
682
+ * Creates and returns a drag preview initialized by creating a copy of the specified source node.
683
+ * @param node <DOMNode> The source node.
684
+ * {
685
+ * @param forItem <BMDragItem> The drag item for which a preview is created.
686
+ * }
687
+ * @returns <BMDragPreview> A drag preview.
688
+ */
689
+ BMDragPreview.dragPreviewWithCopyOfSourceNode = function (node, args) {
690
+ return new this().initWithCopyOfSourceNode(node, args);
691
+ };
692
+
693
+ /**
694
+ * Creates and returns a drag preview initialized with the specified preview node and optionally a source node.
695
+ * @param node <DOMNode> The node representing the preview.
696
+ * {
697
+ * @param forItem <BMDragItem> The drag item for which a preview is created.
698
+ * @param sourceNode <DOMNode, nullable> If specified, the node that the drag item represents.
699
+ * }
700
+ * @returns <BMDragPreview> A drag preview.
701
+ */
702
+ BMDragPreview.dragPreviewWithPreviewNode = function (node, args) {
703
+ return new this().initWithPreviewNode(node, args)
704
+ };
705
+
706
+ // @endtype
707
+
708
+ // @type BMDropPreview
709
+
710
+ /**
711
+ * An object that represents a preview of a drag item at the end of a drop session that can be used
712
+ * by the drop delegate to play an appropriate drop animation for the item. When using the drop preview
713
+ * to customize the drop animation, it is the responsibility of the drop delegate object to detach
714
+ * the drop preview at the end of the animation, unless using one of the built-in animations provided
715
+ * by the drop preview.
716
+ *
717
+ * Drop previews should not be created manually. Core UI will automatically create drop previews for
718
+ * the appropriate items at the end of a drop session if there are items that have been accepted
719
+ * by the drop target.
720
+ */
721
+ export function BMDropPreview() {} // <constructor>
722
+
723
+ BMDropPreview.prototype = {
724
+
725
+ /**
726
+ * The original drag preview from which this drop preview was created.
727
+ */
728
+ _preview: undefined, // <BMDragPreview>
729
+
730
+ /**
731
+ * The drag item represented by this drop preview.
732
+ */
733
+ _item: undefined, // <BMDragItem>
734
+
735
+ get item() {
736
+ return this._item;
737
+ },
738
+
739
+ /**
740
+ * Initializes this drop preview with the specified drag preview and drag item.
741
+ * @param preview <BMDragPreview> The original drag preview.
742
+ * {
743
+ * @param forItem <BMDragItem> The item represented by this preview.
744
+ * }
745
+ * @returns <BMDropPreview> This drop preview.
746
+ */
747
+ _initWithDragPreview(preview, {forItem: item}) {
748
+ this._preview = preview;
749
+ this._item = item;
750
+
751
+ return this;
752
+ },
753
+
754
+ /**
755
+ * The HTML element representing this preview.
756
+ */
757
+ get previewNode() {
758
+ return this._preview._previewNode;
759
+ },
760
+
761
+ /**
762
+ * A rect that describes the current position and size of the preview node relative to the viewport.
763
+ */
764
+ get frame() {
765
+ return this._preview._frame.copy();
766
+ },
767
+
768
+ /**
769
+ * An object that describes the transform currently applied to the preview node. Its keys are transform
770
+ * function names and its values are numbers representing the associated values. The units for the values are:
771
+ * - `deg` for rotation properties
772
+ * - `px` for translation properties
773
+ * - untyped for scale properties
774
+ *
775
+ * The rotation transforms are always applied after all other transforms.
776
+ */
777
+ get transform() {
778
+ return {...this._preview._transform};
779
+ },
780
+
781
+ /**
782
+ * Plays a generic drop animation for this drop preview. This method must be invoked while an animation context is active.
783
+ * When the animation finishes this drag preview is detached from the document.
784
+ */
785
+ performDrop() {
786
+ BMAnimationContextGetCurrent().controllerForObject(this, {node: this.previewNode}).registerBuiltInPropertiesWithDictionary({
787
+ scaleX: [this.transform.scaleX ?? 1 * 0.5, (this.transform.scaleX ?? 1)],
788
+ scaleY: [this.transform.scaleY ?? 1 * 0.5, (this.transform.scaleY ?? 1)],
789
+ opacity: [0, 1],
790
+ });
791
+
792
+ BMAnimationContextAddCompletionHandler(() => this.previewNode.remove());
793
+ },
794
+
795
+ /**
796
+ * Plays a drop animation that visually transforms this drop preview into the specified node. This method must be invoked
797
+ * while an animation context is active. When the animation finishes this drag preview is detached from the document.
798
+ * @param node <DOMNode> The node towards which to play the drop animation.
799
+ */
800
+ performDropToNode(node) {
801
+ const nodeCopy = node.cloneNode(YES);
802
+ const sourceFrame = BMRectMakeWithNodeFrame(node);
803
+
804
+ nodeCopy.classList.add('BMAnimationNode');
805
+ Object.assign(nodeCopy.style, {
806
+ left: `${sourceFrame.origin.x}px`,
807
+ top: `${sourceFrame.origin.y}px`,
808
+ width: `${sourceFrame.size.width}px`,
809
+ height: `${sourceFrame.size.height}px`,
810
+ });
811
+ document.body.insertBefore(nodeCopy, this.previewNode);
812
+
813
+ const transformRect = this.frame.rectWithTransformToRect(sourceFrame);
814
+
815
+ const controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this.previewNode});
816
+ BMHook(this.previewNode, {
817
+ rotateZ: `${this.transform.rotateZ ?? 0}deg`,
818
+ });
819
+ controller.registerBuiltInPropertiesWithDictionary({
820
+ scaleX: [transformRect.size.width, this.transform.scaleX ?? 1],
821
+ scaleY: [transformRect.size.height, this.transform.scaleY ?? 1],
822
+ translateX: `${transformRect.origin.x}px`,
823
+ translateY: `${transformRect.origin.y}px`,
824
+ rotateZ: `0deg`,
825
+ opacity: [0, 1],
826
+ });
827
+
828
+ const targetController = BMAnimationContextGetCurrent().controllerForObject(nodeCopy, {node: nodeCopy});
829
+ BMHook(nodeCopy, {
830
+ translateX: `${-transformRect.origin.x + (this.transform.translateX ?? 0)}px`,
831
+ translateY: `${-transformRect.origin.y + (this.transform.translateY ?? 0)}px`,
832
+ rotateZ: `${this.transform.rotateZ ?? 0}deg`,
833
+ });
834
+ targetController.registerBuiltInPropertiesWithDictionary({
835
+ scaleX: [1, 1 / transformRect.size.width],
836
+ scaleY: [1, 1 / transformRect.size.height],
837
+ translateX: '0px',
838
+ translateY: '0px',
839
+ rotateZ: '0deg',
840
+ opacity: [1, 0],
841
+ });
842
+
843
+ BMAnimationContextAddCompletionHandler(() => {
844
+ nodeCopy.remove();
845
+ });
846
+ },
847
+
848
+ };
849
+
850
+ // @endtype
851
+
852
+ // @type _BMDragIndicatorOrientation
853
+
854
+ /**
855
+ * Constants describing the position of the drag indicator relative to the
856
+ * top edge of the drag previews.
857
+ */
858
+ export const _BMDragIndicatorOrientation = Object.freeze({ // <enum>
859
+
860
+ /**
861
+ * Indicates that the drag indicator should appear on the top left corner.
862
+ */
863
+ Left: 'Left', // <enum>
864
+
865
+ /**
866
+ * Indicates that the drag indicator should appear on the top right corner.
867
+ */
868
+ Right: 'Right', // <enum>
869
+
870
+ });
871
+
872
+ // @endtype
873
+
874
+ // @type _BMDragIndicator
875
+
876
+ /**
877
+ * An object that manages the indicator that appears during a drag and drop gesture.
878
+ */
879
+ export function _BMDragIndicator() {} // <constructor>
880
+
881
+ _BMDragIndicator.prototype = {
882
+
883
+ /**
884
+ * The number of items that are acceptable for the current drop target. This should be equal to
885
+ * or lower than the total number of items in this drag session.
886
+ */
887
+ _acceptableItemCount: 0, // <Number>
888
+
889
+ /**
890
+ * The node containing the indicator.
891
+ */
892
+ _containerNode: undefined, // <DOMNode>
893
+
894
+ /**
895
+ * The node displaying the item count, drop outcome and message.
896
+ */
897
+ _indicatorNode: undefined, // <DOMNode>
898
+
899
+ /**
900
+ * The node displaying the icon associated with the current drop icon.
901
+ */
902
+ _iconNode: undefined, // <DOMNode>
903
+
904
+ /**
905
+ * The HTML element representing the message displayed to the user.
906
+ */
907
+ _messageNode: undefined, // <DOMNode>
908
+
909
+ /**
910
+ * The HTML element used to measure the message text.
911
+ */
912
+ _messageMeasurementNode: undefined, // <DOMNode>
913
+
914
+ /**
915
+ * The message text currently displayed on the drag indicator.
916
+ */
917
+ _message: undefined, // <String, nullable>
918
+
919
+ /**
920
+ * The message HTML markup currently displayed on the drag indicator.
921
+ */
922
+ _message: undefined, // <String, nullable>
923
+
924
+ /**
925
+ * The kind of action displayed on the indicator.
926
+ */
927
+ _dropActionKind: BMDragSessionActionKind.Ignore, // <BMDragSessionActionKind or BMDropSessionActionKind>
928
+
929
+ /**
930
+ * Initializes this drag indicator and attaches it to the document.
931
+ * @return <_BMDragIndicator> This drag indicator.
932
+ */
933
+ init() {
934
+ const container = document.createElement('div');
935
+ container.classList.add('BMDragSessionIndicatorContainer');
936
+ container.inert = YES;
937
+
938
+ this._containerNode = container;
939
+ container.innerHTML = /*HTML*/ `
940
+ <div class="BMDragSessionIndicator">
941
+ <img class="BMDragSessionIndicatorIcon BMDragSessionIndicatorIconHidden" />
942
+ <div class="BMDragSessionIndicatorItemCount"></div>
943
+ <div class="BMDragSessionIndicatorMessage BMDragSessionIndicatorMessageHidden"></div>
944
+ </div>
945
+ <div class="BMDragSessionIndicatorMessage BMDragSessionIndicatorMessageMeasurement"></div>
946
+ `;
947
+
948
+ this._indicatorNode = container.querySelector('.BMDragSessionIndicator');
949
+ this._iconNode = container.querySelector('.BMDragSessionIndicatorIcon');
950
+ this._itemCountNode = container.querySelector('.BMDragSessionIndicatorItemCount');
951
+ this._messageNode = this._indicatorNode.querySelector('.BMDragSessionIndicatorMessage');
952
+ this._messageMeasurementNode = container.querySelector('.BMDragSessionIndicatorMessageMeasurement');
953
+
954
+ document.body.appendChild(container);
955
+
956
+ return this;
957
+ },
958
+
959
+ /**
960
+ * Updates the contents of this drag indicator using the details of the specified
961
+ * drag or drop action.
962
+ * @param action <BMDragSessionAction or BMDropSessionAction> The action.
963
+ */
964
+ setAction(action) {
965
+ // Update the color and icon of the indicator
966
+ this._setDropActionKind(action._action);
967
+
968
+ // Update the message
969
+ if (action._message) {
970
+ this._setMessage(action._message);
971
+ }
972
+ else {
973
+ this._setMessageHTML(action._messageHTML);
974
+ }
975
+ },
976
+
977
+ /**
978
+ * The offset between the drag pointer and the center of this drag indicator.
979
+ */
980
+ _offset: BMPointMake(), // <BMPoint>
981
+
982
+ /**
983
+ * The unique sequence identifier of the current offset animation.
984
+ */
985
+ _offsetAnimationID: 0, // <Number>
986
+
987
+ /**
988
+ * Updates the offset of the indicator from the pointer's position.
989
+ * @param offset <BMPoint> The new offset to use.
990
+ * {
991
+ * @param animated <Boolean, nullable> Defaults to `NO`. When set to `YES` this change will be
992
+ * animated, otherwise it will be instant.
993
+ * }
994
+ */
995
+ setOffset(offset, args) {
996
+ offset = offset.copy();
997
+
998
+ if (!args?.animated) {
999
+ this._offset = offset;
1000
+ this.setPosition(this._position);
1001
+ return;
1002
+ }
1003
+
1004
+ const animationID = this._offsetAnimationID + 1;
1005
+ this._offsetAnimationID = animationID;
1006
+
1007
+ // Animate the offset change using a top/left displacement, then
1008
+ // reset to using transforms and clear out the displacement
1009
+ const distance = BMPointMake(offset.x - this._offset.x, offset.y - this._offset.y);
1010
+
1011
+ BMAnimateWithBlock(() => {
1012
+ const controller = BMAnimationContextGetCurrent().controllerForObject(this._indicatorNode, {node: this._indicatorNode});
1013
+ controller.registerBuiltInPropertiesWithDictionary({
1014
+ left: distance.x + 'px',
1015
+ top: distance.y + 'px',
1016
+ });
1017
+ }, {duration: 300, easing: 'easeInOutQuart'}).then(() => {
1018
+ if (animationID == this._offsetAnimationID) {
1019
+ Object.assign(this._indicatorNode.style, {left: '0', top: '0'});
1020
+ this._offset = offset;
1021
+ this.setPosition(this._position);
1022
+ }
1023
+ });
1024
+ },
1025
+
1026
+ /**
1027
+ * The current position of the drag session, relative to the viewport.
1028
+ */
1029
+ _position: BMPointMake(), // <BMPoint>
1030
+
1031
+ /**
1032
+ * Updates the position of the drag gesture and all the drag previews.
1033
+ * @param position <BMPoint> The new position.
1034
+ */
1035
+ setPosition(position) {
1036
+ this._position = position.copy();
1037
+
1038
+ const indicatorPosition = this._position.copy();
1039
+ indicatorPosition.x += this._offset.x;
1040
+ indicatorPosition.y += this._offset.y;
1041
+
1042
+ // The indicator is normally located in the center of the viewport, so an
1043
+ // appropriate displacement must be applied for it to appear at the correct
1044
+ // position on screen
1045
+ const displacement = BMPointMake(
1046
+ indicatorPosition.x - window.innerWidth / 2,
1047
+ indicatorPosition.y - window.innerHeight / 2,
1048
+ );
1049
+
1050
+ BMHook(this._indicatorNode, {
1051
+ translateX: displacement.x + 'px',
1052
+ translateY: displacement.y + 'px',
1053
+ });
1054
+ },
1055
+
1056
+ /**
1057
+ * Updates the drop action displayed on the drag indicator.
1058
+ * @param action <_BMDragDropSessionAction> The new drop action to display.
1059
+ */
1060
+ _setDropActionKind(action) {
1061
+ if (action == this._dropActionKind) {
1062
+ return;
1063
+ }
1064
+
1065
+ // Apply the appropriate class to the indicator element
1066
+ this._indicatorNode.classList.remove(_BMDragSessionDropActionMap[this._dropActionKind]);
1067
+ this._indicatorNode.classList.add(_BMDragSessionDropActionMap[action]);
1068
+ this._dropActionKind = action;
1069
+
1070
+ // Set the appropriate icon on the indicator, hiding or showing it based on whether
1071
+ // an icon exists for the action kind
1072
+ const icon = _BMDragSessionIconMap[action];
1073
+ if (icon) {
1074
+ this._iconNode.src = icon;
1075
+ this._iconNode.style.width = '16px';
1076
+ this._iconNode.style.height = '16px';
1077
+ this._iconNode.classList.remove('BMDragSessionIndicatorIconHidden');
1078
+ }
1079
+ else {
1080
+ this._iconNode.classList.add('BMDragSessionIndicatorIconHidden');
1081
+ this._iconNode.style.width = '0';
1082
+ this._iconNode.style.height = '0';
1083
+ }
1084
+ },
1085
+
1086
+ /**
1087
+ * Updates the item count displayed on the drag indicator.
1088
+ * @param count <Number> The new item count to display.
1089
+ */
1090
+ setAcceptableItemCount(count) {
1091
+ if (count == this._acceptableItemCount) {
1092
+ return;
1093
+ }
1094
+
1095
+ this._acceptableItemCount = count;
1096
+ this._itemCountNode.innerText = count.toFixed();
1097
+ this._itemCountNode.style.width = this._itemCountNode.scrollWidth;
1098
+ },
1099
+
1100
+ /**
1101
+ * Updates the message currently displayed on the drag indicator.
1102
+ * @param message <String, nullable> The message to display, or `undefined` to not show any message.
1103
+ */
1104
+ _setMessage(message) {
1105
+ if (message == this._message && !this._messageHTML) {
1106
+ return;
1107
+ }
1108
+
1109
+ this._messageHTML = undefined;
1110
+ this._message = message;
1111
+
1112
+ this._messageMeasurementNode.innerText = message ?? '';
1113
+
1114
+ if (!message) {
1115
+ this._messageNode.classList.add('BMDragSessionIndicatorMessageHidden');
1116
+ this._messageNode.style.width = '0px';
1117
+ }
1118
+ else {
1119
+ this._messageNode.innerText = message ?? '';
1120
+ this._messageNode.classList.remove('BMDragSessionIndicatorMessageHidden');
1121
+ this._messageNode.style.width = this._messageMeasurementNode.scrollWidth + 'px';
1122
+ }
1123
+ },
1124
+
1125
+ /**
1126
+ * Updates the message currently displayed on the drag indicator using the specified HTML text.
1127
+ * @param message <String, nullable> The message HTML to display, or `undefined` to not show any message.
1128
+ */
1129
+ _setMessageHTML(message) {
1130
+ if (message == this._messageHTML && !this._message) {
1131
+ return;
1132
+ }
1133
+
1134
+ this._message = undefined;
1135
+ this._messageHTML = this.messageHTML;
1136
+ this._messageMeasurementNode.innerHTML = message ?? '';
1137
+
1138
+ if (!message) {
1139
+ this._messageNode.classList.add('BMDragSessionIndicatorMessageHidden');
1140
+ this._messageNode.style.width = '0px';
1141
+ }
1142
+ else {
1143
+ this._messageNode.innerHTML = message ?? '';
1144
+ this._messageNode.classList.remove('BMDragSessionIndicatorMessageHidden');
1145
+ this._messageNode.style.width = this._messageMeasurementNode.scrollWidth + 'px';
1146
+ }
1147
+ },
1148
+
1149
+ /**
1150
+ * Plays the lift animation for this drag indicator.
1151
+ * @return <Promise<void>> A promise that resolves when the animation completes.
1152
+ */
1153
+ performLift() {
1154
+ return BMAnimateWithBlock(() => {
1155
+ const controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this._indicatorNode});
1156
+ controller.registerBuiltInPropertiesWithDictionary({
1157
+ scaleX: [1, 0],
1158
+ scaleY: [1, 0],
1159
+ opacity: [1, 0],
1160
+ });
1161
+ }, {duration: 300, easing: 'easeInOutQuad'});
1162
+ },
1163
+
1164
+ /**
1165
+ * Plays the drop animation for this drag indicator.
1166
+ * @return <Promise<void>> A promise that resolves when the animation completes.
1167
+ */
1168
+ performDrop() {
1169
+ return BMAnimateWithBlock(() => {
1170
+ const controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this._indicatorNode});
1171
+ controller.registerBuiltInPropertiesWithDictionary({
1172
+ scaleX: [0, 1],
1173
+ scaleY: [0, 1],
1174
+ opacity: [0, 1],
1175
+ });
1176
+ }, {duration: 300, easing: 'easeInOutQuad'});
1177
+ },
1178
+
1179
+ /**
1180
+ * Detaches this drag indicator from the document.
1181
+ * The drag indicator should not be reused after this method returns.
1182
+ */
1183
+ release() {
1184
+ this._containerNode.remove();
1185
+ }
1186
+ };
1187
+
1188
+ // @endtype
1189
+
1190
+ // @type _BMDragPreviewSet
1191
+
1192
+ /**
1193
+ * An object that manages the appearance, position and animations of the preview elements
1194
+ * for items included in a drag session and the drag indicator.
1195
+ */
1196
+ export function _BMDragPreviewSet() {} // <constructor>
1197
+
1198
+ _BMDragPreviewSet.prototype = {
1199
+
1200
+ /**
1201
+ * The offset between the drag pointer and the center of the frames of the preview elements.
1202
+ */
1203
+ _offset: BMPointMake(), // <BMPoint>
1204
+
1205
+ /**
1206
+ * The current position of the drag session.
1207
+ */
1208
+ _position: BMPointMake(), // <BMPoint>
1209
+
1210
+ /**
1211
+ * The indicator displaying information about the associated drag session.
1212
+ */
1213
+ _dragIndicator: undefined, // <_BMDragIndicator>
1214
+
1215
+ /**
1216
+ * A mapping between drag items and their associated drag previews.
1217
+ */
1218
+ _dragPreviews: undefined, // <Map<BMDragItem, BMDragPreview>>
1219
+
1220
+ /**
1221
+ * A mapping between drag items and the drag previews provided initially for each.
1222
+ */
1223
+ _baseDragPreviews: undefined, // <Map<BMDragItem, BMDragPreview>>
1224
+
1225
+ /**
1226
+ * A set that controls which items appear as rejected.
1227
+ */
1228
+ _rejectedItems: undefined, // <Set<BMDragItem>>
1229
+
1230
+ /**
1231
+ * The total displacement to apply to rejected previews.
1232
+ */
1233
+ _rejectionDistance: 0, // <Number>
1234
+
1235
+ /**
1236
+ * Initializes this drag preview set with the specified item previews and offset position.
1237
+ * @param previews <[BMDragPreview]> The previews for items in the drag session.
1238
+ * {
1239
+ * @param pointerOffset <BMPoint> The offset between the drag pointer and the
1240
+ * center of the frames of the preview elements.
1241
+ * }
1242
+ * @return <_BMDragPreviewSet> This preview set.
1243
+ */
1244
+ initWithPreviews(previews, {pointerOffset}) {
1245
+ // Create the association between drag items and their previews
1246
+ this._dragPreviews = new Map();
1247
+ this._baseDragPreviews = new Map();
1248
+ this._rejectedItems = new Set();
1249
+
1250
+ for (const preview of previews) {
1251
+ this._dragPreviews.set(preview._dragItem, preview);
1252
+ this._baseDragPreviews.set(preview._dragItem, preview);
1253
+ }
1254
+
1255
+ this._offset = pointerOffset.copy();
1256
+
1257
+ this._dragIndicator = new _BMDragIndicator().init();
1258
+
1259
+ return this;
1260
+ },
1261
+
1262
+ /**
1263
+ * An additional preview to display when all items are rejected.
1264
+ */
1265
+ _additionalPreview: undefined, // <BMDragPreview, nullable>
1266
+
1267
+ /**
1268
+ * Displays the specified additional preview, or clears it.
1269
+ * @param preview <BMDragPreview, nullable> The additional preview to display, or `undefined` to
1270
+ * clear the additional preview.
1271
+ */
1272
+ setAdditionalPreview(preview) {
1273
+ if (preview == this._additionalPreview) {
1274
+ return;
1275
+ }
1276
+
1277
+ if (this._additionalPreview) {
1278
+ // If a different additional preview was already being displayed, remove it
1279
+ const currentPreview = this._additionalPreview;
1280
+ const sourceNode = currentPreview._sourceNode;
1281
+ currentPreview._performDrop().then(() => currentPreview._detach());
1282
+ currentPreview._sourceNode = sourceNode;
1283
+ }
1284
+
1285
+ this._additionalPreview = preview;
1286
+
1287
+ if (preview) {
1288
+ // If a new additional preview should be displayed, attach and display it
1289
+ const previewPosition = this._position.copy();
1290
+ previewPosition.x += this._offset.x;
1291
+ previewPosition.y += this._offset.y;
1292
+
1293
+ preview.transform.scaleX = 1;
1294
+ preview.transform.scaleY = 1;
1295
+ preview.transform.translateX = 0;
1296
+ preview.transform.translateY = 0;
1297
+ preview.transform.rotateZ = Math.random() * _BMDragPreviewMaxRotation * 2 - _BMDragPreviewMaxRotation;
1298
+
1299
+ const iterator = preview._attachAtPosition(previewPosition, {before: this._dragPreviews.values().next().value._previewNode});
1300
+ iterator.next();
1301
+ iterator.next();
1302
+
1303
+
1304
+ const sourceNode = preview._sourceNode;
1305
+ preview._performLift();
1306
+ preview._sourceNode = sourceNode;
1307
+ }
1308
+ },
1309
+
1310
+ /**
1311
+ * Updates the position of the drag gesture and all the drag previews.
1312
+ * @param position <BMPoint> The new position.
1313
+ */
1314
+ setPosition(position) {
1315
+ this._position = position.copy();
1316
+
1317
+ if (position.x + this._indicatorOffsetRight > window.innerWidth - _BMDragIndicatorMaxEdgeDistance) {
1318
+ this._setIndicatorOrientation(_BMDragIndicatorOrientation.Left);
1319
+ }
1320
+ else {
1321
+ this._setIndicatorOrientation(_BMDragIndicatorOrientation.Right);
1322
+ }
1323
+
1324
+ const previewPosition = this._position.copy();
1325
+ previewPosition.x += this._offset.x;
1326
+ previewPosition.y += this._offset.y;
1327
+
1328
+ for (const preview of this._dragPreviews.values()) {
1329
+ preview._setPosition(previewPosition);
1330
+ }
1331
+
1332
+ this._additionalPreview?._setPosition(previewPosition);
1333
+
1334
+ this._dragIndicator.setPosition(this._position);
1335
+ },
1336
+
1337
+ /**
1338
+ * Causes the previews for the specified items to appear as rejected. All other items will
1339
+ * appear as acceptable even if they had been previously set as rejected using this method.
1340
+ * @param items <[BMDragItem], nullable> The items that should appear as rejected,
1341
+ * or `undefined` to clear the rejected items.
1342
+ */
1343
+ setRejectedItems(items) {
1344
+ if (!items?.length && !this._rejectedItems.size) {
1345
+ return;
1346
+ }
1347
+
1348
+ this._rejectedItems.clear();
1349
+
1350
+ if (items) {
1351
+ for (const item of items) {
1352
+ this._rejectedItems.add(item);
1353
+ }
1354
+ }
1355
+
1356
+ for (const [item, preview] of this._dragPreviews.entries()) {
1357
+ preview._setRejected(this._rejectedItems.has(item));
1358
+ }
1359
+ },
1360
+
1361
+ /**
1362
+ * Attaches the drag previews and plays their lift animations.
1363
+ * @param position <BMPoint> The position of the drag session.
1364
+ */
1365
+ beginLiftAtPosition(position) {
1366
+ this._position = position.copy();
1367
+ const previewPosition = position.copy();
1368
+ previewPosition.x += this._offset.x;
1369
+ previewPosition.y += this._offset.y;
1370
+
1371
+ // Attach and measure the previews
1372
+ let attachIterators = [];
1373
+ let previousPreview;
1374
+ let firstPreview;
1375
+ for (const [item, preview] of this._dragPreviews.entries()) {
1376
+
1377
+ // For each preview other than the first, apply a random rotation between 15 and -15 degrees
1378
+ if (previousPreview) {
1379
+ preview._transform.rotateZ = Math.random() * _BMDragPreviewMaxRotation * 2 - _BMDragPreviewMaxRotation;
1380
+ }
1381
+
1382
+ if (!firstPreview) {
1383
+ firstPreview = preview;
1384
+ }
1385
+
1386
+ const attachIterator = preview._attachAtPosition(previewPosition, {before: previousPreview?._previewNode});
1387
+ attachIterators.push(attachIterator);
1388
+ attachIterator.next();
1389
+
1390
+ previousPreview = preview;
1391
+ }
1392
+
1393
+ for (const iterator of attachIterators) {
1394
+ iterator.next();
1395
+ }
1396
+
1397
+ this._updateRejectionDistance();
1398
+
1399
+ // Run the lift animation
1400
+ for (const [item, preview] of this._dragPreviews.entries()) {
1401
+ preview._performLift();
1402
+ }
1403
+
1404
+ // Determine the offset for the drag indicator and play its lift animation as well
1405
+ this._updateDragIndicatorOffsetAnimated(NO);
1406
+ this._dragIndicator.performLift();
1407
+ },
1408
+
1409
+ /**
1410
+ * Updates or resets the previews for the specified items using a map.
1411
+ * @param previews <Map<BMDragItem, BMDragPreview | undefined>> The drag previews to use, or `undefined` to reset it for each item.
1412
+ */
1413
+ updatePreviewsWithMap(previews) {
1414
+ let attachIterators = [];
1415
+ let previewTransitions = new Map();
1416
+
1417
+ const rejectDistance = this._rejectionDistance;
1418
+
1419
+ const previewPosition = this._position.copy();
1420
+ previewPosition.x += this._offset.x;
1421
+ previewPosition.y += this._offset.y;
1422
+
1423
+ for (const [item, preview] of previews.entries()) {
1424
+ if (!preview) {
1425
+ // If a preview is not specified, reset to the default preview
1426
+ const defaultPreview = this._baseDragPreviews.get(item);
1427
+ const currentPreview = this._dragPreviews.get(item);
1428
+
1429
+ // If the specified item does not have a drag preview, don't take any further action
1430
+ if (!defaultPreview) {
1431
+ continue;
1432
+ }
1433
+
1434
+ if (defaultPreview == currentPreview) {
1435
+ // If the item is already using the default preview, don't take any further action
1436
+ continue;
1437
+ }
1438
+ else {
1439
+ defaultPreview._rejectionDistance = this._rejectionDistance;
1440
+ defaultPreview._setRejected(this._rejectedItems.has(item));
1441
+
1442
+ defaultPreview._transform.rotateZ = currentPreview._transform.rotateZ;
1443
+ defaultPreview._transform.translateX = currentPreview._transform.translateX;
1444
+
1445
+ // Otherwise attach the default preview and transition to it
1446
+ const attachIterator = defaultPreview._attachAtPosition(previewPosition, {before: currentPreview._previewNode});
1447
+ attachIterator.next();
1448
+ attachIterators.push(attachIterator);
1449
+
1450
+ previewTransitions.set(currentPreview, defaultPreview);
1451
+ this._dragPreviews.set(item, defaultPreview);
1452
+ }
1453
+ }
1454
+ else {
1455
+ // If a preview is specified, attach it and transition to it
1456
+ const currentPreview = this._dragPreviews.get(item);
1457
+
1458
+ if (!currentPreview) {
1459
+ continue;
1460
+ }
1461
+
1462
+ preview._transform.rotateZ = currentPreview._transform.rotateZ;
1463
+ preview._transform.translateX = currentPreview._transform.translateX;
1464
+ preview._rejectionDistance = this._rejectionDistance;
1465
+ preview._setRejected(this._rejectedItems.has(item));
1466
+
1467
+ const attachIterator = preview._attachAtPosition(previewPosition, {before: currentPreview._previewNode});
1468
+ attachIterator.next();
1469
+ attachIterators.push(attachIterator);
1470
+
1471
+ previewTransitions.set(currentPreview, preview);
1472
+ this._dragPreviews.set(item, preview);
1473
+ }
1474
+ }
1475
+
1476
+ // Finish attaching the previews
1477
+ for (const iterator of attachIterators) {
1478
+ iterator.next();
1479
+ }
1480
+
1481
+ // Update the rejection distance
1482
+ this._updateRejectionDistance();
1483
+
1484
+ // Perform the transitions
1485
+ for (const [fromPreview, toPreview] of previewTransitions.entries()) {
1486
+ toPreview._detachable = NO;
1487
+ fromPreview._detachable = YES;
1488
+ toPreview._performTransitionFromDragPreview(fromPreview, {fromRejectionDistance: rejectDistance});
1489
+ fromPreview._setRejectionDistance(this._rejectionDistance);
1490
+ }
1491
+
1492
+ // Update the indicator offset
1493
+ this._updateDragIndicatorOffsetAnimated(YES);
1494
+ },
1495
+
1496
+ /**
1497
+ * Determines the rejection distance based on the size of all current previews.
1498
+ */
1499
+ _updateRejectionDistance() {
1500
+ const widths = Array.from(this._dragPreviews.values()).map(p => p._frame.size.width * (p._transform.scaleX ?? 1));
1501
+ const maxWidth = Math.max.apply(Math, widths);
1502
+ let distance = maxWidth * _BMDragPreviewRejectMultiplier + _BMDragPreviewRejectDistance;
1503
+
1504
+ if (this._position.x > window.innerWidth / 2) {
1505
+ distance = -distance;
1506
+ }
1507
+
1508
+ if (distance != this._rejectionDistance) {
1509
+ this._rejectionDistance = distance;
1510
+
1511
+ for (const preview of this._dragPreviews.values()) {
1512
+ preview._setRejectionDistance(this._rejectionDistance);
1513
+ }
1514
+ }
1515
+ },
1516
+
1517
+ /**
1518
+ * The orientation of the drag indicator relative to the top edge of the drag previews.
1519
+ */
1520
+ _indicatorOrientation: _BMDragIndicatorOrientation.Right,
1521
+
1522
+ /**
1523
+ * Updates the orientation of the drag indicator. If the orientation changes as a result
1524
+ * of this method, the change will be animated.
1525
+ * @param orientation <_BMDragIndicatorOrientation> The new orientation to use.
1526
+ */
1527
+ _setIndicatorOrientation(orientation) {
1528
+ if (orientation == this._indicatorOrientation) {
1529
+ return;
1530
+ }
1531
+
1532
+ this._indicatorOrientation = orientation;
1533
+ this._updateDragIndicatorOffsetAnimated(YES);
1534
+ },
1535
+
1536
+ /**
1537
+ * The offset the indicator would use if the orientation was set to `.Right`.
1538
+ */
1539
+ _indicatorOffsetRight: 0, // <Number>
1540
+
1541
+ /**
1542
+ * Updates the drag indicator offset from the drag session's position based on the size
1543
+ * and scale of the first displayed item preview.
1544
+ * The offset for the indicator is set such that it will appear to the top left of
1545
+ * the first preview.
1546
+ * @param animated <Boolean, nullable> Defaults to `YES`. Whether this change is animated.
1547
+ */
1548
+ _updateDragIndicatorOffsetAnimated(animated = YES) {
1549
+ const firstPreview = this._dragPreviews.values().next().value;
1550
+ const origin = firstPreview._frame.origin;
1551
+ const size = firstPreview._frame.size;
1552
+ const transform = firstPreview._transform;
1553
+
1554
+ const position = this._position;
1555
+
1556
+ this._indicatorOffsetRight = firstPreview._frame.right - ((size.width - (size.width * (transform.scaleX ?? 1)))) - position.x;
1557
+
1558
+ // Place the indicator in the top-right or top-left corner of the preview depending on the orientation,
1559
+ // accounting for the scale transform that may be applied to it
1560
+ const indicatorOffset = BMPointMake(
1561
+ this._indicatorOrientation == _BMDragIndicatorOrientation.Right ?
1562
+ this._indicatorOffsetRight :
1563
+ origin.x + ((size.width - (size.width * (transform.scaleX ?? 1)))) - position.x,
1564
+ origin.y + (size.height - (size.height * (transform.scaleY ?? 1))) - position.y,
1565
+ );
1566
+
1567
+ this._dragIndicator.setOffset(indicatorOffset, {animated});
1568
+ },
1569
+
1570
+ /**
1571
+ * Plays the drop animation for the current drag previews, then detaches them.
1572
+ */
1573
+ performDrop() {
1574
+
1575
+ if (this._dragIndicator._dropActionKind == BMDropSessionActionKind.Delete) {
1576
+ BMAnimationBeginWithDuration(300, {easing: 'easeInQuart', stride: 100 / this._dragPreviews.size});
1577
+ }
1578
+
1579
+ for (const [item, preview] of this._dragPreviews.entries()) {
1580
+ preview._detachable = YES;
1581
+
1582
+ if (this._dragIndicator._dropActionKind == BMDropSessionActionKind.Delete) {
1583
+ preview._performDelete().then(() => preview._previewNode.remove());
1584
+ continue;
1585
+ }
1586
+
1587
+ // If the drop is handled by the delegate, don't play any animation
1588
+ if (preview._dropHandled) {
1589
+ continue;
1590
+ }
1591
+
1592
+ // Always animate rejected items back to the original node if possible, or all
1593
+ // items for drop actions of type reject
1594
+ let sourceNode, isCopyOfSourceNode;
1595
+ let rejected =
1596
+ preview._rejected ||
1597
+ this._dragIndicator._dropActionKind == BMDropSessionActionKind.Reject ||
1598
+ this._dragIndicator._dropActionKind == BMDropSessionActionKind.Ignore;
1599
+
1600
+ if (rejected) {
1601
+ sourceNode = preview._sourceNode;
1602
+ isCopyOfSourceNode = preview._isCopyOfSourceNode;
1603
+
1604
+ const basePreview = this._baseDragPreviews.get(item);
1605
+ preview._sourceNode = basePreview._sourceNode;
1606
+
1607
+ if (preview != basePreview) {
1608
+ preview._isCopyOfSourceNode = NO;
1609
+ }
1610
+ }
1611
+ preview._performDrop().then(() => preview._previewNode.remove());
1612
+ if (rejected) {
1613
+ preview._sourceNode = sourceNode;
1614
+ preview._isCopyOfSourceNode = isCopyOfSourceNode;
1615
+ }
1616
+ }
1617
+
1618
+ if (this._dragIndicator._dropActionKind == BMDropSessionActionKind.Delete) {
1619
+ BMAnimationApply();
1620
+ }
1621
+
1622
+ if (this._additionalPreview) {
1623
+ this._additionalPreview._performDrop().then(() => this._additionalPreview._previewNode.remove());
1624
+ }
1625
+
1626
+ return this._dragIndicator.performDrop().then(() => this._dragIndicator.release());
1627
+ },
1628
+
1629
+ };
1630
+
1631
+ // @endtype