bm-core-ui 2.7.0 → 2.8.3

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.
@@ -171,172 +171,7 @@ BMLayoutEditor.prototype = BMExtend(Object.create(BMWindow.prototype), {
171
171
  window.addEventListener('resize', this.resizeListener);
172
172
 
173
173
  view.allSubviews.forEach(subview => {
174
- // Create and add the selector, that takes over interactions for the view
175
- let selector = document.createElement('div');
176
- selector.className = 'BMLayoutEditorViewSelector';
177
-
178
- subview._selector = selector;
179
- subview.node.classList.add('BMLayoutEditorManagedView');
180
-
181
- // Also add a resize drag handle that can be used to resize the view's frame
182
- const dragHandle = document.createElement('div');
183
- dragHandle.className = 'material-icons BMWindowDragHandle BMLayoutEditorViewDragHandle';
184
- dragHandle.innerText = 'dehaze';
185
- dragHandle.style.cursor = 'nwse-resize';
186
-
187
- selector.appendChild(dragHandle);
188
-
189
-
190
- let touchDragPoint;
191
-
192
- // Initialize dragging touch events for the drag handler
193
- dragHandle.addEventListener('mousedown', event => {
194
-
195
- let size = BMSizeMake(subview.frame.size.width, subview.frame.size.height);
196
- let lastPosition = BMPointMake(event.clientX, event.clientY);
197
-
198
- let didRemoveConstraints = NO;
199
-
200
- let mouseMoveEventListener = event => {
201
- if (!didRemoveConstraints) {
202
- this._view.node.querySelectorAll('.BMLayoutEditorConstraint, .BMLayoutEditorLeadLine').forEach(node => node.remove());
203
- didRemoveConstraints = YES;
204
- }
205
-
206
- let position = BMPointMake(event.clientX, event.clientY);
207
- const frame = subview.frame.copy();
208
- frame.size.width = size.width + (position.x - lastPosition.x) / this.scale;
209
- frame.size.height = size.height + (position.y - lastPosition.y) / this.scale;
210
- subview.frame = frame;
211
- size = BMSizeMake(size.width + (position.x - lastPosition.x) / this.scale, size.height + (position.y - lastPosition.y) / this.scale);
212
- lastPosition = position;
213
-
214
- event.preventDefault();
215
- event.stopPropagation();
216
- };
217
-
218
- let mask = document.createElement('div');
219
- mask.className = 'BMLayoutGuideMask';
220
- mask.style.cursor = 'nwse-resize';
221
- document.body.appendChild(mask);
222
-
223
- let mouseUpEventListener = event => {
224
- window.removeEventListener('mousemove', mouseMoveEventListener, YES);
225
- window.removeEventListener('mouseup', mouseUpEventListener, YES);
226
- mask.remove();
227
- event.preventDefault();
228
- event.stopPropagation();
229
-
230
- this.selectView(subview);
231
- }
232
-
233
- window.addEventListener('mousemove', mouseMoveEventListener, YES);
234
- window.addEventListener('mouseup', mouseUpEventListener, YES);
235
-
236
- event.preventDefault();
237
- event.stopPropagation();
238
- });
239
-
240
- let touchDragHandlePoint;
241
-
242
- dragHandle.addEventListener('touchstart', /** @type {TouchEvent} */ event => {
243
- // If there is already a drag in progress, don't process this new event
244
- if (typeof touchDragPoint !== 'undefined') {
245
- return;
246
- }
247
-
248
- // Only use the first touch point
249
- touchDragHandlePoint = event.changedTouches[0].identifier;
250
-
251
- let size = BMSizeMake(subview.frame.size.width, subview.frame.size.height);
252
- let lastPosition = BMPointMake(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
253
-
254
- let didRemoveConstraints = NO;
255
-
256
- let mouseMoveEventListener = event => {
257
- // Look for the actively tracked touch point
258
- let touch;
259
- for (let changedTouch of event.changedTouches) {
260
- if (changedTouch.identifier == touchDragHandlePoint) {
261
- touch = changedTouch;
262
- break;
263
- }
264
- }
265
-
266
- // If the actively tracked touch point did not move, do not process this event
267
- if (!touch) return;
268
-
269
- if (!didRemoveConstraints) {
270
- this._view.node.querySelectorAll('.BMLayoutEditorConstraint, .BMLayoutEditorLeadLine').forEach(node => node.remove());
271
- didRemoveConstraints = YES;
272
- }
273
-
274
- let position = BMPointMake(touch.clientX, touch.clientY);
275
- const frame = subview.frame.copy();
276
- frame.size.width = size.width + (position.x - lastPosition.x) / this.scale;
277
- frame.size.height = size.height + (position.y - lastPosition.y) / this.scale;
278
- subview.frame = frame;
279
- size = BMSizeMake(size.width + (position.x - lastPosition.x) / this.scale, size.height + (position.y - lastPosition.y) / this.scale);
280
- lastPosition = position;
281
- event.preventDefault();
282
- event.stopPropagation();
283
- };
284
-
285
- let mouseUpEventListener = event => {
286
- touchDragHandlePoint = undefined;
287
- window.removeEventListener('touchmove', mouseMoveEventListener);
288
- window.removeEventListener('touchend', mouseUpEventListener);
289
- window.removeEventListener('touchcancel', mouseUpEventListener);
290
-
291
- event.preventDefault();
292
- event.stopPropagation();
293
-
294
- this.selectView(subview);
295
- }
296
-
297
- window.addEventListener('touchmove', mouseMoveEventListener);
298
- window.addEventListener('touchend', mouseUpEventListener);
299
- window.removeEventListener('touchcancel', mouseUpEventListener);
300
-
301
- event.preventDefault();
302
- event.stopPropagation();
303
- });
304
-
305
- // Enable double click to select views
306
- let doubleClickTimeout;
307
-
308
- selector.addEventListener('click', event => {
309
- if (event.altKey) return;
310
-
311
- this.selectView(subview, {withEvent: event});
312
- if (!doubleClickTimeout) {
313
- doubleClickTimeout = setTimeout(() => doubleClickTimeout = undefined, 250);
314
- }
315
- else {
316
- doubleClickTimeout = undefined;
317
- if (!this._detailsToolWindow._visible) {
318
- this._detailsToolWindow.bringToFrontAnimated(YES, {fromRect: BMRectMakeWithNodeFrame(this._inspectorButton)});
319
- }
320
- }
321
-
322
- event.stopPropagation();
323
- event.stopImmediatePropagation();
324
- });
325
-
326
- this.initDragEventListenerForNode(selector, {view: subview});
327
-
328
- // Disable all other non-right mouse button interactions
329
- selector.addEventListener('mousedown', event => (event.button != 2) && (event.stopPropagation(), event.stopImmediatePropagation()));
330
- selector.addEventListener('mousemove', event => (event.button != 2) && (event.stopPropagation(), event.stopImmediatePropagation()));
331
- selector.addEventListener('mouseup', event => (event.button != 2) && (event.stopPropagation(), event.stopImmediatePropagation()));
332
-
333
-
334
- if (subview.node.childNodes.length) {
335
- subview.node.insertBefore(selector, subview.node.childNodes[0]);
336
- }
337
- else {
338
- subview.node.appendChild(selector);
339
- }
174
+ this._prepareView(subview);
340
175
  });
341
176
 
342
177
  var width = document.documentElement.clientWidth;
@@ -590,6 +425,180 @@ BMLayoutEditor.prototype = BMExtend(Object.create(BMWindow.prototype), {
590
425
  return this;
591
426
  },
592
427
 
428
+ /**
429
+ * Prepares the given view for being edited by setting up the appropriate event handlers and
430
+ * adding the required controls to the view's node.
431
+ * @param view <BMView> The view to prepare.
432
+ */
433
+ _prepareView(view) {
434
+ // Create and add the selector, that takes over interactions for the view
435
+ let selector = document.createElement('div');
436
+ selector.className = 'BMLayoutEditorViewSelector';
437
+
438
+ view._selector = selector;
439
+ view.node.classList.add('BMLayoutEditorManagedView');
440
+
441
+ // Also add a resize drag handle that can be used to resize the view's frame
442
+ const dragHandle = document.createElement('div');
443
+ dragHandle.className = 'material-icons BMWindowDragHandle BMLayoutEditorViewDragHandle';
444
+ dragHandle.innerText = 'dehaze';
445
+ dragHandle.style.cursor = 'nwse-resize';
446
+
447
+ selector.appendChild(dragHandle);
448
+
449
+
450
+ let touchDragPoint;
451
+
452
+ // Initialize dragging touch events for the drag handler
453
+ dragHandle.addEventListener('mousedown', event => {
454
+
455
+ let size = BMSizeMake(view.frame.size.width, view.frame.size.height);
456
+ let lastPosition = BMPointMake(event.clientX, event.clientY);
457
+
458
+ let didRemoveConstraints = NO;
459
+
460
+ let mouseMoveEventListener = event => {
461
+ if (!didRemoveConstraints) {
462
+ this._view.node.querySelectorAll('.BMLayoutEditorConstraint, .BMLayoutEditorLeadLine').forEach(node => node.remove());
463
+ didRemoveConstraints = YES;
464
+ }
465
+
466
+ let position = BMPointMake(event.clientX, event.clientY);
467
+ const frame = view.frame.copy();
468
+ frame.size.width = size.width + (position.x - lastPosition.x) / this.scale;
469
+ frame.size.height = size.height + (position.y - lastPosition.y) / this.scale;
470
+ view.frame = frame;
471
+ size = BMSizeMake(size.width + (position.x - lastPosition.x) / this.scale, size.height + (position.y - lastPosition.y) / this.scale);
472
+ lastPosition = position;
473
+
474
+ event.preventDefault();
475
+ event.stopPropagation();
476
+ };
477
+
478
+ let mask = document.createElement('div');
479
+ mask.className = 'BMLayoutGuideMask';
480
+ mask.style.cursor = 'nwse-resize';
481
+ document.body.appendChild(mask);
482
+
483
+ let mouseUpEventListener = event => {
484
+ window.removeEventListener('mousemove', mouseMoveEventListener, YES);
485
+ window.removeEventListener('mouseup', mouseUpEventListener, YES);
486
+ mask.remove();
487
+ event.preventDefault();
488
+ event.stopPropagation();
489
+
490
+ this.selectView(view);
491
+ }
492
+
493
+ window.addEventListener('mousemove', mouseMoveEventListener, YES);
494
+ window.addEventListener('mouseup', mouseUpEventListener, YES);
495
+
496
+ event.preventDefault();
497
+ event.stopPropagation();
498
+ });
499
+
500
+ let touchDragHandlePoint;
501
+
502
+ dragHandle.addEventListener('touchstart', /** @type {TouchEvent} */ event => {
503
+ // If there is already a drag in progress, don't process this new event
504
+ if (typeof touchDragPoint !== 'undefined') {
505
+ return;
506
+ }
507
+
508
+ // Only use the first touch point
509
+ touchDragHandlePoint = event.changedTouches[0].identifier;
510
+
511
+ let size = BMSizeMake(view.frame.size.width, view.frame.size.height);
512
+ let lastPosition = BMPointMake(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
513
+
514
+ let didRemoveConstraints = NO;
515
+
516
+ let mouseMoveEventListener = event => {
517
+ // Look for the actively tracked touch point
518
+ let touch;
519
+ for (let changedTouch of event.changedTouches) {
520
+ if (changedTouch.identifier == touchDragHandlePoint) {
521
+ touch = changedTouch;
522
+ break;
523
+ }
524
+ }
525
+
526
+ // If the actively tracked touch point did not move, do not process this event
527
+ if (!touch) return;
528
+
529
+ if (!didRemoveConstraints) {
530
+ this._view.node.querySelectorAll('.BMLayoutEditorConstraint, .BMLayoutEditorLeadLine').forEach(node => node.remove());
531
+ didRemoveConstraints = YES;
532
+ }
533
+
534
+ let position = BMPointMake(touch.clientX, touch.clientY);
535
+ const frame = view.frame.copy();
536
+ frame.size.width = size.width + (position.x - lastPosition.x) / this.scale;
537
+ frame.size.height = size.height + (position.y - lastPosition.y) / this.scale;
538
+ view.frame = frame;
539
+ size = BMSizeMake(size.width + (position.x - lastPosition.x) / this.scale, size.height + (position.y - lastPosition.y) / this.scale);
540
+ lastPosition = position;
541
+ event.preventDefault();
542
+ event.stopPropagation();
543
+ };
544
+
545
+ let mouseUpEventListener = event => {
546
+ touchDragHandlePoint = undefined;
547
+ window.removeEventListener('touchmove', mouseMoveEventListener);
548
+ window.removeEventListener('touchend', mouseUpEventListener);
549
+ window.removeEventListener('touchcancel', mouseUpEventListener);
550
+
551
+ event.preventDefault();
552
+ event.stopPropagation();
553
+
554
+ this.selectView(view);
555
+ }
556
+
557
+ window.addEventListener('touchmove', mouseMoveEventListener);
558
+ window.addEventListener('touchend', mouseUpEventListener);
559
+ window.removeEventListener('touchcancel', mouseUpEventListener);
560
+
561
+ event.preventDefault();
562
+ event.stopPropagation();
563
+ });
564
+
565
+ // Enable double click to select views
566
+ let doubleClickTimeout;
567
+
568
+ selector.addEventListener('click', event => {
569
+ if (event.altKey) return;
570
+
571
+ this.selectView(view, {withEvent: event});
572
+ if (!doubleClickTimeout) {
573
+ doubleClickTimeout = setTimeout(() => doubleClickTimeout = undefined, 250);
574
+ }
575
+ else {
576
+ doubleClickTimeout = undefined;
577
+ if (!this._detailsToolWindow._visible) {
578
+ this._detailsToolWindow.bringToFrontAnimated(YES, {fromRect: BMRectMakeWithNodeFrame(this._inspectorButton)});
579
+ }
580
+ }
581
+
582
+ event.stopPropagation();
583
+ event.stopImmediatePropagation();
584
+ });
585
+
586
+ this.initDragEventListenerForNode(selector, {view: view});
587
+
588
+ // Disable all other non-right mouse button interactions
589
+ selector.addEventListener('mousedown', event => (event.button != 2) && (event.stopPropagation(), event.stopImmediatePropagation()));
590
+ selector.addEventListener('mousemove', event => (event.button != 2) && (event.stopPropagation(), event.stopImmediatePropagation()));
591
+ selector.addEventListener('mouseup', event => (event.button != 2) && (event.stopPropagation(), event.stopImmediatePropagation()));
592
+
593
+
594
+ if (view.node.childNodes.length) {
595
+ view.node.insertBefore(selector, view.node.childNodes[0]);
596
+ }
597
+ else {
598
+ view.node.appendChild(selector);
599
+ }
600
+ },
601
+
593
602
  /**
594
603
  * Is set to <code>YES</code> while this layout editor is full screen.
595
604
  */
@@ -859,7 +868,7 @@ BMLayoutEditor.prototype = BMExtend(Object.create(BMWindow.prototype), {
859
868
  const closeButton = document.createElement('div');
860
869
  closeButton.className = 'BMWindowToolbarButton BMLayoutEditorToolbarButton';
861
870
  closeButton.innerHTML = '<i class="material-icons">&#xE5CD;</i>';
862
- closeButton.style.opacity = 1;
871
+ closeButton.style.opacity = '1';
863
872
 
864
873
  closeButton.addEventListener('click', event => this.dismissAnimated(YES, {toNode: this._returnNode}));
865
874
 
@@ -872,7 +881,7 @@ BMLayoutEditor.prototype = BMExtend(Object.create(BMWindow.prototype), {
872
881
  fullscreenButton.innerHTML = `<i class="material-icons" style="pointer-events: none;">${this._useSettingsView ? 'layers' : '&#xE5DC;'}</i>`;
873
882
  this._fullScreenButton = fullscreenButton;
874
883
 
875
- fullscreenButton.style.opacity = 1;
884
+ fullscreenButton.style.opacity = '1';
876
885
  fullscreenButton.style.pointerEvents = 'all';
877
886
 
878
887
  fullscreenButton.addEventListener('click', this._useSettingsView ? event => this._treeWindow.toggleAnimated(YES) : event => this.toggleFullscreenWithEvent(event));
@@ -900,6 +909,9 @@ BMLayoutEditor.prototype = BMExtend(Object.create(BMWindow.prototype), {
900
909
  toolbar.appendChild(deviceSelector);
901
910
 
902
911
 
912
+ /**
913
+ * @type {HTMLInputElement}
914
+ */
903
915
  let widthInput = document.createElement('input');
904
916
  widthInput.type = 'number';
905
917
  widthInput.style.cssText = 'width: 64px !important; margin-left: 32px !important; text-align: center;';
@@ -1119,6 +1131,14 @@ BMLayoutEditor.prototype = BMExtend(Object.create(BMWindow.prototype), {
1119
1131
  this._showSizeClassMenuAtPoint(point);
1120
1132
  });
1121
1133
 
1134
+ const addViewsButton = document.createElement('div');
1135
+ addViewsButton.className = 'BMWindowToolbarButton BMLayoutEditorToolbarButton';
1136
+ addViewsButton.innerHTML = '<i class="material-icons BMScriptFont" style="pointer-events: none;">+</i>';
1137
+ toolbar.appendChild(addViewsButton);
1138
+ this.addViewsButton = addViewsButton;
1139
+
1140
+ addViewsButton.addEventListener('click', event => this.showViewsPopupWithEvent(event));
1141
+
1122
1142
  const layoutVariablesButton = document.createElement('div');
1123
1143
  layoutVariablesButton.className = 'BMWindowToolbarButton BMLayoutEditorToolbarButton';
1124
1144
  layoutVariablesButton.innerHTML = '<i class="material-icons BMScriptFont" style="pointer-events: none;">{X}</i>';
@@ -518,10 +518,10 @@ BMLayoutEditorSettingsDeactivateConstraintsCell.prototype = BMExtend(Object.crea
518
518
 
519
519
  buttonView.node.addEventListener('click', event => {
520
520
  if (hasOption && hasShift && hasCtrl) {
521
- new Set(this.setting.target.rootView.allConstraints).forEach(constraint => constraint.remove());
521
+ new Set(this.setting.target._view.rootView.allConstraints).forEach(constraint => constraint.remove());
522
522
  }
523
523
  else if (hasOption) {
524
- for (let constraint of this.setting.target.localConstraints) {
524
+ for (let constraint of this.setting.target._view.localConstraints) {
525
525
  if (hasShift) {
526
526
  constraint.remove();
527
527
  }
@@ -179,6 +179,11 @@ BMAlertPopup.prototype = BMExtend(Object.create(BMWindow.prototype), {
179
179
  return BMLayoutAttribute.CenterY;
180
180
  },
181
181
 
182
+ /**
183
+ * The DOM node that was focused when this alert was opened.
184
+ */
185
+ _previouslyActiveNode: undefined, // DOMNode
186
+
182
187
  /**
183
188
  * Designated initializer. Initializes this alert popup with the given labels.
184
189
  * @param title <String> The popup's title.
@@ -249,6 +254,8 @@ BMAlertPopup.prototype = BMExtend(Object.create(BMWindow.prototype), {
249
254
  escapeKeyboardShortcut.preventsDefault = YES;
250
255
  this.registerKeyboardShortcut(escapeKeyboardShortcut);
251
256
 
257
+ this._previouslyActiveNode = document.activeElement;
258
+
252
259
  return this;
253
260
  },
254
261
  /**
@@ -291,6 +298,10 @@ BMAlertPopup.prototype = BMExtend(Object.create(BMWindow.prototype), {
291
298
  this._resolve(BMConfirmationPopupResult.Confirmed);
292
299
  }
293
300
 
301
+ if (this._previouslyActiveNode) {
302
+ this._previouslyActiveNode.focus();
303
+ }
304
+
294
305
  return BMWindow.prototype.dismissAnimated.apply(this, arguments);
295
306
  }
296
307
 
@@ -455,6 +466,10 @@ BMConfirmationPopup.prototype = BMExtend(Object.create(BMAlertPopup.prototype),
455
466
  this._resolve(BMConfirmationPopupResult.Cancelled);
456
467
  }
457
468
 
469
+ if (this._previouslyActiveNode) {
470
+ this._previouslyActiveNode.focus();
471
+ }
472
+
458
473
  return BMWindow.prototype.dismissAnimated.apply(this, arguments);
459
474
  }
460
475
 
@@ -1,7 +1,7 @@
1
- import { NO } from "../Core/BMCoreUI";
2
-
3
1
  // @ts-check
4
2
 
3
+ import { NO } from "../Core/BMCoreUI";
4
+
5
5
  // @type BMKeyboardShortcutModifier
6
6
 
7
7
  /**
@@ -9,22 +9,27 @@ import { NO } from "../Core/BMCoreUI";
9
9
  * for views and windows.
10
10
  */
11
11
  export const BMKeyboardShortcutModifier = Object.freeze({ // <enum>
12
+
12
13
  /**
13
14
  * Represents the command key on macOS and iOS, windows key on Windows and meta key on Linux.
14
15
  */
15
16
  Command: {key: 'metaKey', value: 1}, // <enum>
17
+
16
18
  /**
17
19
  * Represents the option key on macOS and alt on other systems.
18
20
  */
19
21
  Option: {key: 'altKey', value: 2}, // <enum>
22
+
20
23
  /**
21
24
  * Represents the shit key.
22
25
  */
23
26
  Shift: {key: 'shiftKey', value: 4}, // <enum>
27
+
24
28
  /**
25
29
  * Represents the control key.
26
30
  */
27
31
  Control: {key: 'ctrlKey', value: 8}, // <enum>
32
+
28
33
  /**
29
34
  * Represents the command key on macOS and iOS and control key on other systems.
30
35
  */
@@ -96,7 +101,7 @@ BMKeyboardShortcut.prototype = {
96
101
  preventsDefault: NO, // <Boolean>
97
102
 
98
103
  /**
99
- * Initializes this keyboard shortcut with the given key and optional modifiers, as well as
104
+ * Designated initializer. Initializes this keyboard shortcut with the given key and optional modifiers, as well as
100
105
  * the target and action that will handle it.
101
106
  *
102
107
  * In order to be triggered, this keyboard shortcut must be registered with an event handler such as a window
@@ -124,6 +129,88 @@ BMKeyboardShortcut.prototype = {
124
129
  }
125
130
 
126
131
  return this;
132
+ },
133
+
134
+ /**
135
+ * Initializes this keyboard shortcut with the given keyboard event. If the event includes the control or command keys, these may be converted
136
+ * into a system key modifier based on the current platform.
137
+ * @param event <KeyboardEvent> The keyboard event from which this keyboard shortcut should be initialized.
138
+ * {
139
+ * @param target <AnyObject> The object that will handle this keyboard shortcut action.
140
+ * @param action <String> The name of a method on the target object that will be invoked when this
141
+ * keyboard shortcut is triggered. That method will receive the keyboard event as its single parameter.
142
+ * @param preventsDefault <Boolean, nullable> Defaults to `NO`. When set to `YES`, the default action of the event that triggers this keyboard shortcut will be prevented.
143
+ * }
144
+ *
145
+ */
146
+ initWithKeyboardEvent(event, {target, action, preventsDefault} = {}) {
147
+ const modifiers = [];
148
+ if (event.shiftKey) {
149
+ modifiers.push(BMKeyboardShortcutModifier.Shift);
150
+ }
151
+ if (event.altKey) {
152
+ modifiers.push(BMKeyboardShortcutModifier.Option);
153
+ }
154
+ if (event.metaKey) {
155
+ if (BMKeyboardShortcutModifier.System.value == BMKeyboardShortcutModifier.Command.value) {
156
+ modifiers.push(BMKeyboardShortcutModifier.System);
157
+ }
158
+ else {
159
+ modifiers.push(BMKeyboardShortcutModifier.Command);
160
+ }
161
+ }
162
+ if (event.ctrlKey) {
163
+ if (BMKeyboardShortcutModifier.System.value == BMKeyboardShortcutModifier.Control.value) {
164
+ modifiers.push(BMKeyboardShortcutModifier.System);
165
+ }
166
+ else {
167
+ modifiers.push(BMKeyboardShortcutModifier.Control);
168
+ }
169
+ }
170
+
171
+ return this.initWithKeyCode(event.code, {modifiers, target, action, preventsDefault});
172
+ },
173
+
174
+ /**
175
+ * Initializes this keyboard shortcut with the given serialized object representation. A callback is used to
176
+ * convert from the target object ID to an actual target object.
177
+ * @param shortcut <Object> A serialized keyboard shortcut.
178
+ * {
179
+ * @param targetID <Object ^(String)> A callback that is invoked with the target ID and should return
180
+ * the target object which will handle the keyboard shortcut.
181
+ * }
182
+ * @returns <Object> This keyboard shortcut.
183
+ */
184
+ initWithSerializedKeyboardShortcut(shortcut, {targetID: resolver}) {
185
+ if (shortcut._class != 'BMKeyboardShortcut') {
186
+ throw new Error(`Unknown keyboard shortcut class - "${shortcut._class}"`);
187
+ }
188
+
189
+ const target = resolver(shortcut._targetID);
190
+
191
+ const modifiers = shortcut._modifiers.map(modifier => {
192
+ return Object.values(BMKeyboardShortcutModifier).find(m => m.key == modifier)
193
+ });
194
+
195
+ return this.initWithKeyCode(shortcut._key, {modifiers, target, action: shortcut._action, preventsDefault: shortcut._preventsDefault});
196
+ },
197
+
198
+ /**
199
+ * Returns an object representation of this keyboard shortcut that can be used with `JSON.stringify` to
200
+ * serialize this keyboard shortcut.
201
+ * @param ID <String> A string that identifies the target object and can be used when recreating
202
+ * the keyboard shortcut instance.
203
+ * @returns <Object> An object that can be used with `JSON.stringify`.
204
+ */
205
+ serializedKeyboardShortcutWithTargetID(ID) {
206
+ return {
207
+ _class: `BMKeyboardShortcut`,
208
+ _key: this._key,
209
+ _modifiers: this._modifiers.map(m => m.key),
210
+ _targetID: ID,
211
+ _action: this._action,
212
+ _preventsDefault: this.preventsDefault,
213
+ };
127
214
  }
128
215
 
129
216
  };
@@ -141,6 +228,7 @@ BMKeyboardShortcut.prototype = {
141
228
  * @param target <AnyObject> The object that will handle this keyboard shortcut action.
142
229
  * @param action <String> The name of a method on the target object that will be invoked when this
143
230
  * keyboard shortcut is triggered. That method will receive the keyboard event as its single parameter.
231
+ * @param preventsDefault <Boolean, nullable> Defaults to `NO`. When set to `YES`, the default action of the event that triggers this keyboard shortcut will be prevented.
144
232
  * }
145
233
  * @return <BMKeyboardShortcut> A keyboard shortcut.
146
234
  */
@@ -148,4 +236,35 @@ BMKeyboardShortcut.keyboardShortcutWithKeyCode = function (key, args) {
148
236
  return (new BMKeyboardShortcut).initWithKeyCode(key, args);
149
237
  };
150
238
 
239
+
240
+ /**
241
+ * Constructs and returns a keyboard shortcut with the given keyboard event. If the event includes the control or command keys, these may be converted
242
+ * into a system key modifier based on the current platform.
243
+ * @param event <KeyboardEvent> The keyboard event from which this keyboard shortcut should be initialized.
244
+ * {
245
+ * @param target <AnyObject> The object that will handle this keyboard shortcut action.
246
+ * @param action <String> The name of a method on the target object that will be invoked when this
247
+ * keyboard shortcut is triggered. That method will receive the keyboard event as its single parameter.
248
+ * @param preventsDefault <Boolean, nullable> Defaults to `NO`. When set to `YES`, the default action of the event that triggers this keyboard shortcut will be prevented.
249
+ * }
250
+ *
251
+ */
252
+ BMKeyboardShortcut.keyboardShortcutWithKeyboardEvent = function (event, args) {
253
+ return (new BMKeyboardShortcut).initWithKeyboardEvent(event, args);
254
+ };
255
+
256
+ /**
257
+ * Constructs and returns keyboard shortcut with the given serialized object representation. A callback is used to
258
+ * convert from the target object ID to an actual target object.
259
+ * @param shortcut <Object> A serialized keyboard shortcut.
260
+ * {
261
+ * @param targetID <Object ^(String)> A callback that is invoked with the target ID and should return
262
+ * the target object which will handle the keyboard shortcut.
263
+ * }
264
+ * @returns <Object> A keyboard shortcut.
265
+ */
266
+ BMKeyboardShortcut.keyboardShortcutWithSerializedKeyboardShortcut = function (shortcut, args) {
267
+ return (new BMKeyboardShortcut).initWithSerializedKeyboardShortcut(shortcut, args);
268
+ };
269
+
151
270
  // @endtype