bm-core-ui 2.7.0

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 (99) hide show
  1. package/CHANGELOG.md +1624 -0
  2. package/LICENSE +21 -0
  3. package/README.md +145 -0
  4. package/build/@types/index.d.ts +14078 -0
  5. package/build/BMCodeEditor/BMCodeEditor.js +707 -0
  6. package/build/BMCollectionView/BMCollectionView.js +5861 -0
  7. package/build/BMCollectionView/BMCollectionViewCell.js +688 -0
  8. package/build/BMCollectionView/BMCollectionViewFlowLayout.js +4467 -0
  9. package/build/BMCollectionView/BMCollectionViewLayout.js +830 -0
  10. package/build/BMCollectionView/BMCollectionViewLayoutAttributes.js +673 -0
  11. package/build/BMCollectionView/BMCollectionViewMasonryLayout.js +491 -0
  12. package/build/BMCollectionView/BMCollectionViewStackLayout.js +634 -0
  13. package/build/BMCollectionView/BMCollectionViewTileLayout.js +1121 -0
  14. package/build/BMCoreUI.css +2833 -0
  15. package/build/BMView/BMAttributedLabelView.js +310 -0
  16. package/build/BMView/BMLayoutConstraint_v2.5.js +1813 -0
  17. package/build/BMView/BMLayoutGuide.js +192 -0
  18. package/build/BMView/BMLayoutSizeClass.js +466 -0
  19. package/build/BMView/BMMenu.js +574 -0
  20. package/build/BMView/BMScrollView.js +247 -0
  21. package/build/BMView/BMTextField.js +512 -0
  22. package/build/BMView/BMTextFieldDelegate.js +71 -0
  23. package/build/BMView/BMView_v2.5.js +3572 -0
  24. package/build/BMView/BMViewport.js +221 -0
  25. package/build/BMViewLayoutEditor/BMLayoutEditor.js +6117 -0
  26. package/build/BMViewLayoutEditor/BMLayoutEditorConstraintSettings.js +417 -0
  27. package/build/BMViewLayoutEditor/BMLayoutEditorDelegate.js +59 -0
  28. package/build/BMViewLayoutEditor/BMLayoutEditorSettingCells.js +1653 -0
  29. package/build/BMViewLayoutEditor/BMLayoutEditorSettings.js +1480 -0
  30. package/build/BMViewLayoutEditor/BMLayoutEditorSettingsComplexCells.js +431 -0
  31. package/build/BMViewLayoutEditor/BMLayoutEditorSettingsDelegate.js +46 -0
  32. package/build/BMViewLayoutEditor/BMLayoutEditorVariablesController.js +460 -0
  33. package/build/BMViewLayoutEditor/BMLayoutEditorViewGroupSettings.js +293 -0
  34. package/build/BMViewLayoutEditor/BMLayoutEditorViewSettings.js +382 -0
  35. package/build/BMViewLayoutEditor/BMLayoutVariableProvider.js +202 -0
  36. package/build/BMWindow/BMConfirmationPopup.js +477 -0
  37. package/build/BMWindow/BMKeyboardShortcut.js +151 -0
  38. package/build/BMWindow/BMPopover/BMPopover.js +492 -0
  39. package/build/BMWindow/BMToolWindow.js +71 -0
  40. package/build/BMWindow/BMWindow.js +2000 -0
  41. package/build/Core/BMAnimationContext.js +1181 -0
  42. package/build/Core/BMColor.js +991 -0
  43. package/build/Core/BMCoreUI.js +470 -0
  44. package/build/Core/BMFunctionCollection.js +110 -0
  45. package/build/Core/BMIndexPath.js +165 -0
  46. package/build/Core/BMInset.js +138 -0
  47. package/build/Core/BMKeyPath.js +100 -0
  48. package/build/Core/BMPoint.js +291 -0
  49. package/build/Core/BMRect.js +556 -0
  50. package/build/Core/BMSize.js +137 -0
  51. package/build/iScroll/LICENSE +22 -0
  52. package/build/iScroll/iscroll-probe.js +2154 -0
  53. package/build/images/AlignBottom.png +0 -0
  54. package/build/images/AlignCenterX.png +0 -0
  55. package/build/images/AlignCenterY.png +0 -0
  56. package/build/images/AlignLeading.png +0 -0
  57. package/build/images/AlignTop.png +0 -0
  58. package/build/images/AlignTrailing.png +0 -0
  59. package/build/images/AllConstraints.png +0 -0
  60. package/build/images/BottomConstraint.png +0 -0
  61. package/build/images/CenterXConstraint.png +0 -0
  62. package/build/images/CenterYConstraint.png +0 -0
  63. package/build/images/CoreUI2.png +0 -0
  64. package/build/images/CoreUI2@2x.png +0 -0
  65. package/build/images/Desktop.png +0 -0
  66. package/build/images/DesktopMini.png +0 -0
  67. package/build/images/EqualHeight.png +0 -0
  68. package/build/images/EqualHorizontalSpacing.png +0 -0
  69. package/build/images/EqualHorizontalSpacingInSuperview.png +0 -0
  70. package/build/images/EqualVerticalSpacing.png +0 -0
  71. package/build/images/EqualVerticalSpacingInSuperview.png +0 -0
  72. package/build/images/EqualWidth.png +0 -0
  73. package/build/images/HeightConstraint.png +0 -0
  74. package/build/images/InactiveConstraints.png +0 -0
  75. package/build/images/Layout.png +0 -0
  76. package/build/images/LayoutVariables.png +0 -0
  77. package/build/images/LeftConstraint.png +0 -0
  78. package/build/images/OwnConstraints.png +0 -0
  79. package/build/images/Phone.png +0 -0
  80. package/build/images/PhoneLandscape.png +0 -0
  81. package/build/images/PhoneLandscapeMini.png +0 -0
  82. package/build/images/PhoneMini.png +0 -0
  83. package/build/images/PhonePortrait.png +0 -0
  84. package/build/images/PhonePortraitMini.png +0 -0
  85. package/build/images/Properties.png +0 -0
  86. package/build/images/RightConstraint.png +0 -0
  87. package/build/images/SubviewConstraints.png +0 -0
  88. package/build/images/Tablet.png +0 -0
  89. package/build/images/TabletLandscape.png +0 -0
  90. package/build/images/TabletLandscapeMini.png +0 -0
  91. package/build/images/TabletMini.png +0 -0
  92. package/build/images/TabletPortrait.png +0 -0
  93. package/build/images/TabletPortraitMini.png +0 -0
  94. package/build/images/TopConstraint.png +0 -0
  95. package/build/images/WidthConstraint.png +0 -0
  96. package/build/index.js +40 -0
  97. package/lib/@types/BMCoreUI.min.d.ts +14078 -0
  98. package/lib/BMCoreUI.min.js +1 -0
  99. package/package.json +58 -0
