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,2000 @@
1
+ // @ts-check
2
+
3
+ import {YES, NO, BMExtend, BMCopyProperties} from '../Core/BMCoreUI'
4
+ import {BMPointMake} from '../Core/BMPoint'
5
+ import {BMRectMake, BMRectMakeWithNodeFrame} from '../Core/BMRect'
6
+ import {BMAnimateWithBlock, BMAnimationContextGetCurrent, BMAnimationContextEnableWebAnimations, __BMVelocityAnimate, BMHook} from '../Core/BMAnimationContext'
7
+ import {BMView} from '../BMView/BMView_v2.5'
8
+ import {BMLayoutOrientation} from '../BMView/BMLayoutSizeClass'
9
+ import { BMKeyboardShortcutModifier } from './BMKeyboardShortcut'
10
+ import { BMSizeMake } from '../Core/BMSize'
11
+ import { BMLayoutAttribute } from '../BMView/BMLayoutConstraint_v2.5'
12
+
13
+ //@ts-check
14
+ // @type BMWindowOverlay extends BMView
15
+
16
+ /**
17
+ * Window overlays are objects that are created by windows to mask the content below the window
18
+ * and allow clicking outside a window to close it.
19
+ */
20
+ class BMWindowOverlay extends BMView {
21
+
22
+ /**
23
+ * Initializes this window overlay for the given window.
24
+ * @param window <BMWindow> The window.
25
+ * {
26
+ * @param modal <Boolean, nullable> Defaults to `YES`. If set to `YES`, the blocker will prevent events from reaching elements behind it.
27
+ * When set to `NO`, it will allow events to propagate to those elements.
28
+ * }
29
+ * @return <BMWindowOverlay> This window overlay.
30
+ */
31
+ initWithWindow(window, args) {
32
+ this._window = window;
33
+
34
+ let modal = (args && 'modal' in args) ? args.modal : YES;
35
+
36
+ let node = document.createElement('div');
37
+ node.className = 'BMWindowBlocker';
38
+ // TODO: This isn't good for subclasses; consider using a class instead
39
+ BMCopyProperties(node.style, {
40
+ opacity: 0,
41
+ display: 'none',
42
+ backdropFilter: modal ? 'blur(15px)' : 'none',
43
+ webkitBackdropFilter: modal ? 'blur(15px)' : 'none'
44
+ });
45
+
46
+ if (!modal) {
47
+ node.style.pointerEvents = 'none';
48
+ node.classList.add('BMWindowBlockerNonModal');
49
+ }
50
+
51
+ if (modal) node.addEventListener('click', event => {
52
+ // Do not close if the click originated from inside the window.
53
+ if (event.target != node) return;
54
+
55
+ var shouldClose = YES;
56
+
57
+ if (window.delegate && window.delegate.windowShouldClose) {
58
+ shouldClose = window.delegate.windowShouldClose(window);
59
+ }
60
+
61
+ var targetRect = undefined;
62
+ var targetNode = undefined;
63
+
64
+ if (shouldClose) {
65
+ // Don't ask the delegate for an anchor if it has been set via properties
66
+ if (window._anchorPoint || window._anchorRect || window._anchorNode) {
67
+ window.dismissAnimated(YES);
68
+ }
69
+ else {
70
+ if (window.delegate && window.delegate.rectForDismissedWindow) {
71
+ targetRect = window.delegate.rectForDismissedWindow(window);
72
+ }
73
+
74
+ if (window.delegate && window.delegate.DOMNodeForDismissedWindow) {
75
+ targetNode = window.delegate.DOMNodeForDismissedWindow(window);
76
+ }
77
+
78
+ window.dismissAnimated(YES, {toRect: targetRect, toNode: targetNode});
79
+ }
80
+ }
81
+ });
82
+
83
+ return super.initWithDOMNode(node);
84
+ }
85
+
86
+ // @override - BMView
87
+ frameForDescendant(descendant) {
88
+ return this._window.frameForDescendant(descendant);
89
+ }
90
+
91
+ // @override - BMView
92
+ _registerLayout() {
93
+ if (this._window._visible) {
94
+ super._registerLayout();
95
+ }
96
+ else {
97
+ this._awaitsLayout = YES;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Invoked when this overlay's window will become visible.
103
+ */
104
+ _windowWillAppear() {
105
+ if (this._awaitsLayout) {
106
+ this._awaitsLayout = NO;
107
+ super._registerLayout();
108
+ this.layoutQueue.dequeue();
109
+ }
110
+ }
111
+
112
+
113
+ // @override - BMView
114
+ get _currentViewport() {
115
+ if (this._requiredViewport) return this._requiredViewport;
116
+
117
+ let viewport = new BMViewport;
118
+ viewport.init();
119
+
120
+ viewport._width = this._window._fullScreen ? window.innerWidth : this._window.frame.size.width;
121
+ viewport._height = this._window._fullScreen ? window.innerHeight : this._window.frame.size.height;
122
+ viewport._diagonal = Math.sqrt(Math.pow(viewport._width, 2) + Math.pow(viewport._height, 2));
123
+ viewport._orientation = viewport._width >= viewport._height ? BMLayoutOrientation.Landscape : BMLayoutOrientation.Portrait;
124
+ viewport._surfaceArea = viewport._width * viewport._height;
125
+
126
+ return viewport;
127
+ }
128
+
129
+ }
130
+
131
+ // @endtype
132
+
133
+ // @type BMWindow extends BMView
134
+
135
+ var _BMWindowAnimationDurationDefault = 400;
136
+ var _BMWindowAnimationEasingDefault = 'easeInOutQuart';
137
+ const _BMWindowAnimationEasingIncomingDefault = 'easeOutQuart';
138
+ const _BMWindowAnimationDurationIncomingDefault = 200;
139
+ const _BMWindowAnimationEasingOutgoingDefault = 'easeInQuart';
140
+ const _BMWindowAnimationDurationOutgoingDefault = 200;
141
+
142
+ const BM_WINDOW_SHOWCASE_MAX_ITERATIONS = 5000;
143
+ var BM_WINDOW_Z_INDEX_MAX = 2007;
144
+
145
+ /**
146
+ * A window is an object that manages the display and lifecycle of a popup window.
147
+ */
148
+ export function BMWindow() {}; // <constructor>
149
+
150
+ // Represents the current key window
151
+ BMWindow._keyWindow = undefined;
152
+
153
+ // An array containing all created non-modal windows
154
+ BMWindow._windows = [];
155
+
156
+ // An array containing all minimized non-modal window DOM nodes
157
+ BMWindow._minimizedWindows = [];
158
+
159
+
160
+ /**
161
+ * Returns the z-index value that the topmost non-modal window will have.
162
+ * Any z-index value higher than the value returned by this method is guaranteed
163
+ * to appear on top of any other window.
164
+ *
165
+ * Note that the value used by modal windows is always at least `BM_WINDOW_Z_INDEX_MAX + 1`.
166
+ * @return <Number> The maximum z-index value used by a non-modal `BMWindow`.
167
+ */
168
+ BMWindow.zIndexMax = function () {
169
+ return BM_WINDOW_Z_INDEX_MAX;
170
+ }
171
+
172
+ /**
173
+ * Minimizes all non-minimized windows.
174
+ * @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated.
175
+ */
176
+ BMWindow.minimizeAllAnimated = function (animated) {
177
+ for (let window of this._windows.slice()) {
178
+ if (!window._minimizedWindow) {
179
+ window.minimizeAnimated(animated);
180
+ }
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Restores all minimized windows.
186
+ * @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated.
187
+ */
188
+ BMWindow.restoreAllAnimated = function (animated) {
189
+ for (let window of this._windows.slice()) {
190
+ if (window._minimizedWindow) {
191
+ window.restoreAnimated(animated);
192
+ }
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Reveals all hidden windows.
198
+ */
199
+ BMWindow.revealAll = function () {
200
+ for (let window of this._windows.slice()) {
201
+ if (window._hidden) {
202
+ window.reveal();
203
+ }
204
+ }
205
+ }
206
+
207
+ /**
208
+ * Hides all windows.
209
+ */
210
+ BMWindow.hideAll = function () {
211
+ for (let window of this._windows.slice()) {
212
+ if (!window._hidden) {
213
+ window.hide();
214
+ }
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Set to `YES` while showcase mode is enabled.
220
+ */
221
+ BMWindow._isShowcasing = NO;
222
+
223
+ // Adds a global handler that listens for cmd + alt + a to enter or exit showcase
224
+ document.addEventListener('keydown', event => {
225
+ if (event.which == 69 && event.altKey && (navigator.userAgent.includes('Macintosh') ? event.metaKey : event.ctrlKey)) {
226
+ if (BMWindow._isShowcasing) {
227
+ BMWindow.exitShowcase();
228
+ }
229
+ else {
230
+ BMWindow.enterShowcase();
231
+ }
232
+ }
233
+ }, YES);
234
+
235
+ BMWindow._showcaseElements = [];
236
+
237
+ /**
238
+ * Registers an element that will participate in the window showcase.
239
+ * @param element <AnyObject> The showcase element.
240
+ */
241
+ BMWindow.registerShowcaseElement = function (element) {
242
+ BMWindow._showcaseElements.push(element);
243
+ }
244
+
245
+
246
+ /**
247
+ * Unregisters a showcase element.
248
+ * @param element <AnyObject> The showcase element.
249
+ */
250
+ BMWindow.unregisterShowcaseElement = function (element) {
251
+ BMWindow._showcaseElements.splice(BMWindow._showcaseElements.indexOf(element), 1);
252
+ }
253
+
254
+ /**
255
+ * Enters showcase mode, making it easy to select between open windows.
256
+ */
257
+ BMWindow.enterShowcase = async function () {
258
+ if (BMWindow._isShowcasing) return;
259
+
260
+ document.body.style.backgroundColor = 'rgb(60, 60, 60)';
261
+
262
+ BMWindow._isShowcasing = YES;
263
+
264
+ document.body.addEventListener('click', BMWindow.exitShowcase);
265
+
266
+ let openWindows = BMWindow._windows.filter(value => !value._minimizedWindow);
267
+
268
+ let viewportFrame = BMRectMake(0, 0, window.innerWidth, window.innerHeight);
269
+
270
+ for (let element of BMWindow._showcaseElements) {
271
+ openWindows.unshift(element);
272
+ }
273
+
274
+ openWindows.forEach(window => {
275
+ window.node.classList.add('BMWindowShowcaseWindow');
276
+
277
+ let selector = document.createElement('div');
278
+ selector.classList.add('BMWindowShowcaseWindowSelector');
279
+ selector.addEventListener('click', event => {
280
+ if (window.becomeKeyWindow) {
281
+ window.becomeKeyWindow();
282
+ }
283
+ else {
284
+ BMWindow.hideAll();
285
+ }
286
+ BMWindow.exitShowcase();
287
+ });
288
+
289
+ window.node.appendChild(selector);
290
+ });
291
+
292
+ let sourceRects = openWindows.map(window => window._fullScreen ? viewportFrame.copy() : window.frame.copy());
293
+ let originalRects = sourceRects.map(rect => rect.copy());
294
+ let overlap;
295
+
296
+ let bounds = viewportFrame.copy();
297
+
298
+ for (let i = 0; i < BM_WINDOW_SHOWCASE_MAX_ITERATIONS; i++) {
299
+
300
+ for (let j = 0; j < sourceRects.length; j++) {
301
+ for (let k = 0; k < sourceRects.length; k++) {
302
+ if (j != k && sourceRects[j].intersectsRect(sourceRects[k])) {
303
+ overlap = YES;
304
+
305
+ let rect1 = sourceRects[j];
306
+ let rect2 = sourceRects[k];
307
+
308
+ let difference = rect2.center.pointBySubtractingPoint(rect1.center);
309
+
310
+ if (!difference.x && !difference.y) {
311
+ difference.x = Math.random() * 2 - 1;
312
+ difference.y = Math.random() * 2 - 1;
313
+ }
314
+
315
+ if (bounds.height / bounds.width > viewportFrame.height / viewportFrame.width) {
316
+ difference.x = difference.x * 2;
317
+ }
318
+ else {
319
+ difference.y = difference.y * 2;
320
+ }
321
+
322
+ let length = Math.sqrt(difference.x * difference.x + difference.y * difference.y);
323
+ difference.x = difference.x * 20 / length;
324
+ difference.y = difference.y * 20 / length;
325
+
326
+ rect1.offsetWithX(-difference.x, {y: -difference.y});
327
+ rect2.offsetWithX(difference.x, {y: difference.y});
328
+
329
+ bounds = bounds.rectByUnionWithRect(rect1).rectByUnionWithRect(rect2);
330
+
331
+ }
332
+ }
333
+ }
334
+
335
+ if (!overlap) break;
336
+ }
337
+
338
+ let scale = Math.min(1, viewportFrame.width / bounds.width, viewportFrame.height / bounds.height);
339
+
340
+ for (let rect of sourceRects) {
341
+ rect.offset(-bounds.left, -bounds.top);
342
+
343
+ rect.origin.x = rect.origin.x * scale;
344
+ rect.origin.y = rect.origin.y * scale;
345
+
346
+ rect.size.width = rect.size.width * scale;
347
+ rect.size.height = rect.size.height * scale;
348
+ }
349
+
350
+ // This method appears to be faster when used with web animations, so they are always enabled here when available
351
+ BMAnimateWithBlock(() => {
352
+ BMAnimationContextEnableWebAnimations();
353
+
354
+ /*let header = document.getElementById('twStudioHeader');
355
+ let headerController = BMAnimationContextGetCurrent().controllerForObject(header, {node: header});
356
+ headerController.registerBuiltInProperty('translateY', {withValue: -header.offsetHeight + 'px'});*/
357
+
358
+ for (let i = 0; i < sourceRects.length; i++) {
359
+ let transformRect = originalRects[i].rectWithTransformToRect(sourceRects[i]);
360
+
361
+ let controller = BMAnimationContextGetCurrent().controllerForObject(openWindows[i].node, {node: openWindows[i].node});
362
+ controller.registerBuiltInPropertiesWithDictionary({
363
+ translateX: transformRect.origin.x + 'px',
364
+ translateY: transformRect.origin.y + 'px',
365
+ scaleX: transformRect.size.width,
366
+ scaleY: transformRect.size.height,
367
+ translateZ: 0
368
+ });
369
+ }
370
+ }, {duration: 300, easing: 'easeInOutQuad', complete: x => BMWindow.revealAll()});
371
+
372
+ document.body.addEventListener('wheel', BMWindow._wheelHandler, YES);
373
+
374
+ }
375
+
376
+ /**
377
+ * A mousewheel handler that is attached to the document during showcase mode.
378
+ * @param event <WheelEvent> The event.
379
+ */
380
+ BMWindow._wheelHandler = function (event) {
381
+ if (event.deltaY < 0) {
382
+ BMWindow.exitShowcase();
383
+ }
384
+ }
385
+
386
+ /**
387
+ * Exits showcase mode.
388
+ */
389
+ BMWindow.exitShowcase = function (event) {
390
+ if (event && event.target != document.body) return;
391
+
392
+ BMWindow._isShowcasing = NO;
393
+
394
+ document.body.removeEventListener('click', BMWindow.exitShowcase);
395
+ document.body.removeEventListener('wheel', BMWindow._wheelHandler, YES);
396
+
397
+ let openWindows = BMWindow._windows.filter(value => !value._minimizedWindow);
398
+ /*let twStudio = document.querySelector('#twStudioBody');
399
+ let pageWindow = {node: twStudio};
400
+ openWindows.unshift(pageWindow);*/
401
+
402
+ for (let element of BMWindow._showcaseElements) {
403
+ openWindows.unshift(element);
404
+ }
405
+
406
+ openWindows.forEach(window => {
407
+ window.node.classList.remove('BMWindowShowcaseWindow');
408
+
409
+ let selector = window.node.querySelector('.BMWindowShowcaseWindowSelector');
410
+ if (selector) selector.remove();
411
+ });
412
+
413
+ BMAnimateWithBlock(() => {
414
+ BMAnimationContextEnableWebAnimations();
415
+
416
+ /*let header = document.getElementById('twStudioHeader');
417
+ let headerController = BMAnimationContextGetCurrent().controllerForObject(header, {node: header});
418
+ headerController.registerBuiltInProperty('translateY', {withValue: '0px'});*/
419
+
420
+ for (let i = 0; i < openWindows.length; i++) {
421
+ let controller = BMAnimationContextGetCurrent().controllerForObject(openWindows[i].node, {node: openWindows[i].node});
422
+ controller.registerBuiltInPropertiesWithDictionary({
423
+ translateX: '0px',
424
+ translateY: '0px',
425
+ scaleX: 1,
426
+ scaleY: 1,
427
+ translateZ: 0
428
+ });
429
+ }
430
+ }, {duration: 300, easing: 'easeInOutQuad', complete: x => openWindows[0].node.style.transform = 'none'});
431
+ }
432
+
433
+ BMWindow.prototype = BMExtend(Object.create(BMView.prototype), {
434
+
435
+ /**
436
+ * An optional delegate which this window may notify of key events.
437
+ */
438
+ delegate: undefined, // <BMWindowDelegate, nullable>
439
+
440
+ /**
441
+ * The window's frame, which represents its on-screen area.
442
+ */
443
+ _frame: undefined, // <BMRect>
444
+
445
+ // @override - BMView
446
+ get contentNode() {
447
+ return this.content;
448
+ },
449
+
450
+ /**
451
+ * The view to which subviews should be added.
452
+ */
453
+ get contentView() { // <BMView>
454
+ return this;
455
+ },
456
+
457
+ get frame() {
458
+ return this._frame;
459
+ },
460
+ set frame(frame) {
461
+ if (this._fullScreen) return;
462
+
463
+ frame = frame.integralRect;
464
+
465
+ this._frame = frame.copy();
466
+
467
+ BMCopyProperties(this._window.style, {
468
+ left: frame.origin.x + 'px',
469
+ top: frame.origin.y + 'px',
470
+ width: frame.size.width + 'px',
471
+ height: frame.size.height + 'px'
472
+ });
473
+
474
+ switch (this.frameHorizontalPositionLayoutAttribute) {
475
+ case BMLayoutAttribute.Left:
476
+ case BMLayoutAttribute.Leading:
477
+ this.leftConstraint.constant = frame.origin.x;
478
+ break;
479
+ case BMLayoutAttribute.CenterX:
480
+ this.leftConstraint.constant = frame.center.x - this.superview.frame.center.x;
481
+ break;
482
+ case BMLayoutAttribute.Right:
483
+ case BMLayoutAttribute.Trailing:
484
+ this.leftConstraint.constant = frame.right - this.superview.frame.right;
485
+ break;
486
+ }
487
+
488
+ switch (this.frameVerticalPositionLayoutAttribute) {
489
+ case BMLayoutAttribute.Top:
490
+ this.topConstraint.constant = frame.origin.y;
491
+ break;
492
+ case BMLayoutAttribute.CenterY:
493
+ this.topConstraint.constant = frame.center.y - this.superview.frame.center.y;
494
+ break;
495
+ case BMLayoutAttribute.Bottom:
496
+ this.topConstraint.constant = frame.bottom - this.superview.frame.bottom;
497
+ break;
498
+ }
499
+
500
+ this.widthConstraint.constant = frame.size.width;
501
+ this.heightConstraint.constant = frame.size.height;
502
+ },
503
+
504
+ /**
505
+ * @protected
506
+ * The priority to use when setting up frame position constraints.
507
+ */
508
+ get framePositionPriority() { // <Number>
509
+ return 500;
510
+ },
511
+
512
+ /**
513
+ * @protected
514
+ * The priority to use when setting up frame size constraints.
515
+ */
516
+ get frameSizePriority() { // <Number>
517
+ return 750;
518
+ },
519
+
520
+ /**
521
+ * @protected
522
+ * The source anchor point to use when setting up the horizontal position constraint.
523
+ */
524
+ get frameHorizontalPositionLayoutAttribute() { // <BMLayoutAttribute>
525
+ return BMLayoutAttribute.Left;
526
+ },
527
+
528
+ /**
529
+ * Returns the name of the horizontal anchor from which the horizontal position constraint will be created.
530
+ */
531
+ get _frameHorizontalAnchorName() { // <String>
532
+ const key = this.frameHorizontalPositionLayoutAttribute;
533
+ return key.substring(0, 1).toLowerCase() + key.substring(1, key.length);
534
+ },
535
+
536
+ /**
537
+ * @protected
538
+ * The source anchor point to use when setting up the vertical position constraint.
539
+ */
540
+ get frameVerticalPositionLayoutAttribute() { // <BMLayoutAttribute>
541
+ return BMLayoutAttribute.Top;
542
+ },
543
+
544
+ /**
545
+ * Returns the name of the vertical anchor from which the vertical position constraint will be created.
546
+ */
547
+ get _frameVerticalAnchorName() { // <String>
548
+ const key = this.frameVerticalPositionLayoutAttribute;
549
+ return key.substring(0, 1).toLowerCase() + key.substring(1, key.length);
550
+ },
551
+
552
+ /**
553
+ * The window DOM node.
554
+ */
555
+ _window: undefined, // <DOMNode>
556
+
557
+ /**
558
+ * The window toolbar DOM node.
559
+ */
560
+ _toolbar: undefined, // <DOMNode>
561
+ get toolbar() {
562
+ return this._toolbar;
563
+ },
564
+
565
+ /**
566
+ * The window overlay.
567
+ */
568
+ _overlay: undefined, // <BMWindowOverlay>
569
+
570
+ /**
571
+ * The modal blocker DOM node.
572
+ */
573
+ _blocker: undefined, // <DOMNode>
574
+
575
+ /**
576
+ * The DOM node in which the window contents should be added.
577
+ */
578
+ _content: undefined, // <DOMNode>
579
+ get content() {
580
+ return this._content;
581
+ },
582
+
583
+ /**
584
+ * A string that is used for when this window is minimized.
585
+ */
586
+ _title: 'New Window', // <String>
587
+ get title() {
588
+ return this._title;
589
+ },
590
+ set title(title) {
591
+ this._title = title;
592
+ },
593
+
594
+ /**
595
+ * The point from which this popover should originate when open or dimissed with an animation.
596
+ * Setting this property causes the `anchorNode` and `anchorRect` properties to be set to `undefined`.
597
+ */
598
+ _anchorPoint: undefined, // <BMPoint, nullable>
599
+
600
+ get anchorPoint() {
601
+ return this._anchorPoint;
602
+ },
603
+ set anchorPoint(point) {
604
+ this._anchorPoint = point && point.copy();
605
+ this.anchorNode = undefined;
606
+ this._anchorRect = undefined;
607
+ },
608
+
609
+ /**
610
+ * The rect from which this popover should originate when open or dimissed with an animation.
611
+ * Setting this property causes the `anchorNode` and `anchorPoint` properties to be set to `undefined`.
612
+ */
613
+ _anchorRect: undefined, // <BMRect, nullable>
614
+
615
+ get anchorRect() {
616
+ return this._anchorRect;
617
+ },
618
+ set anchorRect(rect) {
619
+ this._anchorRect = rect && rect.copy();
620
+ this.anchorNode = undefined;
621
+ this._anchorPoint = undefined;
622
+ },
623
+
624
+
625
+ /**
626
+ * The node from which this popover should originate when open or dimissed with an animation.
627
+ * Setting this property causes the `anchorPoint` and `anchorRect` properties to be set to `undefined`.
628
+ */
629
+ _anchorNode: undefined, // <DOMNode, nullable>
630
+
631
+ get anchorNode() {
632
+ return this._anchorNode;
633
+ },
634
+ set anchorNode(node) {
635
+ this._anchorNode = node;
636
+ this._anchorPoint = undefined;
637
+ this._anchorRect = undefined;
638
+ },
639
+
640
+ /**
641
+ * Set to `YES` while this window is visible.
642
+ */
643
+ _visible: NO, // <Boolean>
644
+
645
+ /**
646
+ * Set to `YES` while this window is visible.
647
+ */
648
+ get isVisible() { // <Boolean>
649
+ return this._visible;
650
+ },
651
+
652
+ /**
653
+ * Set to `YES` while this window is full screen.
654
+ */
655
+ _fullScreen: NO, // <Boolean>
656
+
657
+ /**
658
+ * Set to `YES` while this window is full screen.
659
+ */
660
+ get isFullScreen() { // <Boolean>
661
+ return this._fullScreen;
662
+ },
663
+
664
+ /**
665
+ * Set to `YES` if this window is modal.
666
+ */
667
+ _modal: NO, // <Boolean>
668
+
669
+ /**
670
+ * Set to `YES` if this window is modal.
671
+ */
672
+ get isModal() {
673
+ return this._modal;
674
+ },
675
+
676
+ /**
677
+ * Set to `YES` if this window is a tool window.
678
+ */
679
+ _toolWindow: NO, // <Boolean>
680
+
681
+ /**
682
+ * An array containing the tool windows associated with this window.
683
+ */
684
+ _toolWindows: undefined, // <[BMToolWindow]>
685
+
686
+ /**
687
+ * The keyboard shortcuts that have been registered to this window.
688
+ */
689
+ _keyboardShortcuts: undefined, // <[BMKeyboardShortcut]>
690
+
691
+ /**
692
+ * Initializes this window with the given screen area.
693
+ * @param frame <BMRect> The frame.
694
+ * {
695
+ * @param toolbar <Boolean, nullable> Defaults to `YES`. If set to `YES`, the window will have a toolbar.
696
+ * @param modal <Boolean, nullable> Defaults to `YES`. If set to `YES`, the window will be modal, blocking content behind it.
697
+ * When set to `NO`, it will be possible to interact with content that is not obstructed by this window.
698
+ * }
699
+ * @return <BMWindow> This window.
700
+ */
701
+ initWithFrame: function (frame, args) {
702
+ frame = frame.integralRect;
703
+
704
+ let modal = (args && 'modal' in args) ? args.modal : YES;
705
+
706
+ this._toolWindows = [];
707
+ this._keyboardShortcuts = [];
708
+
709
+ this._overlay = (new BMWindowOverlay).initWithWindow(this, {modal});
710
+ this._blocker = this._overlay.node;
711
+
712
+ let node = document.createElement('div');
713
+ this._window = node;
714
+ node.className = 'BMWindow';
715
+
716
+ BMCopyProperties(node.style, {
717
+ opacity: 0,
718
+ display: 'none'
719
+ });
720
+
721
+ this._modal = modal;
722
+
723
+ if (!modal) {
724
+ // BMCopyProperties(node.style, {
725
+ // pointerEvents: 'none',
726
+ // backdropFilter: modal ? 'none' : 'blur(15px)',
727
+ // webkitBackdropFilter: modal ? 'none' : 'blur(15px)'
728
+ // });
729
+
730
+ // In non-modal mode, clicking the window should cause it to become the key window
731
+ if (!this._toolWindow) {
732
+ node.addEventListener('mousedown', event => {
733
+ if (!this.isKeyWindow) this.becomeKeyWindow();
734
+ }, YES);
735
+
736
+ node.classList.add('BMWindowInactive');
737
+
738
+ BMWindow._windows.push(this);
739
+ }
740
+
741
+ node.classList.add('BMNonModalWindow');
742
+ }
743
+ else {
744
+ node.classList.add('BMModalWindow');
745
+ }
746
+
747
+ this._content = document.createElement('div');
748
+ this._content.className = 'BMWindowContent';
749
+ node.appendChild(this._content);
750
+
751
+ var withToolbar = (!args || args.toolbar === undefined) ? YES : args.toolbar;
752
+
753
+ if (withToolbar) {
754
+ this._toolbar = document.createElement('div');
755
+ this._toolbar.className = 'BMWindowToolbar';
756
+ node.appendChild(this._toolbar);
757
+ }
758
+ else {
759
+ this._content.className = 'BMWindowContent BMWindowFullContent';
760
+ }
761
+
762
+ document.body.appendChild(this._blocker);
763
+ BMView.prototype.initWithDOMNode.call(this, node);
764
+ this._overlay.addSubview(this);
765
+ document.body.appendChild(node);
766
+
767
+ this.leftConstraint = this[this._frameHorizontalAnchorName].equalTo(this._overlay[this._frameHorizontalAnchorName], {plus: frame.origin.x, priority: this.framePositionPriority});
768
+ this.topConstraint = this[this._frameVerticalAnchorName].equalTo(this._overlay[this._frameVerticalAnchorName], {plus: frame.origin.y, priority: this.framePositionPriority});
769
+ this.widthConstraint = this.width.equalTo(frame.size.width, {priority: this.frameSizePriority});
770
+ this.heightConstraint = this.height.equalTo(frame.size.height, {priority: this.frameSizePriority});
771
+ this.frame = frame;
772
+
773
+ this.leftConstraint.isActive = YES;
774
+ this.topConstraint.isActive = YES;
775
+ this.widthConstraint.isActive = YES;
776
+ this.heightConstraint.isActive = YES;
777
+
778
+ if (!modal && withToolbar) {
779
+ // In non-modal mode, allow the toolbar to move the window
780
+ this.maxLeftConstraint = this.left.greaterThanOrEqualTo(0);
781
+ this.maxTopConstraint = this.top.greaterThanOrEqualTo(0);
782
+
783
+ this.maxRightConstraint = this.right.lessThanOrEqualTo(window.innerWidth);
784
+ this.maxBottomConstraint = this.bottom.lessThanOrEqualTo(window.innerHeight);
785
+
786
+ this.maxLeftConstraint.isActive = YES;
787
+ this.maxTopConstraint.isActive = YES;
788
+
789
+ this.maxRightConstraint.isActive = YES;
790
+ this.maxBottomConstraint.isActive = YES;
791
+
792
+ window.addEventListener('resize', this._boundViewportDidResize = this._viewportDidResize.bind(this));
793
+
794
+ // Initialize dragging touch events for the toolbar
795
+ this._toolbar.addEventListener('mousedown', event => {
796
+ // Don't process events originating from children of the toolbar
797
+ if (event.target != this._toolbar) return;
798
+
799
+ // Full screen windows cannot be moved
800
+ if (this._fullScreen) return;
801
+
802
+ this._dragged = YES;
803
+
804
+ this._position = BMPointMake(this.node.offsetLeft, this.node.offsetTop);
805
+ let lastPosition = BMPointMake(event.clientX, event.clientY);
806
+
807
+ let mouseMoveEventListener = event => {
808
+ let position = BMPointMake(event.clientX, event.clientY);
809
+ const newPosition = BMPointMake(this._position.x + position.x - lastPosition.x, this._position.y + position.y - lastPosition.y);
810
+
811
+ // Check if the window can move
812
+ let canMove = YES;
813
+ if (this.delegate && this.delegate.windowShouldMove) {
814
+ canMove = this.delegate.windowShouldMove(this, newPosition);
815
+ }
816
+ if (!canMove) return;
817
+
818
+ this.leftConstraint.constant = this._position.x + position.x - lastPosition.x;
819
+ this.topConstraint.constant = this._position.y + position.y - lastPosition.y;
820
+ this._position = newPosition;
821
+ lastPosition = position;
822
+ this.layout();
823
+ event.preventDefault();
824
+ };
825
+
826
+ let mouseUpEventListener = event => {
827
+ window.removeEventListener('mousemove', mouseMoveEventListener, YES);
828
+ window.removeEventListener('mouseup', mouseUpEventListener, YES);
829
+ }
830
+
831
+ window.addEventListener('mousemove', mouseMoveEventListener, YES);
832
+ window.addEventListener('mouseup', mouseUpEventListener, YES);
833
+
834
+ event.preventDefault();
835
+ });
836
+
837
+ let touchDragPoint;
838
+
839
+ this._toolbar.addEventListener('touchstart', /** @type {TouchEvent} */ event => {
840
+ // If there is already a drag in progress, don't process this new event
841
+ if (typeof touchDragPoint !== 'undefined') {
842
+ return;
843
+ }
844
+
845
+ // Don't process events originating from children of the toolbar
846
+ if (event.target != this._toolbar) return;
847
+
848
+ // Full screen windows cannot be moved
849
+ if (this._fullScreen) return;
850
+
851
+ // Only use the first touch point
852
+ touchDragPoint = event.changedTouches[0].identifier;
853
+ this._dragged = YES;
854
+
855
+ this._position = BMPointMake(this.node.offsetLeft, this.node.offsetTop);
856
+ let lastPosition = BMPointMake(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
857
+
858
+ let mouseMoveEventListener = event => {
859
+ // Look for the actively tracked touch point
860
+ let touch;
861
+ for (let changedTouch of event.changedTouches) {
862
+ if (changedTouch.identifier == touchDragPoint) {
863
+ touch = changedTouch;
864
+ break;
865
+ }
866
+ }
867
+
868
+ // If the actively tracked touch point did not move, do not process this event
869
+ if (!touch) return;
870
+
871
+ let position = BMPointMake(touch.clientX, touch.clientY);
872
+ const newPosition = BMPointMake(this._position.x + position.x - lastPosition.x, this._position.y + position.y - lastPosition.y);
873
+
874
+ // Check if the window can move
875
+ let canMove = YES;
876
+ if (this.delegate && this.delegate.windowShouldMove) {
877
+ canMove = this.delegate.windowShouldMove(this, newPosition);
878
+ }
879
+ if (!canMove) return;
880
+
881
+ this.leftConstraint.constant = this._position.x + position.x - lastPosition.x;
882
+ this.topConstraint.constant = this._position.y + position.y - lastPosition.y;
883
+ this._position = newPosition;
884
+ lastPosition = position;
885
+ this.layout();
886
+ event.preventDefault();
887
+ };
888
+
889
+ let mouseUpEventListener = event => {
890
+ touchDragPoint = undefined;
891
+ window.removeEventListener('touchmove', mouseMoveEventListener);
892
+ window.removeEventListener('touchend', mouseUpEventListener);
893
+ window.removeEventListener('touchcancel', mouseUpEventListener);
894
+ }
895
+
896
+ window.addEventListener('touchmove', mouseMoveEventListener);
897
+ window.addEventListener('touchend', mouseUpEventListener);
898
+ window.removeEventListener('touchcancel', mouseUpEventListener);
899
+
900
+ event.preventDefault();
901
+ });
902
+
903
+ this._toolbar.addEventListener('dbclick', event => {
904
+ this.minimizeAnimated(YES);
905
+ });
906
+
907
+ this._toolbar.addEventListener('wheel', /** @param {WheelEvent} event */ event => {
908
+ // Scrolling on the toolbar should enter showcase
909
+ if (event.deltaY > 0) {
910
+ // Allow the delegate to suppress this behaviour
911
+ let shouldEnterShowcase = YES;
912
+ if (this.delegate && this.delegate.windowShouldEnterShowcase) {
913
+ shouldEnterShowcase = this.delegate.windowShouldEnterShowcase(this);
914
+ }
915
+
916
+ if (shouldEnterShowcase) {
917
+ BMWindow.enterShowcase();
918
+ }
919
+ }
920
+ });
921
+
922
+ // Also create a drag handle to resize the window
923
+ let dragHandle = this._dragHandle = document.createElement('div');
924
+ dragHandle.className = 'material-icons BMWindowDragHandle';
925
+ dragHandle.innerText = 'dehaze';
926
+ dragHandle.style.cursor = 'nwse-resize';
927
+ node.appendChild(dragHandle);
928
+
929
+
930
+ // Initialize dragging touch events for the drag handler
931
+ dragHandle.addEventListener('mousedown', event => {
932
+
933
+ // Full screen windows cannot be resized
934
+ if (this._fullScreen) return;
935
+
936
+ this._dragged = YES;
937
+
938
+ this._position = BMPointMake(this.widthConstraint.constant, this.heightConstraint.constant);
939
+ let lastPosition = BMPointMake(event.clientX, event.clientY);
940
+
941
+ let mouseMoveEventListener = event => {
942
+ let position = BMPointMake(event.clientX, event.clientY);
943
+
944
+ const newWidth = this._position.x + position.x - lastPosition.x;
945
+ const newHeight = this._position.y + position.y - lastPosition.y;
946
+
947
+ this._position = BMPointMake(newWidth, newHeight);
948
+ lastPosition = position;
949
+
950
+ let canResize = YES;
951
+ if (this.delegate && this.delegate.windowShouldResize) {
952
+ canResize = this.delegate.windowShouldResize(this, BMSizeMake(newWidth, newHeight));
953
+ }
954
+ if (!canResize) return;
955
+
956
+ this.widthConstraint.constant = newWidth;
957
+ this.heightConstraint.constant = newHeight;
958
+ this.frame.size.width = this.widthConstraint.constant;
959
+ this.frame.size.height = this.heightConstraint.constant;
960
+ this.layout();
961
+ if (this.delegate && this.delegate.windowDidResize) {
962
+ this.delegate.windowDidResize(this, {toSize: this.frame.size});
963
+ }
964
+ event.preventDefault();
965
+ };
966
+
967
+ let mask = document.createElement('div');
968
+ mask.className = 'BMLayoutGuideMask';
969
+ mask.style.cursor = 'nwse-resize';
970
+ document.body.appendChild(mask);
971
+
972
+ let mouseUpEventListener = event => {
973
+ window.removeEventListener('mousemove', mouseMoveEventListener, YES);
974
+ window.removeEventListener('mouseup', mouseUpEventListener, YES);
975
+ mask.remove();
976
+ }
977
+
978
+ window.addEventListener('mousemove', mouseMoveEventListener, YES);
979
+ window.addEventListener('mouseup', mouseUpEventListener, YES);
980
+
981
+ event.preventDefault();
982
+ });
983
+
984
+ let touchDragHandlePoint;
985
+
986
+ dragHandle.addEventListener('touchstart', /** @type {TouchEvent} */ event => {
987
+ // Full screen windows cannot be resized
988
+ if (this._fullScreen) return;
989
+
990
+ // If there is already a drag in progress, don't process this new event
991
+ if (typeof touchDragPoint !== 'undefined') {
992
+ return;
993
+ }
994
+
995
+ // Only use the first touch point
996
+ touchDragHandlePoint = event.changedTouches[0].identifier;
997
+ this._dragged = YES;
998
+
999
+ this._position = BMPointMake(this.widthConstraint.constant, this.heightConstraint.constant);
1000
+ let lastPosition = BMPointMake(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
1001
+
1002
+ let mouseMoveEventListener = event => {
1003
+ // Look for the actively tracked touch point
1004
+ let touch;
1005
+ for (let changedTouch of event.changedTouches) {
1006
+ if (changedTouch.identifier == touchDragHandlePoint) {
1007
+ touch = changedTouch;
1008
+ break;
1009
+ }
1010
+ }
1011
+
1012
+ // If the actively tracked touch point did not move, do not process this event
1013
+ if (!touch) return;
1014
+
1015
+ let position = BMPointMake(touch.clientX, touch.clientY);
1016
+
1017
+ const newWidth = this._position.x + position.x - lastPosition.x;
1018
+ const newHeight = this._position.y + position.y - lastPosition.y;
1019
+
1020
+ this._position = BMPointMake(newWidth, newHeight);
1021
+ lastPosition = position;
1022
+
1023
+ let canResize = YES;
1024
+ if (this.delegate && this.delegate.windowShouldResize) {
1025
+ canResize = this.delegate.windowShouldResize(this, BMSizeMake(newWidth, newHeight));
1026
+ }
1027
+ if (!canResize) return;
1028
+
1029
+ this.widthConstraint.constant = newWidth;
1030
+ this.heightConstraint.constant = newHeight;
1031
+ this.frame.size.width = this.widthConstraint.constant;
1032
+ this.frame.size.height = this.heightConstraint.constant;
1033
+
1034
+ this.layout();
1035
+ if (this.delegate && this.delegate.windowDidResize) {
1036
+ this.delegate.windowDidResize(this, {toSize: this.frame.size});
1037
+ }
1038
+ event.preventDefault();
1039
+ };
1040
+
1041
+ let mouseUpEventListener = event => {
1042
+ touchDragHandlePoint = undefined;
1043
+ window.removeEventListener('touchmove', mouseMoveEventListener);
1044
+ window.removeEventListener('touchend', mouseUpEventListener);
1045
+ window.removeEventListener('touchcancel', mouseUpEventListener);
1046
+ }
1047
+
1048
+ window.addEventListener('touchmove', mouseMoveEventListener);
1049
+ window.addEventListener('touchend', mouseUpEventListener);
1050
+ window.removeEventListener('touchcancel', mouseUpEventListener);
1051
+
1052
+ event.preventDefault();
1053
+ });
1054
+ }
1055
+
1056
+ return this;
1057
+ },
1058
+
1059
+ /**
1060
+ * Invoked on non-modal windows when the viewport resizes.
1061
+ * @param event <Event> The event that triggered this action.
1062
+ */
1063
+ _viewportDidResize(event) {
1064
+ this.maxRightConstraint.constant = window.innerWidth;
1065
+ this.maxBottomConstraint.constant = window.innerHeight;
1066
+
1067
+ if (this._fullScreen) this.layout();
1068
+ },
1069
+
1070
+ /**
1071
+ * Registers a keyboard shortcut that can be activated when this window is the key window.
1072
+ * @param shortcut <BMKeyboardShortcut> The keyboard shortcut to register.
1073
+ */
1074
+ registerKeyboardShortcut(shortcut) {
1075
+ if (!this._keyboardShortcuts.length) this._enableKeyboardShortcuts();
1076
+
1077
+ this._keyboardShortcuts.push(shortcut);
1078
+ },
1079
+
1080
+ /**
1081
+ * Unregisters a keyboard shortcut. If this keyboard shortcut had not been previously registered, this method does nothing.
1082
+ * @param shortcut <BMKeyboardShortcut> The keyboard shortcut to unregister.
1083
+ */
1084
+ unregisterKeyboardShortcut(shortcut) {
1085
+ for (let i = 0; i < this._keyboardShortcuts.length; i++) {
1086
+ if (this._keyboardShortcuts[i] == shortcut) {
1087
+ this._keyboardShortcuts.splice(i, 1);
1088
+ break;
1089
+ }
1090
+ }
1091
+
1092
+ if (!this._keyboardShortcuts.length) this._disableKeyboardShortcuts();
1093
+ },
1094
+
1095
+ /**
1096
+ * If keyboard shortcuts have been registered on this window, this method will be invoked to identify
1097
+ * and handle key presses. Subclasses that override this method should invoke the base implementation to allow
1098
+ * keyboard shortcuts to be handled correctly.
1099
+ * @param event <KeyboardEvent> The event that triggered this action.
1100
+ */
1101
+ keyPressedWithEvent(event) {
1102
+ // Check if a shortcut key has been pressed.
1103
+ for (const shortcut of this._keyboardShortcuts) {
1104
+ if (event.code != shortcut.keyCode) continue;
1105
+
1106
+ // Build the modifier bitmap for this event
1107
+ let bitmap = 0;
1108
+ for (const modifier in BMKeyboardShortcutModifier) {
1109
+ if (event[BMKeyboardShortcutModifier[modifier].key]) {
1110
+ bitmap = bitmap | BMKeyboardShortcutModifier[modifier].value;
1111
+ }
1112
+ }
1113
+
1114
+ if (bitmap == shortcut._modifierBitmap) {
1115
+ if (shortcut.preventsDefault) event.preventDefault();
1116
+ shortcut.target[shortcut.action](event);
1117
+ }
1118
+ }
1119
+ },
1120
+
1121
+ /**
1122
+ * Set to `YES` if keyboard shortcuts are enabled.
1123
+ */
1124
+ _keyboardShortcutsEnabled: NO, // <Boolean>
1125
+
1126
+ /**
1127
+ * Enables keyboard shortcut handling. This makes the window's `node` focusable and attaches
1128
+ * the relevant event handlers to it. If this window is the key window and the keyboard focus
1129
+ * is outside of this window, this window will acquire keyboard focus.
1130
+ */
1131
+ _enableKeyboardShortcuts() {
1132
+ this.node.tabIndex = -1;
1133
+
1134
+ if (document.activeElement && !this.node.contains(document.activeElement)) {
1135
+ this.node.focus();
1136
+ }
1137
+ else if (!document.activeElement) {
1138
+ this.node.focus();
1139
+ }
1140
+
1141
+ this._keypressHandler = (event) => {
1142
+ this.keyPressedWithEvent(event);
1143
+ };
1144
+
1145
+ this.node.addEventListener('keydown', this._keypressHandler);
1146
+ this._keyboardShortcutsEnabled = YES;
1147
+ },
1148
+
1149
+ /**
1150
+ * Disables keyboard shortcut handling. This detaches the relevant event handlers from this window's `node`.
1151
+ */
1152
+ _disableKeyboardShortcuts() {
1153
+ this.node.removeEventListener('keydown', this._keypressHandler);
1154
+ this._keyboardShortcutsEnabled = NO;
1155
+ },
1156
+
1157
+ /**
1158
+ * If this window is visible, this method dismisses it. Otherwise the window is made visible.
1159
+ * @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated, otherwise it will be instant.
1160
+ * {
1161
+ * @param completionHandler <void ^ (), nullable> If specified, this handler will be invoked when the operation completes.
1162
+ * }
1163
+ */
1164
+ toggleAnimated: function (animated, args) {
1165
+ if (this._visible) {
1166
+ this.dismissAnimated(animated, args);
1167
+ }
1168
+ else {
1169
+ this.bringToFrontAnimated(animated, args);
1170
+ }
1171
+ },
1172
+
1173
+ /**
1174
+ * @protected
1175
+ * This method is invoked during `bringToFrontAnimated()` to run the animations that will make this window visible.
1176
+ * Subclasses can override this method to perform custom animations.
1177
+ *
1178
+ * This method is not invoked from within an animation context.
1179
+ * @param completionHandler <void ^()> A method that custom implementations must invoke when the animation completes.
1180
+ */
1181
+ animateInWithCompletionHandler(completionHandler) {
1182
+ const self = this;
1183
+ if (this._anchorRect) {
1184
+ const rect = this._anchorRect;
1185
+ const frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
1186
+ const transformRect = frame.rectWithTransformToRect(rect);
1187
+
1188
+ __BMVelocityAnimate(this._window, {opacity: 1, translateX: ['0px', transformRect.origin.x + 'px'], translateY: ['0px', transformRect.origin.y + 'px'],
1189
+ scaleX: [1, transformRect.size.width], scaleY: [1, transformRect.size.height]}, {
1190
+ duration: _BMWindowAnimationDurationDefault,
1191
+ easing: _BMWindowAnimationEasingDefault,
1192
+ display: 'block'
1193
+ }, YES);
1194
+ }
1195
+ else if (this._anchorNode) {
1196
+ // Create a copy of the given node which will be used in the animation in place of the original node
1197
+ const node = this._anchorNode;
1198
+ const rect = BMRectMakeWithNodeFrame(node);
1199
+ const animationNode = node.cloneNode(YES);
1200
+
1201
+ const nodeDisplay = node.style.display;
1202
+
1203
+ animationNode.style.position = 'fixed';
1204
+ animationNode.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
1205
+ animationNode.style.transform = '';
1206
+ BMCopyProperties(animationNode.style, {left: rect.origin.x + 'px', top: rect.origin.y + 'px', width: rect.size.width + 'px', height: rect.size.height + 'px', margin: 0});
1207
+
1208
+ // Disable pointer events on the new view so that hover states do not cause the layer to be redrawn mid-animation
1209
+ animationNode.style.pointerEvents = 'none';
1210
+
1211
+ // Compute the transform and apply it first to the window
1212
+ // and then to the animation node, but inversed
1213
+ const frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
1214
+ const transformRect = frame.rectWithTransformToRect(rect);
1215
+
1216
+ document.body.appendChild(animationNode);
1217
+ node.style.display = 'none';
1218
+
1219
+ __BMVelocityAnimate(this._window, {opacity: 1, translateX: ['0px', transformRect.origin.x + 'px'], translateY: ['0px', transformRect.origin.y + 'px'],
1220
+ scaleX: [1, transformRect.size.width], scaleY: [1, transformRect.size.height]}, {
1221
+ duration: _BMWindowAnimationDurationDefault,
1222
+ easing: _BMWindowAnimationEasingDefault,
1223
+ display: 'block',
1224
+ complete: completionHandler
1225
+ }, YES);
1226
+
1227
+ // After finishing the animation, remove the temporary node and restore the original node's display
1228
+ __BMVelocityAnimate(animationNode, {opacity: [0, 1], translateX: -transformRect.origin.x + 'px', translateY: -transformRect.origin.y + 'px',
1229
+ scaleX: 1 / transformRect.size.width, scaleY: 1 / transformRect.size.height}, {
1230
+ duration: _BMWindowAnimationDurationDefault,
1231
+ easing: _BMWindowAnimationEasingDefault,
1232
+ complete: function () {
1233
+ animationNode.remove();
1234
+ var windowShouldKeepNodeHidden = NO;
1235
+ if (self.delegate && self.delegate.windowShouldKeepNodeHidden) {
1236
+ windowShouldKeepNodeHidden = self.delegate.windowShouldKeepNodeHidden(self, node);
1237
+ }
1238
+ if (windowShouldKeepNodeHidden) {
1239
+ self._nodeDisplay = nodeDisplay;
1240
+ }
1241
+ else {
1242
+ node.style.display = nodeDisplay;
1243
+ }
1244
+
1245
+ completionHandler();
1246
+ }
1247
+ }, YES);
1248
+ }
1249
+ else {
1250
+
1251
+ __BMVelocityAnimate(this._window, {opacity: 1, scaleX: [1, .9], scaleY: [1, .9], translateZ: 0}, {
1252
+ duration: _BMWindowAnimationDurationIncomingDefault,
1253
+ easing: _BMWindowAnimationEasingIncomingDefault,
1254
+ display: 'block',
1255
+ complete: completionHandler
1256
+ }, YES);
1257
+
1258
+ }
1259
+ },
1260
+
1261
+ /**
1262
+ * Should be invoked to make this window visible. Subclasses overriding this method should invoke the base method at some point in their implementation.
1263
+ * @param animated <Boolean, nullable> Defaults to YES. If set to YES, this change will be animated, otherwise it will be instant.
1264
+ * {
1265
+ * @param fromRect <BMRect, nullable> *Deprecated - Use the `anchorRect` property instead*. Ignored if either `anchorPoint`, `anchorRect` or `anchorNode` are set.
1266
+ * Sets the value of the `anchorRect` property to the given value until this operation completes.
1267
+ * @param fromNode <DOMNode, nullable> *Deprecated - Use the `anchorRect` property instead*. Ignored if either `anchorPoint`, `anchorRect` or `anchorNode` are set.
1268
+ * Sets the value of the `anchorNode` property to the given value until this operation completes.
1269
+ * @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been made visible.
1270
+ * }
1271
+ */
1272
+ bringToFrontAnimated: function (animated, args = {}) {
1273
+ this._visible = YES;
1274
+
1275
+ this.becomeKeyWindow();
1276
+
1277
+ for (let window of this._toolWindows) {
1278
+ if (window.opensAutomatically) window.bringToFrontAnimated(animated);
1279
+ }
1280
+
1281
+ animated = (animated === undefined ? YES : animated);
1282
+
1283
+ //__BMVelocityAnimate(this._blocker, 'stop');
1284
+ //__BMVelocityAnimate(this._window, 'stop');
1285
+
1286
+ var self = this;
1287
+
1288
+ if (self.delegate && self.delegate.windowWillAppear) {
1289
+ self.delegate.windowWillAppear(self);
1290
+ }
1291
+
1292
+ if (animated) {
1293
+
1294
+ this._blocker.style.pointerEvents = 'none';
1295
+ this._window.style.pointerEvents = 'none';
1296
+
1297
+ __BMVelocityAnimate(this._blocker, {opacity: 1, translateZ: 0}, {
1298
+ duration: 500,
1299
+ easing: 'easeInOutQuint',
1300
+ display: 'block',
1301
+ /*progress: function (elements, complete) {
1302
+ self._blocker.style.webkitBackdropFilter = 'blur(' + (complete * 15).toFixed(2) + 'px)';
1303
+ self._blocker.style.backdropFilter = 'blur(' + (complete * 15).toFixed(2) + 'px)';
1304
+ },*/
1305
+ complete: function () {
1306
+ self._blocker.style.pointerEvents = '';
1307
+ self._window.style.pointerEvents = '';
1308
+
1309
+ if (self.delegate && self.delegate.windowDidAppear) {
1310
+ self.delegate.windowDidAppear(self);
1311
+ }
1312
+ if (args && args.completionHandler) args.completionHandler();
1313
+ }
1314
+ }, YES);
1315
+
1316
+ // Included for compatibility with the anchor arguments,
1317
+ // due to be removed soon
1318
+ let completionHandler;
1319
+ if (this._anchorNode || this._anchorRect || this._anchorPoint) {
1320
+ completionHandler = () => {};
1321
+ }
1322
+ else {
1323
+ if (args.fromRect) {
1324
+ this.anchorRect = args.fromRect;
1325
+ }
1326
+ if (args.fromNode) {
1327
+ this.anchorNode = args.fromNode;
1328
+ }
1329
+ completionHandler = () => {
1330
+ this.anchorRect = undefined;
1331
+ this.anchorNode = undefined;
1332
+ }
1333
+ }
1334
+
1335
+ this.animateInWithCompletionHandler(completionHandler);
1336
+
1337
+ this._blocker.style.display = 'block';
1338
+ this._window.style.display = 'block';
1339
+
1340
+ this._overlay._windowWillAppear();
1341
+
1342
+ }
1343
+ else {
1344
+ this._blocker.style.opacity = 1;
1345
+ this._blocker.style.display = 'block';
1346
+
1347
+ this._window.style.opacity = 1;
1348
+ BMHook(this._window, {scaleX: 1, scaleY: 1});
1349
+ this._window.style.display = 'block';
1350
+
1351
+ this._overlay._windowWillAppear();
1352
+
1353
+ this._blocker.style.pointerEvents = '';
1354
+ this._window.style.pointerEvents = '';
1355
+
1356
+ if (self.delegate && self.delegate.windowDidAppear) self.delegate.windowDidAppear(self);
1357
+
1358
+ if (args && args.completionHandler) args.completionHandler();
1359
+ }
1360
+
1361
+ if (this._keyboardShortcutsEnabled) {
1362
+ this.node.focus();
1363
+ }
1364
+ },
1365
+
1366
+
1367
+
1368
+ /**
1369
+ * @protected
1370
+ * This method is invoked during `dismissAnimated()` to run the animations that will make this window hidden.
1371
+ * Subclasses can override this method to perform custom animations.
1372
+ *
1373
+ * This method is not invoked from within an animation context.
1374
+ * @param completionHandler <void ^()> A method that custom implementations must invoke when the animation completes.
1375
+ */
1376
+ animateOutWithCompletionHandler(completionHandler) {
1377
+ const self = this;
1378
+
1379
+ if (this._anchorRect) {
1380
+ const rect = this._anchorRect;
1381
+ const frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
1382
+ const transformRect = frame.rectWithTransformToRect(rect);
1383
+
1384
+ __BMVelocityAnimate(this._window, {opacity: 0, translateX: transformRect.origin.x + 'px', translateY: transformRect.origin.y + 'px',
1385
+ scaleX: transformRect.size.width, scaleY: transformRect.size.height}, {
1386
+ duration: _BMWindowAnimationDurationDefault,
1387
+ easing: _BMWindowAnimationEasingDefault,
1388
+ display: 'none',
1389
+ complete: completionHandler
1390
+ }, YES);
1391
+ }
1392
+ else if (this._anchorNode) {
1393
+ // Create a copy of the given node which will be used in the animation in place of the original node
1394
+ const node = this._anchorNode;
1395
+
1396
+ if ('_nodeDisplay' in self) {
1397
+ node.style.display = self._nodeDisplay;
1398
+ }
1399
+
1400
+ const rect = BMRectMakeWithNodeFrame(node);
1401
+ const animationNode = node.cloneNode(YES);
1402
+
1403
+ const nodeDisplay = ('_nodeDisplay' in self) ? self._nodeDisplay : node.style.display;
1404
+ self._nodeDisplay = undefined;
1405
+
1406
+ animationNode.style.position = 'fixed';
1407
+ animationNode.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
1408
+ animationNode.style.transform = '';
1409
+ BMCopyProperties(animationNode.style, {left: rect.origin.x + 'px', top: rect.origin.y + 'px', width: rect.size.width + 'px', height: rect.size.height + 'px', margin: 0});
1410
+
1411
+ // Disable pointer events on the new view so that hover states do not cause the layer to be redrawn mid-animation
1412
+ animationNode.style.pointerEvents = 'none';
1413
+
1414
+ // Compute the transform and apply it first to the window
1415
+ // and then to the animation node, but inversed
1416
+ const frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
1417
+ const transformRect = frame.rectWithTransformToRect(rect);
1418
+
1419
+ BMHook(animationNode, {translateX: -transformRect.origin.x + 'px', translateY: -transformRect.origin.y + 'px',
1420
+ scaleX: 1 / transformRect.size.width, scaleY: 1 / transformRect.size.height, opacity: 0});
1421
+
1422
+ document.body.appendChild(animationNode);
1423
+ node.style.display = 'none';
1424
+
1425
+ __BMVelocityAnimate(this._window, {opacity: 0, translateX: transformRect.origin.x + 'px', translateY: transformRect.origin.y + 'px',
1426
+ scaleX: transformRect.size.width, scaleY: transformRect.size.height}, {
1427
+ duration: _BMWindowAnimationDurationDefault,
1428
+ easing: _BMWindowAnimationEasingDefault,
1429
+ display: 'none',
1430
+ complete: completionHandler
1431
+ }, YES);
1432
+
1433
+ // After finishing the animation, remove the temporary node and restore the original node's display
1434
+ __BMVelocityAnimate(animationNode, {opacity: [1, 0], translateX: ['0px', -transformRect.origin.x + 'px'], translateY: ['0px', -transformRect.origin.y + 'px'],
1435
+ scaleX: [1, 1 / transformRect.size.width], scaleY: [1, 1 / transformRect.size.height]}, {
1436
+ duration: _BMWindowAnimationDurationDefault,
1437
+ easing: _BMWindowAnimationEasingDefault,
1438
+ complete: function () { node.style.display = nodeDisplay; animationNode.remove(); }
1439
+ }, YES);
1440
+ }
1441
+ else {
1442
+ __BMVelocityAnimate(this._window, {opacity: 0, scaleX: .9, scaleY: .9, translateZ: 0}, {
1443
+ duration: _BMWindowAnimationDurationOutgoingDefault,
1444
+ easing: _BMWindowAnimationEasingOutgoingDefault,
1445
+ display: 'none',
1446
+ complete: completionHandler
1447
+ }, YES);
1448
+ }
1449
+ },
1450
+
1451
+ /**
1452
+ * Should be invoked to dismiss this window. Subclasses overriding this method should invoke the base method at some point in their implementation.
1453
+ * @param animated <Boolean, nullable> Defaults to YES. If set to YES, this change will be animated, otherwise it will be instant.
1454
+ * {
1455
+ * @param toRect <BMRect, nullable> *Deprecated - Use the `anchorRect` property instead*. Ignored if either `anchorPoint`, `anchorRect` or `anchorNode` are set.
1456
+ * Sets the value of the `anchorRect` property to the given value until this operation completes.
1457
+ * @param toNode <DOMNode, nullable> *Deprecated - Use the `anchorRect` property instead*. Ignored if either `anchorPoint`, `anchorRect` or `anchorNode` are set.
1458
+ * Sets the value of the `anchorNode` property to the given value until this operation completes.
1459
+ * If set to a DOM node, the window will compact to that element.
1460
+ * @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been hidden.
1461
+ * }
1462
+ */
1463
+ dismissAnimated: function (animated, args = {}) {
1464
+
1465
+ animated = (animated === undefined ? YES : animated);
1466
+
1467
+ //__BMVelocityAnimate(this._blocker, 'stop');
1468
+ //__BMVelocityAnimate(this._window, 'stop');
1469
+
1470
+ var self = this;
1471
+
1472
+ this._blocker.style.pointerEvents = 'none';
1473
+ this._window.style.pointerEvents = 'none';
1474
+
1475
+ if (self.delegate && self.delegate.windowWillClose) {
1476
+ self.delegate.windowWillClose(self);
1477
+ }
1478
+ this._visible = NO;
1479
+
1480
+ for (let window of this._toolWindows) {
1481
+ window.dismissAnimated(animated);
1482
+ }
1483
+
1484
+ if (animated) {
1485
+
1486
+ __BMVelocityAnimate(this._blocker, {opacity: 0, translateZ: 0}, {
1487
+ duration: _BMWindowAnimationDurationDefault,
1488
+ easing: _BMWindowAnimationEasingDefault,
1489
+ display: 'none',
1490
+ /*progress: function (elements, complete) {
1491
+ self._blocker.style.webkitBackdropFilter = 'blur(' + ((1 - complete) * 15).toFixed(2) + 'px)';
1492
+ self._blocker.style.backdropFilter = 'blur(' + ((1 - complete) * 15).toFixed(2) + 'px)';
1493
+ },*/
1494
+ complete: function () {
1495
+ if (self.delegate && self.delegate.windowDidClose) {
1496
+ self.delegate.windowDidClose(self);
1497
+ }
1498
+ if (args && args.completionHandler) args.completionHandler();
1499
+ }
1500
+ }, YES);
1501
+
1502
+ // Included for compatibility with the anchor arguments,
1503
+ // due to be removed soon
1504
+ let completionHandler;
1505
+ if (this._anchorNode || this._anchorRect || this._anchorPoint) {
1506
+ completionHandler = () => {
1507
+ this._window.style.display = 'none';
1508
+ this._window.style.opacity = 0;
1509
+ };
1510
+ }
1511
+ else {
1512
+ if (args.fromRect) {
1513
+ this.anchorRect = args.fromRect;
1514
+ }
1515
+ if (args.fromNode) {
1516
+ this.anchorNode = args.fromNode;
1517
+ }
1518
+ completionHandler = () => {
1519
+ this.anchorRect = undefined;
1520
+ this.anchorNode = undefined;
1521
+ this._window.style.display = 'none';
1522
+ this._window.style.opacity = 0;
1523
+ }
1524
+ }
1525
+
1526
+ this.animateOutWithCompletionHandler(completionHandler);
1527
+
1528
+ }
1529
+ else {
1530
+ this._blocker.style.opacity = 0;
1531
+ this._blocker.style.display = 'none';
1532
+
1533
+ this._window.style.opacity = 0;
1534
+ this._window.style.display = 'none';
1535
+
1536
+ if (self.delegate && self.delegate.windowDidClose) {
1537
+ self.delegate.windowDidClose(self);
1538
+ }
1539
+ if (args && args.completionHandler) args.completionHandler();
1540
+ }
1541
+ },
1542
+
1543
+ /**
1544
+ * Minimizes this window. This method will raise an error if this window is modal.
1545
+ * If this window is already minimized, this method will have no effect.
1546
+ * @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated, otherwise it will be instant.
1547
+ * Setting this value to `NO` is currently unsupported.
1548
+ * {
1549
+ * @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been minimized.
1550
+ * }
1551
+ */
1552
+ minimizeAnimated: function (animated = YES, args = {}) {
1553
+ if (this._modal) throw new Error('A modal window cannot be minimized.');
1554
+ if (this._minimizedWindow) return;
1555
+
1556
+ if (this.delegate && this.delegate.windowWillMinimize) {
1557
+ this.delegate.windowWillMinimize(this);
1558
+ }
1559
+
1560
+ let minimizedWindow = document.createElement('div');
1561
+ minimizedWindow.className = 'BMWindowMinimized';
1562
+ minimizedWindow.innerText = this.title;
1563
+ minimizedWindow.style.left = 2 + BMWindow._minimizedWindows.length * 258 + 'px';
1564
+ minimizedWindow.addEventListener('click', event => {
1565
+ if (event.altKey) {
1566
+ BMWindow.restoreAllAnimated(YES);
1567
+ }
1568
+ else {
1569
+ this.restoreAnimated(YES);
1570
+ }
1571
+ });
1572
+
1573
+ BMWindow._minimizedWindows.push(minimizedWindow);
1574
+
1575
+ document.body.appendChild(minimizedWindow);
1576
+
1577
+ this._minimizedWindow = minimizedWindow;
1578
+
1579
+ let self = this;
1580
+
1581
+ if (animated) {
1582
+ // Create a copy of the given node which will be used in the animation in place of the original node
1583
+ var node = minimizedWindow;
1584
+
1585
+ var rect = BMRectMakeWithNodeFrame(node);
1586
+ var animationNode = node.cloneNode(YES);
1587
+
1588
+ animationNode.style.position = 'fixed';
1589
+ animationNode.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
1590
+ animationNode.style.transform = '';
1591
+ BMCopyProperties(animationNode.style, {left: rect.origin.x + 'px', top: rect.origin.y + 'px', width: rect.size.width + 'px', height: rect.size.height + 'px', margin: 0});
1592
+
1593
+ // Disable pointer events on the new view so that hover states do not cause the layer to be redrawn mid-animation
1594
+ animationNode.style.pointerEvents = 'none';
1595
+
1596
+ // Compute the transform and apply it first to the window
1597
+ // and then to the animation node, but inversed
1598
+ var frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
1599
+ var transformRect = frame.rectWithTransformToRect(rect);
1600
+
1601
+ BMHook(animationNode, {translateX: -transformRect.origin.x + 'px', translateY: -transformRect.origin.y + 'px',
1602
+ scaleX: 1 / transformRect.size.width, scaleY: 1 / transformRect.size.height, opacity: 0});
1603
+
1604
+ document.body.appendChild(animationNode);
1605
+ node.style.display = 'none';
1606
+
1607
+ __BMVelocityAnimate(this._window, {opacity: 0, translateX: transformRect.origin.x + 'px', translateY: transformRect.origin.y + 'px',
1608
+ scaleX: transformRect.size.width, scaleY: transformRect.size.height}, {
1609
+ duration: _BMWindowAnimationDurationDefault,
1610
+ easing: _BMWindowAnimationEasingDefault,
1611
+ display: 'none'
1612
+ }, YES);
1613
+
1614
+ // After finishing the animation, remove the temporary node and restore the original node's display
1615
+ __BMVelocityAnimate(animationNode, {opacity: [1, 0], translateX: ['0px', -transformRect.origin.x + 'px'], translateY: ['0px', -transformRect.origin.y + 'px'],
1616
+ scaleX: [1, 1 / transformRect.size.width], scaleY: [1, 1 / transformRect.size.height]}, {
1617
+ duration: _BMWindowAnimationDurationDefault,
1618
+ easing: _BMWindowAnimationEasingDefault,
1619
+ complete: function () {
1620
+ node.style.display = 'block';
1621
+ animationNode.remove();
1622
+
1623
+ if (self.delegate && self.delegate.windowDidMinimize) {
1624
+ self.delegate.windowDidMinimize(self);
1625
+ }
1626
+
1627
+ if (args && args.completionHandler) args.completionHandler();
1628
+ }
1629
+ }, YES);
1630
+ }
1631
+ },
1632
+
1633
+ /**
1634
+ * Restores this window. This method will raise an error if this window is modal.
1635
+ * If this window is not minimized, this method will have no effect.
1636
+ * @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated, otherwise it will be instant.
1637
+ * Setting this value to `NO` is currently unsupported.
1638
+ * {
1639
+ * @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been minimized.
1640
+ * }
1641
+ */
1642
+ restoreAnimated(animated = YES, args = {}) {
1643
+ if (this._modal) throw new Error('A modal window cannot be restored.');
1644
+ if (!this._minimizedWindow) return;
1645
+
1646
+ if (this.delegate && this.delegate.windowWillRestore) {
1647
+ this.delegate.windowWillRestore(this);
1648
+ }
1649
+
1650
+ let self = this;
1651
+
1652
+ if (animated) {
1653
+ // Create a copy of the given node which will be used in the animation in place of the original node
1654
+ var node = this._minimizedWindow;
1655
+ var rect = BMRectMakeWithNodeFrame(node);
1656
+ var animationNode = node.cloneNode(YES);
1657
+
1658
+ this._minimizedWindow = undefined;
1659
+ let removedWindow = NO;
1660
+ for (let i = 0, length = BMWindow._minimizedWindows.length, windows = BMWindow._minimizedWindows; i < length; i++) {
1661
+ if (windows[i] == node) {
1662
+ windows.splice(i, 1);
1663
+ i--;
1664
+ length--;
1665
+ removedWindow = YES;
1666
+ continue;
1667
+ }
1668
+
1669
+ if (removedWindow) {
1670
+ windows[i].style.left = 2 + 258 * i + 'px';
1671
+ }
1672
+ }
1673
+
1674
+ if (!this.isKeyWindow) this.becomeKeyWindow();
1675
+
1676
+ animationNode.style.position = 'fixed';
1677
+ animationNode.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
1678
+ animationNode.style.transform = '';
1679
+ BMCopyProperties(animationNode.style, {left: rect.origin.x + 'px', top: rect.origin.y + 'px', width: rect.size.width + 'px', height: rect.size.height + 'px', margin: 0});
1680
+
1681
+ // Disable pointer events on the new view so that hover states do not cause the layer to be redrawn mid-animation
1682
+ animationNode.style.pointerEvents = 'none';
1683
+
1684
+ // Compute the transform and apply it first to the window
1685
+ // and then to the animation node, but inversed
1686
+ var frame = self._fullScreen ? BMRectMake(0, 0, window.innerWidth, window.innerHeight) : self.frame;
1687
+ var transformRect = frame.rectWithTransformToRect(rect);
1688
+
1689
+ document.body.appendChild(animationNode);
1690
+ node.style.display = 'none';
1691
+
1692
+ __BMVelocityAnimate(this._window, {opacity: 1, translateX: ['0px', transformRect.origin.x + 'px'], translateY: ['0px', transformRect.origin.y + 'px'],
1693
+ scaleX: [1, transformRect.size.width], scaleY: [1, transformRect.size.height]}, {
1694
+ duration: _BMWindowAnimationDurationDefault,
1695
+ easing: _BMWindowAnimationEasingDefault,
1696
+ display: 'block'
1697
+ }, YES);
1698
+
1699
+ // After finishing the animation, remove the temporary node and restore the original node's display
1700
+ __BMVelocityAnimate(animationNode, {opacity: [0, 1], translateX: -transformRect.origin.x + 'px', translateY: -transformRect.origin.y + 'px',
1701
+ scaleX: 1 / transformRect.size.width, scaleY: 1 / transformRect.size.height}, {
1702
+ duration: _BMWindowAnimationDurationDefault,
1703
+ easing: _BMWindowAnimationEasingDefault,
1704
+ complete: function () {
1705
+ animationNode.remove();
1706
+ node.remove();
1707
+
1708
+ if (self.delegate && self.delegate.windowDidRestore) {
1709
+ self.delegate.windowDidRestore(self);
1710
+ }
1711
+
1712
+ if (args && args.completionHandler) args.completionHandler();
1713
+ }
1714
+ }, YES);
1715
+ }
1716
+ },
1717
+
1718
+ /**
1719
+ * Should be invoked to make this window fullscreen.
1720
+ * @param animated <Boolean, nullable> Defaults to YES. If set to YES, this change will be animated, otherwise it will be instant.
1721
+ * {
1722
+ * @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been hidden.
1723
+ * }
1724
+ */
1725
+ enterFullScreenAnimated: function (animated, args) {
1726
+ this._fullScreen = YES;
1727
+
1728
+ if (!this.isKeyWindow) this.becomeKeyWindow();
1729
+
1730
+ animated = (animated === undefined ? YES : animated);
1731
+
1732
+ //__BMVelocityAnimate(this._blocker, 'stop');
1733
+ //__BMVelocityAnimate(this._window, 'stop');
1734
+
1735
+ var self = this;
1736
+
1737
+ this.widthConstraint.constant = window.innerWidth;
1738
+ this.heightConstraint.constant = window.innerHeight;
1739
+ this.leftConstraint.constant = 0;
1740
+ this.topConstraint.constant = 0;
1741
+
1742
+ if (animated) {
1743
+ BMAnimateWithBlock(() => {
1744
+ let controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this.node});
1745
+ controller.registerBuiltInPropertiesWithDictionary({left: '0px', top: '0px', width: window.innerWidth + 'px', height: window.innerHeight + 'px'});
1746
+
1747
+ const frame = this.frame.copy();
1748
+ this.layout();
1749
+
1750
+ // When the window contains elements whose intrinsic size must be measured, upon finishing the layout, the window will continue to keep its
1751
+ // temporary frame used for measuring, so it has to be reset prior to the animation starting
1752
+ BMCopyProperties(this.node.style, {left: frame.origin.x + 'px', top: frame.origin.y + 'px', width: frame.size.width + 'px', height: frame.size.height + 'px'})
1753
+ }, {duration: 300, easing: 'easeInOutQuart',
1754
+ complete: function () {
1755
+ if (self.delegate && self.delegate.windowDidEnterFullScreen) self.delegate.windowDidEnterFullScreen(self);
1756
+ if (args && args.completionHandler) args.completionHandler();
1757
+
1758
+ self._window.classList.add('BMFullScreenWindow');
1759
+ }
1760
+ });
1761
+ }
1762
+ else {
1763
+ self._window.classList.add('BMFullScreenWindow');
1764
+ if (self.delegate && self.delegate.windowDidEnterFullScreen) self.delegate.windowDidEnterFullScreen(self);
1765
+ if (args && args.completionHandler) args.completionHandler();
1766
+ }
1767
+
1768
+ this._resizeHandler = event => {
1769
+ this.widthConstraint.constant = window.innerWidth;
1770
+ this.heightConstraint.constant = window.innerHeight;
1771
+ }
1772
+
1773
+ window.addEventListener('resize', this._resizeHandler);
1774
+
1775
+ },
1776
+
1777
+
1778
+ /**
1779
+ * Should be invoked to exit full screen mode.
1780
+ * @param animated <Boolean, nullable> Defaults to YES. If set to YES, this change will be animated, otherwise it will be instant.
1781
+ * {
1782
+ * @param completionHandler <void ^ (), nullable> A handler that will be invoked after this window has been hidden.
1783
+ * }
1784
+ */
1785
+ exitFullScreenAnimated: function (animated, args) {
1786
+ if (!this._fullScreen) return;
1787
+
1788
+ animated = (animated === undefined ? YES : animated);
1789
+
1790
+ //__BMVelocityAnimate(this._blocker, 'stop');
1791
+ //__BMVelocityAnimate(this._window, 'stop');
1792
+
1793
+ var self = this;
1794
+
1795
+ this.widthConstraint.constant = this.frame.width;
1796
+ this.heightConstraint.constant = this.frame.height;
1797
+ this.leftConstraint.constant = this.frame.left;
1798
+ this.topConstraint.constant = this.frame.top;
1799
+
1800
+ if (animated) {
1801
+ self._window.classList.remove('BMFullScreenWindow');
1802
+ var frame = self.frame;
1803
+ BMCopyProperties(self._window.style, {left: '0px', top: '0px', width: window.innerWidth + 'px', height: window.innerHeight + 'px'});
1804
+ /*__BMVelocityAnimate(this._window, {left: frame.origin.x + 'px', top: frame.origin.y + 'px', width: frame.size.width + 'px', height: frame.size.height + 'px'}, {
1805
+ duration: 300,
1806
+ easing: 'easeInOutQuart',
1807
+ complete: function () {
1808
+ if (self.delegate && self.delegate.windowDidExitFullScreen) self.delegate.windowDidExitFullScreen(self);
1809
+ if (args && args.completionHandler) args.completionHandler();
1810
+
1811
+ self.frame = self.frame;
1812
+ }
1813
+ });*/
1814
+
1815
+ BMAnimateWithBlock(() => {
1816
+ let controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this.node});
1817
+ controller.registerBuiltInProperty('left', {withValue: frame.origin.x + 'px'});
1818
+ controller.registerBuiltInProperty('top', {withValue: frame.origin.y + 'px'});
1819
+ controller.registerBuiltInProperty('width', {withValue: frame.size.width + 'px'});
1820
+ controller.registerBuiltInProperty('height', {withValue: frame.size.height + 'px'});
1821
+ this._fullScreen = NO;
1822
+ this.layout();
1823
+ BMCopyProperties(self._window.style, {left: '0px', top: '0px', width: window.innerWidth + 'px', height: window.innerHeight + 'px'});
1824
+ }, {
1825
+ duration: 300, easing: 'easeInOutQuart',
1826
+ complete: function () {
1827
+ if (self.delegate && self.delegate.windowDidExitFullScreen) self.delegate.windowDidExitFullScreen(self);
1828
+ if (args && args.completionHandler) args.completionHandler();
1829
+
1830
+ self.frame = self.frame;
1831
+ }
1832
+ });
1833
+ }
1834
+ else {
1835
+ self._window.classList.remove('BMFullScreenWindow');
1836
+ this._fullScreen = NO;
1837
+ self.frame = self.frame;
1838
+
1839
+ if (self.delegate && self.delegate.windowDidExitFullScreen) self.delegate.windowDidExitFullScreen(self);
1840
+ if (args && args.completionHandler) args.completionHandler();
1841
+ }
1842
+
1843
+ window.removeEventListener('resize', this._resizeHandler);
1844
+ },
1845
+
1846
+ /**
1847
+ * Returns `YES` if this window is the key window, `NO` otherwise.
1848
+ */
1849
+ get isKeyWindow() { // <Boolean>
1850
+ return BMWindow._keyWindow == this;
1851
+ },
1852
+
1853
+ /**
1854
+ * Causes this window to stop being the key window.
1855
+ * If this window is not the key window, this method has no effect.
1856
+ */
1857
+ resignKeyWindow() {
1858
+ if (BMWindow._keyWindow == this && !this._modal) {
1859
+ this.node.classList.remove('BMKeyWindow');
1860
+ this.node.classList.add('BMWindowInactive');
1861
+ if (this.delegate && this.delegate.windowDidResignKeyWindow) {
1862
+ this.delegate.windowDidResignKeyWindow(this);
1863
+ }
1864
+ BMWindow._keyWindow = undefined;
1865
+
1866
+ for (let toolWindow of this._toolWindows) {
1867
+ toolWindow.hide();
1868
+ }
1869
+ }
1870
+ },
1871
+
1872
+ /**
1873
+ * Causes this window to become the key window.
1874
+ * If this window is minimized when this method is invoked, it will be restored.
1875
+ */
1876
+ becomeKeyWindow() {
1877
+ let minimized = !!this._minimizedWindow;
1878
+ if (this._minimizedWindow) {
1879
+ this.restoreAnimated(YES);
1880
+ }
1881
+
1882
+ if (this._hidden) {
1883
+ this.reveal();
1884
+ }
1885
+
1886
+ if (BMWindow._keyWindow != this) {
1887
+ if (BMWindow._keyWindow) {
1888
+ BMWindow._keyWindow.resignKeyWindow();
1889
+ }
1890
+
1891
+ this.node.classList.add('BMKeyWindow');
1892
+ this.node.classList.remove('BMWindowInactive');
1893
+ BMWindow._keyWindow = this;
1894
+
1895
+ if (!this._modal) {
1896
+ BMWindow._windows.splice(BMWindow._windows.indexOf(this), 1);
1897
+ BMWindow._windows.push(this);
1898
+ BMWindow._windows.slice().reverse().forEach((window, index) => window.node.style.zIndex = BM_WINDOW_Z_INDEX_MAX - index);
1899
+ }
1900
+ else {
1901
+ this._overlay.node.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
1902
+ this.node.style.zIndex = BM_WINDOW_Z_INDEX_MAX + 1;
1903
+ }
1904
+
1905
+ if (this.delegate && this.delegate.windowDidBecomeKeyWindow) {
1906
+ this.delegate.windowDidBecomeKeyWindow(this);
1907
+ }
1908
+
1909
+ for (let toolWindow of this._toolWindows) {
1910
+ toolWindow.reveal();
1911
+ }
1912
+ }
1913
+ else if (!minimized && !BMWindow._isShowcasing) {
1914
+ BMAnimateWithBlock(() => {
1915
+ BMAnimationContextEnableWebAnimations();
1916
+ let controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this.node});
1917
+ controller.registerBuiltInProperty('scaleX', {withValue: 1.05});
1918
+ controller.registerBuiltInProperty('scaleY', {withValue: 1.05});
1919
+ }, {duration: 100, easing: 'easeInQuad', complete: () => {
1920
+ BMAnimateWithBlock(() => {
1921
+ BMAnimationContextEnableWebAnimations();
1922
+ let controller = BMAnimationContextGetCurrent().controllerForObject(this, {node: this.node});
1923
+ controller.registerBuiltInProperty('scaleX', {withValue: 1});
1924
+ controller.registerBuiltInProperty('scaleY', {withValue: 1});
1925
+ }, {duration: 100, easing: 'easeOutQuad'});
1926
+ }});
1927
+ }
1928
+ },
1929
+
1930
+ /**
1931
+ * Destroys this window and removes all DOM nodes associated with it.
1932
+ * This instance should not be reused after invoking this method.
1933
+ */
1934
+ release() {
1935
+ if (BMWindow._keyWindow == this) BMWindow._keyWindow = undefined;
1936
+
1937
+ if (this._boundViewportDidResize) {
1938
+ window.removeEventListener('resize', this._boundViewportDidResize);
1939
+ }
1940
+
1941
+ if (!this._modal) {
1942
+ // Remove this window from the global list of windows
1943
+ for (let i = 0, length = BMWindow._windows.length; i < length; i++) {
1944
+ if (BMWindow._windows[i] == this) {
1945
+ BMWindow._windows.splice(i, 1);
1946
+ break;
1947
+ }
1948
+ }
1949
+
1950
+ if (this._minimizedWindow) {
1951
+ for (let i = 0, length = BMWindow._minimizedWindows.length; i < length; i++) {
1952
+ if (BMWindow._minimizedWindows[i] == this._minimizedWindow) {
1953
+ BMWindow._minimizedWindows.splice(i, 1);
1954
+ break;
1955
+ }
1956
+ }
1957
+ this._minimizedWindow.remove();
1958
+ }
1959
+ }
1960
+
1961
+ BMView.prototype.release.call(this);
1962
+ this._cancelLayout();
1963
+ this._overlay.release();
1964
+ this._overlay.node.remove();
1965
+ this.node.remove();
1966
+ },
1967
+
1968
+ /**
1969
+ * Hides this window.
1970
+ */
1971
+ hide() {
1972
+ this._hidden = YES;
1973
+ this.node.classList.add('BMWindowHidden');
1974
+ },
1975
+
1976
+ /**
1977
+ * Reveals this window.
1978
+ */
1979
+ reveal() {
1980
+ this._hidden = NO;
1981
+ this.node.classList.remove('BMWindowHidden');
1982
+ }
1983
+
1984
+ });
1985
+
1986
+ /**
1987
+ * Constructs and returns a window that will display in the given rect.
1988
+ * @param frame <BMRect> The frame.
1989
+ * {
1990
+ * @param toolbar <Boolean, nullable> Defaults to `YES`. If set to `YES`, the window will have a toolbar.
1991
+ * @param modal <Boolean, nullable> Defaults to `YES`. If set to `YES`, the window will be modal, blocking content behind it.
1992
+ * When set to `NO`, it will be possible to interact with content that is not obstructed by this window.
1993
+ * }
1994
+ * @return <BMWindow> A window.
1995
+ */
1996
+ export function BMWindowMakeWithFrame(frame, args) {
1997
+ return (new BMWindow()).initWithFrame(frame, args);
1998
+ }
1999
+
2000
+ // @endtype