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.
- package/README.md +1 -1
- package/build/@types/index.d.ts +2194 -94
- package/build/BMCollectionView/BMCollectionView.js +977 -196
- package/build/BMCollectionView/BMCollectionViewFlowLayout.js +11 -4
- package/build/BMCoreUI.css +133 -2
- package/build/BMView/BMDragSession.js +1150 -0
- package/build/BMView/BMDragSessionActions.js +121 -0
- package/build/BMView/BMDragSessionPreview.js +1631 -0
- package/build/BMView/BMLayoutConstraint_v2.5.js +14 -59
- package/build/BMView/BMView_v2.5.js +34 -3
- package/build/BMWindow/BMPopover/BMPopover.js +35 -20
- package/build/BMWindow/BMToolWindow.js +29 -1
- package/build/BMWindow/BMWindow.js +128 -65
- package/build/images/AlignBottom.png +0 -0
- package/build/images/AlignCenterX.png +0 -0
- package/build/images/AlignCenterY.png +0 -0
- package/build/images/AlignLeading.png +0 -0
- package/build/images/AlignTop.png +0 -0
- package/build/images/AlignTrailing.png +0 -0
- package/build/images/AllConstraints.png +0 -0
- package/build/images/BottomConstraint.png +0 -0
- package/build/images/CenterXConstraint.png +0 -0
- package/build/images/CenterYConstraint.png +0 -0
- package/build/images/CoreUI2.png +0 -0
- package/build/images/CoreUI2@2x.png +0 -0
- package/build/images/Desktop.png +0 -0
- package/build/images/DesktopMini.png +0 -0
- package/build/images/EqualHeight.png +0 -0
- package/build/images/EqualHorizontalSpacing.png +0 -0
- package/build/images/EqualHorizontalSpacingInSuperview.png +0 -0
- package/build/images/EqualVerticalSpacing.png +0 -0
- package/build/images/EqualVerticalSpacingInSuperview.png +0 -0
- package/build/images/EqualWidth.png +0 -0
- package/build/images/HeightConstraint.png +0 -0
- package/build/images/InactiveConstraints.png +0 -0
- package/build/images/Layout.png +0 -0
- package/build/images/LayoutVariables.png +0 -0
- package/build/images/LeftConstraint.png +0 -0
- package/build/images/OwnConstraints.png +0 -0
- package/build/images/Phone.png +0 -0
- package/build/images/PhoneLandscape.png +0 -0
- package/build/images/PhoneLandscapeMini.png +0 -0
- package/build/images/PhoneMini.png +0 -0
- package/build/images/PhonePortrait.png +0 -0
- package/build/images/PhonePortraitMini.png +0 -0
- package/build/images/Properties.png +0 -0
- package/build/images/RightConstraint.png +0 -0
- package/build/images/SubviewConstraints.png +0 -0
- package/build/images/Tablet.png +0 -0
- package/build/images/TabletLandscape.png +0 -0
- package/build/images/TabletLandscapeMini.png +0 -0
- package/build/images/TabletMini.png +0 -0
- package/build/images/TabletPortrait.png +0 -0
- package/build/images/TabletPortraitMini.png +0 -0
- package/build/images/TopConstraint.png +0 -0
- package/build/images/WidthConstraint.png +0 -0
- package/build/index.js +4 -0
- package/lib/@types/BMCoreUI.min.d.ts +2194 -94
- package/lib/BMCoreUI.min.js +1 -1
- 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
|