@@ -0,0 +1,574 @@
1
+ // @ts-check
2
+
3
+ import {YES, NO, BMCopyProperties} from '../Core/BMCoreUI'
4
+ import {BMPoint, BMPointMake} from '../Core/BMPoint'
5
+ import {BMHook, __BMVelocityAnimate} from '../Core/BMAnimationContext'
6
+ import 'velocity-animate'
7
+ import { BMRectMakeWithNodeFrame } from '../Core/BMRect';
8
+
9
+ /**
10
+ * The standard spacing between the menu and the node it is created from.
11
+ */
12
+ const _BMMenuSpacingToNode = 8;
13
+
14
+ /**
15
+ * The name of the CSS class that is added to nodes that have a popup menu.
16
+ */
17
+ export const BMMenuSourceNodeCSSClass = 'BMMenuSourceNode';
18
+
19
+ // @type BMMenuKind
20
+
21
+ export var BMMenuKind = Object.freeze({ // <enum>
22
+
23
+ /**
24
+ * Indicates that the menu should be a context menu.
25
+ */
26
+ Menu: {}, // <enum>
27
+
28
+ /**
29
+ * Indicates that the menu should be a pull down menu.
30
+ */
31
+ PullDownMenu: {} // <enum>
32
+
33
+ });
34
+
35
+ // @endtype
36
+
37
+ // @type BMMenuItem
38
+
39
+ /**
40
+ * A menu item is an object that represents an entry in a menu.
41
+ */
42
+ export function BMMenuItem() {} // <constructor>
43
+
44
+ BMMenuItem.prototype = {
45
+
46
+ /**
47
+ * The name of this menu item.
48
+ */
49
+ _name: undefined, // <String>
50
+ get name() {
51
+ return this._name;
52
+ },
53
+
54
+ /**
55
+ * An optional icon associated with this menu item.
56
+ */
57
+ _icon: undefined, // <String, nullable>
58
+ get icon() {
59
+ return this._icon;
60
+ },
61
+
62
+ /**
63
+ * An optional function that will be invoked when this menu item is selected.
64
+ * This function will receive this menu item as a parameter when it is invoked in this way.
65
+ */
66
+ _action: undefined, // <void ^ (BMMenuItem), nullable>
67
+ get action() {
68
+ return this._action;
69
+ },
70
+
71
+ /**
72
+ * Additional, arbitrary information associated with this menu item.
73
+ */
74
+ userInfo: undefined, // <AnyObject, nullable>
75
+
76
+ /**
77
+ * Constructs and returns a menu item with the given name.
78
+ * Optionally, the menu item may have an icon associated with it as well as an
79
+ * action that is invoked when the item is selected.
80
+ * @param name <String> The name of the menu item.
81
+ * {
82
+ * @param icon <String, nullable> If specified, this represents the URL to an icon associated with this menu item.
83
+ * For most menus, icons will only be displayed if all of its menu items have icons defined.
84
+ *
85
+ * @param action <void ^ (BMMenuItem), nullable> If specified, this is a function that will be invoked when this menu item is selected.
86
+ * This function will receive the selected menu item as a parameter.
87
+ * @param userInfo <AnyObject, nullable> Optional. If specified, this represents additional arbitrary data attached to this menu item.
88
+ * }
89
+ * @return <BMMenuItem> This menu item.
90
+ */
91
+ initWithName(name, {icon, action, userInfo} = {}) {
92
+ this._name = name;
93
+ this._icon = icon;
94
+ this._action = action;
95
+ this.userInfo = userInfo;
96
+
97
+ return this;
98
+ }
99
+
100
+ };
101
+
102
+ /**
103
+ * Constructs and returns a menu item with the given name.
104
+ * Optionally, the menu item may have an icon associated with it as well as an
105
+ * action that is invoked when the item is selected.
106
+ * @param name <String> The name of the menu item.
107
+ * {
108
+ * @param icon <String, nullable> If specified, this represents the URL to an icon associated with this menu item.
109
+ * For most menus, icons will only be displayed if all of its menu items have icons defined.
110
+ *
111
+ * @param action <void ^ (BMMenuItem), nullable> If specified, this is a function that will be invoked when this menu item is selected.
112
+ * This function will receive the selected menu item as a parameter.
113
+ * }
114
+ * @return <BMMenuItem> A menu item.
115
+ */
116
+ BMMenuItem.menuItemWithName = function (name, args) {
117
+ return (new BMMenuItem).initWithName(name, args);
118
+ }
119
+
120
+ /**
121
+ * Constructs and returns a menu separator.
122
+ * @return <BMMenuItem> A menu item.
123
+ */
124
+ BMMenuItem.menuSeparator = function () {
125
+ return (new BMMenuItem).initWithName('---');
126
+ }
127
+
128
+ // @endtype
129
+
130
+ // @type BMMenu
131
+
132
+ /**
133
+ * A menu object manages the display and lifecycle of a popup menu.
134
+ */
135
+ export function BMMenu() {} // <constructor>
136
+
137
+ BMMenu.prototype = {
138
+
139
+ /**
140
+ * The menu items displayed by this menu.
141
+ */
142
+ _items: undefined, // <[BMMenuItem]>
143
+ get items() {
144
+ return this._items.slice();
145
+ },
146
+
147
+ /**
148
+ * An optional delegate object that will receive callbacks events from this menu.
149
+ */
150
+ _delegate: undefined, // <BMMenuDelegate, nullable>
151
+ get delegate() {
152
+ return this._delegate;
153
+ },
154
+ set delegate(delegate) {
155
+ this._delegate = delegate;
156
+ },
157
+
158
+ /**
159
+ * Defaults to 24. The size to use for this menu's icons.
160
+ */
161
+ _iconSize: 24, // <Number>
162
+
163
+ get iconSize() {
164
+ return this._iconSize;
165
+ },
166
+ set iconSize(size) {
167
+ this._iconSize = size;
168
+
169
+ if (this._node) for (const icon of this._node.querySelectorAll()) {
170
+ BMCopyProperties(icon.style, {
171
+ width: size + 'px',
172
+ height: size + 'px'
173
+ });
174
+ }
175
+ },
176
+
177
+ /**
178
+ * A custom list of CSS classes to add to the menu DOM node.
179
+ * This only has effect when set before the menu is opened.
180
+ */
181
+ _CSSClass: '', // <String>
182
+
183
+ get CSSClass() {
184
+ return this._CSSClass;
185
+ },
186
+
187
+ set CSSClass(CSSClass) {
188
+ this._CSSClass = CSSClass || '';
189
+ },
190
+
191
+ /**
192
+ * The point at which the menu is visible.
193
+ */
194
+ _point: new BMPoint(), // <BMPoint>
195
+
196
+ /**
197
+ * The DOM node currently used by this menu.
198
+ */
199
+ _node: undefined, // <DOMNode, nullable>
200
+
201
+ /**
202
+ * The container DOM node currently used by this menu
203
+ */
204
+ _containerNode: undefined, // <DOMNode, nullable>
205
+
206
+ /**
207
+ * The DOM node from which this menu was opened, if it was opened from a node.
208
+ */
209
+ _sourceNode: undefined, // <DOMNode, nullable>
210
+
211
+ /**
212
+ * A copy of the source node, used while the menu is visible.
213
+ */
214
+ _sourceNodeShadow: undefined, // <DOMNode, nullable>
215
+
216
+ /**
217
+ * The source node displacement, used while the menu is visible.
218
+ */
219
+ _sourceNodeDisplacement: undefined, // <Number, nullable>
220
+
221
+ /**
222
+ * Initializes this menu with the specified items.
223
+ * The menu will be hidden by default; the method showAtPoint(_) should be used to make the menu visible.
224
+ *
225
+ * @param items <[BMMenuItem]> An array of menu items that will be displayed by this menu.
226
+ *
227
+ * @return <BMMenu> A menu.
228
+ */
229
+ initWithItems(items) {
230
+ this._items = [...items];
231
+
232
+ return this;
233
+ },
234
+
235
+ /**
236
+ * Invoked by CoreUI to build the menu node.
237
+ */
238
+ _renderMenu() {
239
+ // The actual menu node
240
+ const menuNode = document.createElement('div');
241
+ menuNode.className = this._CSSClass ? `BMMenu ${this._CSSClass}` : 'BMMenu';
242
+ this._node = menuNode;
243
+
244
+ // The overlay which intercepts clicks outside of the menu
245
+ const menuContainer = document.createElement('div');
246
+ menuContainer.className = 'BMMenuContainer';
247
+ if (!('backdropFilter' in document.body.style) && !('webkitBackdropFilter' in document.body.style)) {
248
+ menuNode.style.backgroundColor = 'white';
249
+ }
250
+ menuContainer.appendChild(menuNode);
251
+ this._containerNode = menuContainer;
252
+
253
+ this._renderMenuItems();
254
+
255
+ menuNode.addEventListener('click', event => event.preventDefault());
256
+
257
+ menuContainer.addEventListener('click', event => {
258
+ this.closeAnimated(YES);
259
+ event.preventDefault();
260
+ });
261
+
262
+ menuContainer.addEventListener('contextmenu', event => {
263
+ this.closeAnimated(YES);
264
+ event.preventDefault();
265
+ });
266
+ },
267
+
268
+ /**
269
+ * Invoked by CoreUI to build the menu item nodes displayed by this menu.
270
+ */
271
+ _renderMenuItems() {
272
+ // Create and append the items for this menu
273
+ const iconSize = this._iconSize;
274
+ for (const item of this._items) {
275
+ if (item.name.startsWith('---')) {
276
+ const itemNode = document.createElement('div');
277
+ itemNode.className = 'BMMenuDivider';
278
+ this._node.appendChild(itemNode);
279
+ continue;
280
+ }
281
+
282
+ let itemNode = document.createElement('div');
283
+ itemNode.className = 'BMMenuItem';
284
+
285
+ if (item.icon) {
286
+ const icon = document.createElement('img');
287
+ icon.className = 'BMMenuIcon';
288
+ icon.src = item.icon;
289
+ BMCopyProperties(icon.style, {
290
+ width: iconSize + 'px',
291
+ height: iconSize + 'px'
292
+ });
293
+ itemNode.appendChild(icon);
294
+ }
295
+
296
+ const itemText = document.createElement('span');
297
+ itemText.innerText = item.name;
298
+ itemNode.appendChild(itemText);
299
+
300
+ itemNode.addEventListener('click', event => {
301
+ if (item.action) item.action(item);
302
+
303
+ if (this.delegate && this.delegate.menuDidSelectItem) {
304
+ this.delegate.menuDidSelectItem(this, item);
305
+ }
306
+ });
307
+
308
+ this._node.appendChild(itemNode);
309
+ }
310
+ },
311
+
312
+ /**
313
+ * The kind of menu currently displayed.
314
+ */
315
+ _kind: BMMenuKind.Menu,
316
+
317
+ /**
318
+ * Animatable. Shows this menu from the given DOM node.
319
+ * @param node <DOMNode> The node from which to show this menu.
320
+ * {
321
+ * @param animated <Boolean, nullable> Defaults to `NO`. If set to `YES`, this change will be animated.
322
+ * If this method is invoked from within an animation context, the value of this parameter is ignored
323
+ * and the values of the current animation context are used.
324
+ *
325
+ * @param kind <BMMenuKind, nullable> Defaults to `Menu`. The kind of menu.
326
+ * }
327
+ * @return <Promise<void>> A promise that resolves when the operation completes.
328
+ */
329
+ async openFromNode(node, {animated = NO, kind = BMMenuKind.Menu} = {}) {
330
+ this._sourceNode = node;
331
+
332
+ const sourceRect = BMRectMakeWithNodeFrame(node);
333
+ sourceRect.size.height = node.offsetHeight;
334
+ const viewportHeight = window.innerHeight;
335
+
336
+ this._sourceNodeShadow = node.cloneNode(YES);
337
+
338
+ this._renderMenu();
339
+
340
+ const menuNode = this._node;
341
+ const menuContainer = this._containerNode;
342
+ document.body.appendChild(menuContainer);
343
+ document.body.appendChild(this._sourceNodeShadow);
344
+
345
+ // When open from a node, the menu container will darken the content behind it
346
+ menuContainer.classList.add('BMMenuContainerFullScreen');
347
+ this._sourceNodeShadow.classList.add('BMMenuSourceNodeShadow');
348
+ menuNode.classList.add('BMMenuTouch');
349
+
350
+ this._sourceNode.classList.add(BMMenuSourceNodeCSSClass);
351
+
352
+ // When applicable, this interaction will perform a short tap
353
+ if ('vibrate' in window.navigator) window.navigator.vibrate(10);
354
+
355
+ const remainingHeight = viewportHeight - sourceRect.bottom;
356
+ const menuHeight = menuNode.offsetHeight;
357
+
358
+ let displacement = 0;
359
+
360
+ const scale = .33;
361
+ const pullDownScale = .5;
362
+ const duration = 400;
363
+ const easing = [.17,1.46,.84,.93];
364
+
365
+ // Displace the element, if needed
366
+ if (menuHeight + _BMMenuSpacingToNode * 2 > remainingHeight) {
367
+ displacement = remainingHeight - (menuHeight + _BMMenuSpacingToNode * 2);
368
+ }
369
+
370
+ this._sourceNodeDisplacement = displacement;
371
+
372
+ BMCopyProperties(this._sourceNodeShadow.style, {left: sourceRect.origin.x + 'px', top: sourceRect.origin.y + 'px', width: sourceRect.size.width + 'px', height: sourceRect.size.height + 'px', transform: 'none'});
373
+
374
+ menuNode.style.transformOrigin = '0% 0%';
375
+ if (kind == BMMenuKind.PullDownMenu) {
376
+ menuNode.style.transformOrigin = '50% 0%';
377
+ }
378
+
379
+ const point = BMPointMake(sourceRect.origin.x, sourceRect.bottom + _BMMenuSpacingToNode + displacement);
380
+ BMCopyProperties(menuNode.style, {left: point.x + 'px', top: point.y + 'px'});
381
+
382
+ BMHook(menuContainer, {opacity: 0});
383
+ BMHook(menuNode, {
384
+ scaleX: kind == BMMenuKind.PullDownMenu ? pullDownScale : scale,
385
+ scaleY: kind == BMMenuKind.PullDownMenu ? pullDownScale : scale,
386
+ translateY: -displacement + 'px'
387
+ });
388
+
389
+ // Make the container visible
390
+ __BMVelocityAnimate(menuContainer, {opacity: 1}, {duration: duration, easing: easing});
391
+
392
+ // Make the menu expand
393
+ __BMVelocityAnimate(menuNode, {scaleX: 1, scaleY: 1, opacity: 1, translateZ: 0, translateY: 0}, {
394
+ duration: duration,
395
+ easing: easing,
396
+ complete: _ => ((menuNode.style.pointerEvents = 'all'), menuContainer.style.pointerEvents = 'all')
397
+ });
398
+
399
+ // Animate each child node in
400
+ let delay = 50;
401
+ for (let child of menuNode.childNodes) {
402
+ BMHook(child, {translateY: '16px', translateZ: 0, opacity: 0});
403
+ __BMVelocityAnimate(child, {translateY: '0px', translateZ: 0, opacity: 1}, {
404
+ duration: 100,
405
+ easing: 'easeOutQuad',
406
+ delay: delay
407
+ });
408
+ delay += 16;
409
+ }
410
+
411
+ // Animate the source node shadow
412
+ await __BMVelocityAnimate(this._sourceNodeShadow, {translateY: displacement + 'px'}, {duration: duration, easing: easing});
413
+
414
+ },
415
+
416
+ /**
417
+ * Animatable. Shows this menu at the specified point. The coordinates of this point are relative to the viewport.
418
+ * @param point <BMPoint> The point at which to show this menu.
419
+ * {
420
+ * @param animated <Boolean, nullable> Defaults to `NO`. If set to `YES`, this change will be animated.
421
+ * If this method is invoked from within an animation context, the value of this parameter is ignored
422
+ * and the values of the current animation context are used.
423
+ *
424
+ * @param kind <BMMenuKind, nullable> Defaults to `Menu`. The kind of menu.
425
+ * }
426
+ */
427
+ openAtPoint(point, {animated = NO, kind = BMMenuKind.Menu} = {}) {
428
+ // If the menu is already open, do nothing
429
+ if (this._node) return;
430
+
431
+ if (this.delegate && this.delegate.menuWillOpen) {
432
+ this.delegate.menuWillOpen(this);
433
+ }
434
+
435
+ this._renderMenu();
436
+
437
+ const menuNode = this._node;
438
+ const menuContainer = this._containerNode;
439
+
440
+ BMHook(menuNode, {
441
+ scaleX: kind == BMMenuKind.PullDownMenu ? .5 : .75,
442
+ scaleY: kind == BMMenuKind.PullDownMenu ? .5 : .75,
443
+ opacity: 0
444
+ });
445
+
446
+ document.body.appendChild(menuContainer);
447
+
448
+ const height = menuNode.offsetHeight;
449
+ const width = menuNode.offsetWidth;
450
+
451
+ let transformOriginX = '0%';
452
+ let transformOriginY = ' 0%';
453
+
454
+ if (kind == BMMenuKind.PullDownMenu) {
455
+ transformOriginX = '50%';
456
+ }
457
+ else if (width + point.x > document.documentElement.clientWidth) {
458
+ transformOriginX = '100%';
459
+ point.x -= width;
460
+ }
461
+ if (height + point.y > document.documentElement.clientHeight) {
462
+ transformOriginY = ' 100%';
463
+ point.y -= height;
464
+ }
465
+ menuNode.style.transformOrigin = transformOriginX + transformOriginY;
466
+
467
+ menuNode.style.left = point.x + 'px';
468
+ menuNode.style.top = point.y + 'px';
469
+
470
+ (window.Velocity || $.Velocity).animate(menuNode, {scaleX: 1, scaleY: 1, opacity: 1, translateZ: 0}, {
471
+ duration: 200,
472
+ easing: 'easeOutQuad',
473
+ complete: _ => ((menuNode.style.pointerEvents = 'all'), menuContainer.style.pointerEvents = 'all')
474
+ });
475
+
476
+ let delay = 0;
477
+ for (let child of menuNode.childNodes) {
478
+ BMHook(child, {translateY: '16px', translateZ: 0, opacity: 0});
479
+ (window.Velocity || $.Velocity).animate(child, {translateY: '0px', translateZ: 0, opacity: 1}, {
480
+ duration: 100,
481
+ easing: 'easeOutQuad',
482
+ delay: delay
483
+ });
484
+ delay += 16;
485
+ }
486
+
487
+ return menuNode;
488
+ },
489
+
490
+ /**
491
+ * Animatable. Dismisses this menu, if it is visible.
492
+ * @param animated <Boolean, nullable> Defaults to `NO`. If set to `YES`, this change will be animated.
493
+ * If this method is invoked from within an animation context, the value of this parameter is ignored
494
+ * and the values of the current animation context are used.
495
+ */
496
+ closeAnimated(animated = YES) {
497
+ if (!this._node) return;
498
+
499
+ if (this.delegate && this.delegate.menuWillClose) {
500
+ this.delegate.menuWillClose(this);
501
+ }
502
+
503
+ this._node.style.pointerEvents = 'none';
504
+ this._containerNode.style.pointerEvents = 'none';
505
+ let delay = this._node.childNodes.length * 16 + 100 - 200;
506
+ delay = (delay < 0 ? 0 : delay);
507
+
508
+ const sourceNodeShadow = this._sourceNodeShadow;
509
+ const containerNode = this._containerNode;
510
+ const sourceNode = this._sourceNode;
511
+
512
+ this._sourceNode = undefined;
513
+ this._sourceNodeShadow = undefined;
514
+
515
+ const scale = sourceNodeShadow ? .33 : .75;
516
+ const duration = sourceNodeShadow ? 200 : 400;
517
+ const easing = sourceNodeShadow ? 'easeInOutQuad' : 'easeInQuad';
518
+
519
+ if (sourceNodeShadow) {
520
+ __BMVelocityAnimate(sourceNodeShadow, {translateY: '0px'}, {duration: duration, easing: easing});
521
+ __BMVelocityAnimate(containerNode, {opacity: 0}, {duration: duration, easing: easing});
522
+ }
523
+
524
+ __BMVelocityAnimate(this._node, {
525
+ scaleX: this._kind == BMMenuKind.PullDownMenu ? 1 : scale,
526
+ scaleY: scale,
527
+ opacity: sourceNodeShadow ? 1 : 0,
528
+ translateY: sourceNodeShadow ? -this._sourceNodeDisplacement + 'px' : '0px',
529
+ translateZ: 0
530
+ }, {
531
+ duration: 200,
532
+ easing: easing,
533
+ delay: delay,
534
+ complete: _ => {
535
+ containerNode.remove();
536
+ if (sourceNodeShadow) {
537
+ sourceNodeShadow.remove();
538
+ sourceNode.classList.remove(BMMenuSourceNodeCSSClass);
539
+ }
540
+ if (this.delegate && this.delegate.menuDidClose) {
541
+ this.delegate.menuDidClose(this);
542
+ }
543
+ }
544
+ });
545
+
546
+ delay = 0;
547
+ for (let i = this._node.childNodes.length - 1; i >= 0; i--) {
548
+ let child = this._node.childNodes[i];
549
+ __BMVelocityAnimate(child, {translateY: '16px', translateZ: 0, opacity: 0}, {
550
+ duration: 100,
551
+ easing: 'easeInQuad',
552
+ delay: delay
553
+ });
554
+ delay += 16;
555
+ }
556
+
557
+ this._node = undefined;
558
+ this._containerNode = undefined;
559
+ }
560
+ };
561
+
562
+ /**
563
+ * Constructs and returns a menu with the specified items.
564
+ * The menu will be hidden by default; the method `openAtPoint(_)` should be used to make the menu visible.
565
+ *
566
+ * @param items <[BMMenuItem]> An array of menu items that will be displayed by this menu.
567
+ *
568
+ * @return <BMMenu> A menu.
569
+ */
570
+ BMMenu.menuWithItems = function (items) {
571
+ return (new BMMenu).initWithItems(items);
572
+ };
573
+
574
+ // @endtype