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.
- package/CHANGELOG.md +147 -0
- package/build/@types/index.d.ts +672 -92
- package/build/BMCodeEditor/BMCodeEditor.js +24 -27
- package/build/BMCollectionView/BMCollectionView.js +404 -55
- package/build/BMCollectionView/BMCollectionViewFlowLayout.js +423 -33
- package/build/BMCollectionView/BMCollectionViewLayout.js +158 -0
- package/build/BMCollectionView/BMCollectionViewMasonryLayout.js +145 -5
- package/build/BMCollectionView/BMCollectionViewTileLayout.js +147 -2
- package/build/BMCoreUI.css +60 -10
- package/build/BMView/BMMenu.js +155 -8
- package/build/BMView/BMTextField.js +25 -14
- package/build/BMView/BMView_v2.5.js +205 -3
- package/build/BMViewLayoutEditor/BMLayoutEditor.js +188 -168
- package/build/BMViewLayoutEditor/BMLayoutEditorSettingCells.js +2 -2
- package/build/BMWindow/BMConfirmationPopup.js +15 -0
- package/build/BMWindow/BMKeyboardShortcut.js +122 -3
- package/build/BMWindow/BMWindow.js +25 -90
- package/build/Core/BMAnimationContext.js +0 -4
- package/build/Core/BMCoreUI.js +9 -1
- package/lib/@types/BMCoreUI.min.d.ts +672 -92
- package/lib/BMCoreUI.min.js +1 -1
- package/package.json +1 -1
package/build/BMView/BMMenu.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
import {YES, NO, BMCopyProperties} from '../Core/BMCoreUI'
|
|
3
|
+
import {YES, NO, BMCopyProperties, BMNumberByConstrainingNumberToBounds} from '../Core/BMCoreUI'
|
|
4
4
|
import {BMPoint, BMPointMake} from '../Core/BMPoint'
|
|
5
5
|
import {BMHook, __BMVelocityAnimate} from '../Core/BMAnimationContext'
|
|
6
6
|
import 'velocity-animate'
|
|
7
7
|
import { BMRectMakeWithNodeFrame } from '../Core/BMRect';
|
|
8
|
+
import { BMView } from './BMView_v2.5';
|
|
9
|
+
import { BMKeyboardShortcut } from '../BMWindow/BMKeyboardShortcut';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* The standard spacing between the menu and the node it is created from.
|
|
@@ -218,6 +220,40 @@ BMMenu.prototype = {
|
|
|
218
220
|
*/
|
|
219
221
|
_sourceNodeDisplacement: undefined, // <Number, nullable>
|
|
220
222
|
|
|
223
|
+
/**
|
|
224
|
+
* The index of the currently highlighted menu item.
|
|
225
|
+
*/
|
|
226
|
+
_highlightedIndex: -1, // <Number>
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The DOM node that was focused when this alert was opened.
|
|
230
|
+
*/
|
|
231
|
+
_previouslyActiveNode: undefined, // DOMNode
|
|
232
|
+
|
|
233
|
+
get highlightedIndex() {
|
|
234
|
+
return this._highlightedIndex;
|
|
235
|
+
},
|
|
236
|
+
|
|
237
|
+
set highlightedIndex(index) {
|
|
238
|
+
if (index != this._highlightedIndex) {
|
|
239
|
+
this._highlightedIndex = index;
|
|
240
|
+
|
|
241
|
+
if (this._node) {
|
|
242
|
+
// Remove the currently highlighted item's style
|
|
243
|
+
const highlightedItem = this._node.querySelector('.BMMenuItemHighlighted');
|
|
244
|
+
if (highlightedItem) {
|
|
245
|
+
highlightedItem.classList.remove('BMMenuItemHighlighted');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// And add it to the newly highlighted item
|
|
249
|
+
const nextHighlightedItem = this._node.children[index];
|
|
250
|
+
if (nextHighlightedItem) {
|
|
251
|
+
nextHighlightedItem.classList.add('BMMenuItemHighlighted');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
|
|
221
257
|
/**
|
|
222
258
|
* Initializes this menu with the specified items.
|
|
223
259
|
* The menu will be hidden by default; the method showAtPoint(_) should be used to make the menu visible.
|
|
@@ -241,6 +277,35 @@ BMMenu.prototype = {
|
|
|
241
277
|
menuNode.className = this._CSSClass ? `BMMenu ${this._CSSClass}` : 'BMMenu';
|
|
242
278
|
this._node = menuNode;
|
|
243
279
|
|
|
280
|
+
this._highlightedIndex = -1;
|
|
281
|
+
|
|
282
|
+
menuNode.tabIndex = -1;
|
|
283
|
+
|
|
284
|
+
// Register the keyboard shortcuts to be used while the menu is visible
|
|
285
|
+
const upArrow = BMKeyboardShortcut.keyboardShortcutWithKeyCode('ArrowUp', {modifiers: [], target: this, action: 'arrowUpPressedWithEvent'});
|
|
286
|
+
upArrow.preventsDefault = YES;
|
|
287
|
+
BMView.registerKeyboardShortcut(upArrow, {forNode: menuNode});
|
|
288
|
+
|
|
289
|
+
const downArrow = BMKeyboardShortcut.keyboardShortcutWithKeyCode('ArrowDown', {modifiers: [], target: this, action: 'arrowDownPressedWithEvent'});
|
|
290
|
+
downArrow.preventsDefault = YES;
|
|
291
|
+
BMView.registerKeyboardShortcut(downArrow, {forNode: menuNode});
|
|
292
|
+
|
|
293
|
+
const returnShortcut = BMKeyboardShortcut.keyboardShortcutWithKeyCode('Enter', {modifiers: [], target: this, action: 'returnPressedWithEvent'});
|
|
294
|
+
returnShortcut.preventsDefault = YES;
|
|
295
|
+
BMView.registerKeyboardShortcut(returnShortcut, {forNode: menuNode});
|
|
296
|
+
|
|
297
|
+
const spacebar = BMKeyboardShortcut.keyboardShortcutWithKeyCode('Space', {modifiers: [], target: this, action: 'returnPressedWithEvent'});
|
|
298
|
+
spacebar.preventsDefault = YES;
|
|
299
|
+
BMView.registerKeyboardShortcut(spacebar, {forNode: menuNode});
|
|
300
|
+
|
|
301
|
+
const tab = BMKeyboardShortcut.keyboardShortcutWithKeyCode('Tab', {modifiers: [], target: this, action: 'tabPressedWithEvent'});
|
|
302
|
+
tab.preventsDefault = YES;
|
|
303
|
+
BMView.registerKeyboardShortcut(tab, {forNode: menuNode});
|
|
304
|
+
|
|
305
|
+
const escape = BMKeyboardShortcut.keyboardShortcutWithKeyCode('Escape', {modifiers: [], target: this, action: 'escapePressedWithEvent'});
|
|
306
|
+
escape.preventsDefault = YES;
|
|
307
|
+
BMView.registerKeyboardShortcut(escape, {forNode: menuNode});
|
|
308
|
+
|
|
244
309
|
// The overlay which intercepts clicks outside of the menu
|
|
245
310
|
const menuContainer = document.createElement('div');
|
|
246
311
|
menuContainer.className = 'BMMenuContainer';
|
|
@@ -254,6 +319,10 @@ BMMenu.prototype = {
|
|
|
254
319
|
|
|
255
320
|
menuNode.addEventListener('click', event => event.preventDefault());
|
|
256
321
|
|
|
322
|
+
menuNode.addEventListener('mouseleave', event => {
|
|
323
|
+
this.highlightedIndex = -1;
|
|
324
|
+
});
|
|
325
|
+
|
|
257
326
|
menuContainer.addEventListener('click', event => {
|
|
258
327
|
this.closeAnimated(YES);
|
|
259
328
|
event.preventDefault();
|
|
@@ -271,12 +340,13 @@ BMMenu.prototype = {
|
|
|
271
340
|
_renderMenuItems() {
|
|
272
341
|
// Create and append the items for this menu
|
|
273
342
|
const iconSize = this._iconSize;
|
|
274
|
-
|
|
343
|
+
|
|
344
|
+
this._items.forEach((item, index) => {
|
|
275
345
|
if (item.name.startsWith('---')) {
|
|
276
346
|
const itemNode = document.createElement('div');
|
|
277
347
|
itemNode.className = 'BMMenuDivider';
|
|
278
348
|
this._node.appendChild(itemNode);
|
|
279
|
-
|
|
349
|
+
return;
|
|
280
350
|
}
|
|
281
351
|
|
|
282
352
|
let itemNode = document.createElement('div');
|
|
@@ -298,6 +368,8 @@ BMMenu.prototype = {
|
|
|
298
368
|
itemNode.appendChild(itemText);
|
|
299
369
|
|
|
300
370
|
itemNode.addEventListener('click', event => {
|
|
371
|
+
itemNode.classList.add('BMMenuItemActive');
|
|
372
|
+
|
|
301
373
|
if (item.action) item.action(item);
|
|
302
374
|
|
|
303
375
|
if (this.delegate && this.delegate.menuDidSelectItem) {
|
|
@@ -305,8 +377,14 @@ BMMenu.prototype = {
|
|
|
305
377
|
}
|
|
306
378
|
});
|
|
307
379
|
|
|
380
|
+
itemNode.addEventListener('mouseenter', event => {
|
|
381
|
+
this.highlightedIndex = index;
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
itemNode.addEventListener
|
|
385
|
+
|
|
308
386
|
this._node.appendChild(itemNode);
|
|
309
|
-
}
|
|
387
|
+
});
|
|
310
388
|
},
|
|
311
389
|
|
|
312
390
|
/**
|
|
@@ -408,6 +486,9 @@ BMMenu.prototype = {
|
|
|
408
486
|
delay += 16;
|
|
409
487
|
}
|
|
410
488
|
|
|
489
|
+
this._previouslyActiveNode = document.activeElement;
|
|
490
|
+
menuNode.focus();
|
|
491
|
+
|
|
411
492
|
// Animate the source node shadow
|
|
412
493
|
await __BMVelocityAnimate(this._sourceNodeShadow, {translateY: displacement + 'px'}, {duration: duration, easing: easing});
|
|
413
494
|
|
|
@@ -484,6 +565,9 @@ BMMenu.prototype = {
|
|
|
484
565
|
delay += 16;
|
|
485
566
|
}
|
|
486
567
|
|
|
568
|
+
this._previouslyActiveNode = document.activeElement;
|
|
569
|
+
menuNode.focus();
|
|
570
|
+
|
|
487
571
|
return menuNode;
|
|
488
572
|
},
|
|
489
573
|
|
|
@@ -500,10 +584,14 @@ BMMenu.prototype = {
|
|
|
500
584
|
this.delegate.menuWillClose(this);
|
|
501
585
|
}
|
|
502
586
|
|
|
587
|
+
if (this._previouslyActiveNode) {
|
|
588
|
+
this._previouslyActiveNode.focus();
|
|
589
|
+
}
|
|
590
|
+
|
|
503
591
|
this._node.style.pointerEvents = 'none';
|
|
504
592
|
this._containerNode.style.pointerEvents = 'none';
|
|
505
593
|
let delay = this._node.childNodes.length * 16 + 100 - 200;
|
|
506
|
-
delay = (delay < 0 ? 0 : delay);
|
|
594
|
+
delay = (delay < 0 ? 0 : delay) + 200;
|
|
507
595
|
|
|
508
596
|
const sourceNodeShadow = this._sourceNodeShadow;
|
|
509
597
|
const containerNode = this._containerNode;
|
|
@@ -517,8 +605,8 @@ BMMenu.prototype = {
|
|
|
517
605
|
const easing = sourceNodeShadow ? 'easeInOutQuad' : 'easeInQuad';
|
|
518
606
|
|
|
519
607
|
if (sourceNodeShadow) {
|
|
520
|
-
__BMVelocityAnimate(sourceNodeShadow, {translateY: '0px'}, {duration: duration, easing: easing});
|
|
521
|
-
__BMVelocityAnimate(containerNode, {opacity: 0}, {duration: duration, easing: easing});
|
|
608
|
+
__BMVelocityAnimate(sourceNodeShadow, {translateY: '0px'}, {duration: duration, easing: easing, delay});
|
|
609
|
+
__BMVelocityAnimate(containerNode, {opacity: 0}, {duration: duration, easing: easing, delay});
|
|
522
610
|
}
|
|
523
611
|
|
|
524
612
|
__BMVelocityAnimate(this._node, {
|
|
@@ -543,7 +631,7 @@ BMMenu.prototype = {
|
|
|
543
631
|
}
|
|
544
632
|
});
|
|
545
633
|
|
|
546
|
-
delay =
|
|
634
|
+
delay = 200;
|
|
547
635
|
for (let i = this._node.childNodes.length - 1; i >= 0; i--) {
|
|
548
636
|
let child = this._node.childNodes[i];
|
|
549
637
|
__BMVelocityAnimate(child, {translateY: '16px', translateZ: 0, opacity: 0}, {
|
|
@@ -556,7 +644,66 @@ BMMenu.prototype = {
|
|
|
556
644
|
|
|
557
645
|
this._node = undefined;
|
|
558
646
|
this._containerNode = undefined;
|
|
647
|
+
},
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Invoked when the up arrow is pressed.
|
|
651
|
+
* @param event <KeyboardEvent> The event that triggered this action.
|
|
652
|
+
*/
|
|
653
|
+
arrowUpPressedWithEvent(event) {
|
|
654
|
+
if (this._highlightedIndex != 0) {
|
|
655
|
+
this.highlightedIndex = BMNumberByConstrainingNumberToBounds(this._highlightedIndex - 1, 0, this._items.length - 1);
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Invoked when the down arrow is pressed.
|
|
661
|
+
* @param event <KeyboardEvent> The event that triggered this action.
|
|
662
|
+
*/
|
|
663
|
+
arrowDownPressedWithEvent(event) {
|
|
664
|
+
if (this._highlightedIndex != this._items.length - 1) {
|
|
665
|
+
this.highlightedIndex = BMNumberByConstrainingNumberToBounds(this._highlightedIndex + 1, 0, this._items.length - 1);
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
|
|
669
|
+
returnPressedWithEvent(event) {
|
|
670
|
+
if (this._highlightedIndex >= 0 && this._highlightedIndex < this._items.length) {
|
|
671
|
+
const item = this._items[this._highlightedIndex];
|
|
672
|
+
|
|
673
|
+
if (item.action) item.action(item);
|
|
674
|
+
|
|
675
|
+
if (this.delegate && this.delegate.menuDidSelectItem) {
|
|
676
|
+
this.delegate.menuDidSelectItem(this, item);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
const highlightedNode = this._node.querySelector('.BMMenuItemHighlighted');
|
|
680
|
+
if (highlightedNode) {
|
|
681
|
+
highlightedNode.classList.add('BMMenuItemActive');
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
this.closeAnimated(YES);
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Invoked when the tab key is pressed.
|
|
690
|
+
* @param event <KeyboardEvent> The event that triggered this action.
|
|
691
|
+
*/
|
|
692
|
+
tabPressedWithEvent(event) {
|
|
693
|
+
// No action, this just prevents the keyboard focus from moving to another element
|
|
694
|
+
},
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* Invoked when the escape key is pressed.
|
|
698
|
+
* @param event <KeyboardEvent> The event that triggered this action.
|
|
699
|
+
*/
|
|
700
|
+
escapePressedWithEvent(event) {
|
|
701
|
+
if (this._node) {
|
|
702
|
+
this.closeAnimated(YES);
|
|
703
|
+
}
|
|
559
704
|
}
|
|
705
|
+
|
|
706
|
+
|
|
560
707
|
};
|
|
561
708
|
|
|
562
709
|
/**
|
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { BMExtend, NO, YES } from "../Core/BMCoreUI";
|
|
2
4
|
import { BMView } from "./BMView_v2.5";
|
|
3
5
|
import { BMMenuKind } from "./BMMenu";
|
|
6
|
+
import { BMRectMakeWithNodeFrame } from "../Core/BMRect";
|
|
7
|
+
import { BMPointMake } from "../Core/BMPoint";
|
|
8
|
+
import { BMHook } from "../Core/BMAnimationContext";
|
|
4
9
|
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The default limit of visible suggestions.
|
|
12
|
+
*/
|
|
7
13
|
const BM_TEXT_FIELD_MAX_SUGGESTIONS = 10;
|
|
8
14
|
|
|
9
15
|
// @type BMTextField extends BMView
|
|
@@ -63,6 +69,11 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
63
69
|
}
|
|
64
70
|
},
|
|
65
71
|
|
|
72
|
+
/**
|
|
73
|
+
* The maximum number of suggestions that will be visible when the autocomplete menu is open.
|
|
74
|
+
*/
|
|
75
|
+
maxSuggestions: BM_TEXT_FIELD_MAX_SUGGESTIONS, // <Number>
|
|
76
|
+
|
|
66
77
|
/**
|
|
67
78
|
* An optional delegate that can be used to respond to various callbacks from the text field.
|
|
68
79
|
*/
|
|
@@ -138,7 +149,7 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
138
149
|
|
|
139
150
|
highlightedItemIndex = -1;
|
|
140
151
|
|
|
141
|
-
let limit =
|
|
152
|
+
let limit = this.maxSuggestions;
|
|
142
153
|
for (const option of suggestions) {
|
|
143
154
|
options.push(this._menuItemWithLabel(option, {action: () => {
|
|
144
155
|
menu = undefined;
|
|
@@ -201,7 +212,7 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
201
212
|
highlightedItemIndex = Math.max((highlightedItemIndex || 0) - 1, 0);
|
|
202
213
|
}
|
|
203
214
|
else {
|
|
204
|
-
highlightedItemIndex = Math.min((isNaN(highlightedItemIndex) ? -1 : highlightedItemIndex) + 1, filteredSuggestions.length - 1);
|
|
215
|
+
highlightedItemIndex = Math.min((isNaN(highlightedItemIndex) ? -1 : highlightedItemIndex) + 1, filteredSuggestions.length - 1, this.maxSuggestions - 1);
|
|
205
216
|
}
|
|
206
217
|
|
|
207
218
|
// Move the menu highlight
|
|
@@ -255,6 +266,9 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
255
266
|
});
|
|
256
267
|
|
|
257
268
|
inputNode.addEventListener('input', this._inputHandler = event => {
|
|
269
|
+
// Set to YES if the change event is fired due to autocompleting the text in the text field
|
|
270
|
+
let textFieldDidHighlightSuggestion = NO;
|
|
271
|
+
|
|
258
272
|
// If suggestions are not enabled, no additional action is required
|
|
259
273
|
if (!this._usesSuggestions) {
|
|
260
274
|
if (!textFieldDidHighlightSuggestion) {
|
|
@@ -273,7 +287,7 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
273
287
|
filteredSuggestions = suggestions.filter(e => e.toLowerCase().startsWith(inputNode.value.toLowerCase()));
|
|
274
288
|
|
|
275
289
|
// Limit to the topmost 10 items
|
|
276
|
-
filteredSuggestions.length = Math.min(filteredSuggestions.length,
|
|
290
|
+
filteredSuggestions.length = Math.min(filteredSuggestions.length, this.maxSuggestions);
|
|
277
291
|
|
|
278
292
|
// Empty the menu
|
|
279
293
|
if (menu) menu.innerHTML = '';
|
|
@@ -282,7 +296,7 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
282
296
|
highlightedItemIndex = 0;
|
|
283
297
|
|
|
284
298
|
// Then add the filtered options
|
|
285
|
-
let limit =
|
|
299
|
+
let limit = this.maxSuggestions;
|
|
286
300
|
if (menu) for (const option of filteredSuggestions) {
|
|
287
301
|
menu.appendChild(this._menuItemWithLabel(option, {action: () => {
|
|
288
302
|
menu = undefined;
|
|
@@ -298,9 +312,6 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
298
312
|
if (!limit) break;
|
|
299
313
|
}
|
|
300
314
|
|
|
301
|
-
// Set to YES if the change event is fired due to autocompleting the text in the text field
|
|
302
|
-
let textFieldDidHighlightSuggestion = NO;
|
|
303
|
-
|
|
304
315
|
// Highlight the first item
|
|
305
316
|
if (filteredSuggestions.length) {
|
|
306
317
|
if (menu) {
|
|
@@ -311,7 +322,7 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
311
322
|
// due to items being removed
|
|
312
323
|
let frame = BMRectMakeWithNodeFrame(inputNode);
|
|
313
324
|
let point = BMPointMake(frame.origin.x, frame.bottom);
|
|
314
|
-
this.
|
|
325
|
+
this._repositionMenu(menu, {atPoint: point});
|
|
315
326
|
}
|
|
316
327
|
|
|
317
328
|
// Autocomplete the suggestion, if the caret is at the end of the text field
|
|
@@ -408,7 +419,7 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
408
419
|
let constraintPopup = document.createElement('div');
|
|
409
420
|
constraintPopup.className = 'BMLayoutEditorConstraintPopup BMTextFieldSuggestions';
|
|
410
421
|
|
|
411
|
-
menu = constraintPopup;
|
|
422
|
+
let menu = constraintPopup;
|
|
412
423
|
|
|
413
424
|
let constraintPopupContainer = document.createElement('div');
|
|
414
425
|
constraintPopupContainer.className = 'BMLayoutEditorConstraintPopupContainer';
|
|
@@ -423,7 +434,7 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
423
434
|
|
|
424
435
|
document.body.appendChild(constraintPopupContainer);
|
|
425
436
|
|
|
426
|
-
this.
|
|
437
|
+
this._repositionMenu(menu, {atPoint: point});
|
|
427
438
|
|
|
428
439
|
constraintPopup.addEventListener('click', event => event.preventDefault());
|
|
429
440
|
|
|
@@ -472,7 +483,7 @@ BMTextField.prototype = BMExtend(Object.create(BMView.prototype), {
|
|
|
472
483
|
return constraintPopup;
|
|
473
484
|
},
|
|
474
485
|
|
|
475
|
-
|
|
486
|
+
_repositionMenu(menu, {atPoint: point}) {
|
|
476
487
|
let height = menu.offsetHeight;
|
|
477
488
|
|
|
478
489
|
menu.style.transformOrigin = '50% 0%';
|
|
@@ -10,6 +10,7 @@ import {BMLayoutSizeClass, BMLayoutOrientation} from './BMLayoutSizeClass'
|
|
|
10
10
|
import {BMViewport} from './BMViewport'
|
|
11
11
|
import {BMLayoutAttribute, BMLayoutConstraintRelation, BMLayoutConstraint, BMLayoutConstraintPriorityRequired, BMLayoutConstraintKind} from './BMLayoutConstraint_v2.5'
|
|
12
12
|
import * as kiwi from 'kiwi.js'
|
|
13
|
+
import { BMKeyboardShortcutModifier } from '../BMWindow/BMKeyboardShortcut'
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
// When set to YES, this will cause view to use transforms instead of left/right for positioning
|
|
@@ -302,7 +303,6 @@ const _BMViewDequeueLayoutQueue = function (layoutQueue) {
|
|
|
302
303
|
*
|
|
303
304
|
* Views are usually not constructed using any constructor function. Instead, their lifecycle is managed by CoreUI.
|
|
304
305
|
* You obtain views by invoking the static <code>viewForNode(_)</code> method on the <code>BMView</code> type.
|
|
305
|
-
* It is also not necessary to invoke any destruction method when the DOM nodes managed by views are removed.
|
|
306
306
|
*
|
|
307
307
|
* To extend from BMView and create a view subtype, extend from the BMView prototype, then, when requesting views use
|
|
308
308
|
* <code>BMView.viewForNode.call(MyCustomViewType, node)</code>. Note that if the node already had
|
|
@@ -317,6 +317,8 @@ export function BMView() {} // <constructor>
|
|
|
317
317
|
// A private map maintaining the link between DOM nodes and their associated view objects.
|
|
318
318
|
var _BMViewMap = new WeakMap; // <WeakMap<DOMNode, BMView>>
|
|
319
319
|
|
|
320
|
+
// #region Factory methods
|
|
321
|
+
|
|
320
322
|
/**
|
|
321
323
|
* Returns the view object associated with the given DOM node.
|
|
322
324
|
* @param node <DOMNode> The DOM node for which to retrieve the view.
|
|
@@ -347,6 +349,10 @@ export function BMView() {} // <constructor>
|
|
|
347
349
|
return view;
|
|
348
350
|
}
|
|
349
351
|
|
|
352
|
+
// #endregion
|
|
353
|
+
|
|
354
|
+
// #region Layout variables
|
|
355
|
+
|
|
350
356
|
// A set that contains the views that are root views.
|
|
351
357
|
// These are the view that will handle updates to layout variables.
|
|
352
358
|
const rootViews = new Set;
|
|
@@ -605,6 +611,86 @@ export function BMView() {} // <constructor>
|
|
|
605
611
|
return values;
|
|
606
612
|
}
|
|
607
613
|
|
|
614
|
+
// #endregion
|
|
615
|
+
|
|
616
|
+
// #region Keyboard shortcuts
|
|
617
|
+
|
|
618
|
+
// A private map containing a mapping between DOM nodes and view stubs that are used for implementing
|
|
619
|
+
// keyboard shortcuts
|
|
620
|
+
const _NodeKeyboardShortcutsMap = new WeakMap; // <WeakMap<DOMNode, Object>
|
|
621
|
+
|
|
622
|
+
// A class that is a subset of `BMView` and contains only the keyboard shortcuts related functionality.
|
|
623
|
+
const _BMViewKeyboardShortcutsStub = function (node) {
|
|
624
|
+
this.node = node;
|
|
625
|
+
this._keyboardShortcuts = {};
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
_BMViewKeyboardShortcutsStub.prototype = {
|
|
629
|
+
|
|
630
|
+
node: undefined, // <DOMNode>
|
|
631
|
+
|
|
632
|
+
_keyboardShortcuts: undefined, // <Dictionary<string, [BMKeyboardShortcut]>>
|
|
633
|
+
|
|
634
|
+
registerKeyboardShortcut(shortcut) {
|
|
635
|
+
return BMView.prototype.registerKeyboardShortcut.apply(this, arguments);
|
|
636
|
+
},
|
|
637
|
+
|
|
638
|
+
unregisterKeyboardShortcut(shortcut) {
|
|
639
|
+
return BMView.prototype.unregisterKeyboardShortcut.apply(this, arguments);
|
|
640
|
+
},
|
|
641
|
+
|
|
642
|
+
keyPressedWithEvent(event) {
|
|
643
|
+
return BMView.prototype.keyPressedWithEvent.apply(this, arguments);
|
|
644
|
+
},
|
|
645
|
+
|
|
646
|
+
_keyboardShortcutsEnabled: NO,
|
|
647
|
+
|
|
648
|
+
_enableKeyboardShortcuts() {
|
|
649
|
+
return BMView.prototype._enableKeyboardShortcuts.apply(this, arguments);
|
|
650
|
+
},
|
|
651
|
+
|
|
652
|
+
_disableKeyboardShortcuts() {
|
|
653
|
+
return BMView.prototype._disableKeyboardShortcuts.apply(this, arguments);
|
|
654
|
+
},
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Registers a keyboard shortcut that can be activated when the given node has keyboard focus.
|
|
659
|
+
* @param shortcut <BMKeyboardShortcut> The shortcut to register.
|
|
660
|
+
* {
|
|
661
|
+
* @param forNode <DOMNode> The DOM node for which the shortcut should be registered.
|
|
662
|
+
* }
|
|
663
|
+
*/
|
|
664
|
+
BMView.registerKeyboardShortcut = function (shortcut, {forNode: node}) {
|
|
665
|
+
let viewStub = _NodeKeyboardShortcutsMap.get(node);
|
|
666
|
+
if (!viewStub) {
|
|
667
|
+
viewStub = new _BMViewKeyboardShortcutsStub(node);
|
|
668
|
+
_NodeKeyboardShortcutsMap.set(node, viewStub);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
viewStub.registerKeyboardShortcut(shortcut);
|
|
672
|
+
},
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Unregisters a keyboard shortcut. If this keyboard shortcut had not been previously registered, this method does nothing.
|
|
676
|
+
* @param shortcut <BMKeyboardShortcut> The keyboard shortcut to unregister.
|
|
677
|
+
* {
|
|
678
|
+
* @param forNode <DOMNode> The DOM node for which the shortcut should be unregistered.
|
|
679
|
+
* }
|
|
680
|
+
*/
|
|
681
|
+
BMView.unregisterKeyboardShortcut = function (shortcut, {forNode: node}) {
|
|
682
|
+
const viewStub = _NodeKeyboardShortcutsMap.get(node);
|
|
683
|
+
|
|
684
|
+
if (viewStub) {
|
|
685
|
+
viewStub.unregisterKeyboardShortcut(shortcut);
|
|
686
|
+
}
|
|
687
|
+
},
|
|
688
|
+
|
|
689
|
+
|
|
690
|
+
// #endregion
|
|
691
|
+
|
|
692
|
+
// #region Destructors
|
|
693
|
+
|
|
608
694
|
/**
|
|
609
695
|
* Used when releasing an entire view hierarchy.
|
|
610
696
|
* Releases this view without affecting constraints or the DOM.
|
|
@@ -654,6 +740,10 @@ export function BMView() {} // <constructor>
|
|
|
654
740
|
rootViews.delete(this);
|
|
655
741
|
}
|
|
656
742
|
|
|
743
|
+
// #endregion
|
|
744
|
+
|
|
745
|
+
// #region Initializer
|
|
746
|
+
|
|
657
747
|
/**
|
|
658
748
|
* Designated initializer, invoked immediately after any view is created.
|
|
659
749
|
* Subclasses must invoke this base initializer at some point during their initialization.
|
|
@@ -705,9 +795,13 @@ export function BMView() {} // <constructor>
|
|
|
705
795
|
this._configuration = {opacity: this._opacity, isVisible: this._isVisible, contentInsets: this.__activeInsets};
|
|
706
796
|
this._variableProperties = {};
|
|
707
797
|
|
|
798
|
+
this._keyboardShortcuts = {};
|
|
799
|
+
|
|
708
800
|
return this;
|
|
709
801
|
}
|
|
710
802
|
|
|
803
|
+
// #endregion
|
|
804
|
+
|
|
711
805
|
})();
|
|
712
806
|
|
|
713
807
|
BMView.prototype = BMExtend(BMView.prototype, {
|
|
@@ -819,16 +913,19 @@ BMView.prototype = BMExtend(BMView.prototype, {
|
|
|
819
913
|
|
|
820
914
|
/**
|
|
821
915
|
* The layout queue on which this view processes its layout passes.
|
|
822
|
-
* If this property is modified while this view had already registered a layout pass with a different queue,
|
|
823
|
-
* that layout pass will still run on that queue the next time it is drained.
|
|
824
916
|
*/
|
|
825
917
|
_layoutQueue: _BMViewLayoutQueue, // <BMViewLayoutQueue, nullResettable>
|
|
826
918
|
get layoutQueue() {
|
|
827
919
|
return this._layoutQueue;
|
|
828
920
|
},
|
|
829
921
|
set layoutQueue(queue) {
|
|
922
|
+
const hasView = this._layoutQueue._views.has(this);
|
|
830
923
|
this._layoutQueue._removeView(this);
|
|
831
924
|
this._layoutQueue = queue || _BMViewLayoutQueue;
|
|
925
|
+
|
|
926
|
+
if (hasView) {
|
|
927
|
+
this._layoutQueue._enqueueView(this);
|
|
928
|
+
}
|
|
832
929
|
},
|
|
833
930
|
|
|
834
931
|
// #endregion
|
|
@@ -2108,6 +2205,111 @@ BMView.prototype = BMExtend(BMView.prototype, {
|
|
|
2108
2205
|
|
|
2109
2206
|
// #endregion
|
|
2110
2207
|
|
|
2208
|
+
// #region Keyboard shortcuts
|
|
2209
|
+
|
|
2210
|
+
/**
|
|
2211
|
+
* The keyboard shortcuts that have been registered to this view.
|
|
2212
|
+
*/
|
|
2213
|
+
_keyboardShortcuts: undefined, // <Dictionary<string, [BMKeyboardShortcut]>>
|
|
2214
|
+
|
|
2215
|
+
/**
|
|
2216
|
+
* Registers a keyboard shortcut that can be activated when this view has keyboard focus.
|
|
2217
|
+
* @param shortcut <BMKeyboardShortcut> The keyboard shortcut to register.
|
|
2218
|
+
*/
|
|
2219
|
+
registerKeyboardShortcut(shortcut) {
|
|
2220
|
+
if (!Object.keys(this._keyboardShortcuts).length) this._enableKeyboardShortcuts();
|
|
2221
|
+
|
|
2222
|
+
if (!this._keyboardShortcuts[shortcut.keyCode]) {
|
|
2223
|
+
this._keyboardShortcuts[shortcut.keyCode] = [];
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
this._keyboardShortcuts[shortcut.keyCode].push(shortcut);
|
|
2227
|
+
},
|
|
2228
|
+
|
|
2229
|
+
/**
|
|
2230
|
+
* Unregisters a keyboard shortcut. If this keyboard shortcut had not been previously registered, this method does nothing.
|
|
2231
|
+
* @param shortcut <BMKeyboardShortcut> The keyboard shortcut to unregister.
|
|
2232
|
+
*/
|
|
2233
|
+
unregisterKeyboardShortcut(shortcut) {
|
|
2234
|
+
const shortcuts = this._keyboardShortcuts[shortcut.keyCode];
|
|
2235
|
+
|
|
2236
|
+
if (shortcuts) {
|
|
2237
|
+
for (let i = 0; i < shortcuts.length; i++) {
|
|
2238
|
+
if (shortcuts[i] == shortcut) {
|
|
2239
|
+
shortcuts.splice(i, 1);
|
|
2240
|
+
break;
|
|
2241
|
+
}
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
// If this removes the last shortcut for this key code, remove the entry
|
|
2245
|
+
if (!shortcuts.length) {
|
|
2246
|
+
delete this._keyboardShortcuts[shortcut.keyCode];
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
if (!Object.keys(this._keyboardShortcuts).length) this._disableKeyboardShortcuts();
|
|
2251
|
+
},
|
|
2252
|
+
|
|
2253
|
+
/**
|
|
2254
|
+
* If keyboard shortcuts have been registered on this view, this method will be invoked to identify
|
|
2255
|
+
* and handle key presses. Subclasses that override this method should invoke the base implementation to allow
|
|
2256
|
+
* keyboard shortcuts to be handled correctly.
|
|
2257
|
+
* @param event <KeyboardEvent> The event that triggered this action.
|
|
2258
|
+
*/
|
|
2259
|
+
keyPressedWithEvent(event) {
|
|
2260
|
+
if (!this._keyboardShortcuts[event.code]) return;
|
|
2261
|
+
|
|
2262
|
+
// Check if a shortcut key has been pressed.
|
|
2263
|
+
for (const shortcut of this._keyboardShortcuts[event.code]) {
|
|
2264
|
+
// Build the modifier bitmap for this event
|
|
2265
|
+
let bitmap = 0;
|
|
2266
|
+
for (const modifier in BMKeyboardShortcutModifier) {
|
|
2267
|
+
if (event[BMKeyboardShortcutModifier[modifier].key]) {
|
|
2268
|
+
bitmap = bitmap | BMKeyboardShortcutModifier[modifier].value;
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
if (bitmap == shortcut._modifierBitmap) {
|
|
2273
|
+
if (shortcut.preventsDefault) event.preventDefault();
|
|
2274
|
+
shortcut.target[shortcut.action](event, {forKeyboardShortcut: shortcut});
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
},
|
|
2278
|
+
|
|
2279
|
+
/**
|
|
2280
|
+
* Set to `YES` if keyboard shortcuts are enabled.
|
|
2281
|
+
*/
|
|
2282
|
+
_keyboardShortcutsEnabled: NO, // <Boolean>
|
|
2283
|
+
|
|
2284
|
+
/**
|
|
2285
|
+
* Enables keyboard shortcut handling. This makes the view's `node` focusable and attaches
|
|
2286
|
+
* the relevant event handlers to it.
|
|
2287
|
+
*/
|
|
2288
|
+
_enableKeyboardShortcuts() {
|
|
2289
|
+
// Make this view focusable if it is not already
|
|
2290
|
+
if (!this.node.hasAttribute('tabIndex')) {
|
|
2291
|
+
this.node.tabIndex = -1;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2294
|
+
this._keypressHandler = (event) => {
|
|
2295
|
+
this.keyPressedWithEvent(event);
|
|
2296
|
+
};
|
|
2297
|
+
|
|
2298
|
+
this.node.addEventListener('keydown', this._keypressHandler);
|
|
2299
|
+
this._keyboardShortcutsEnabled = YES;
|
|
2300
|
+
},
|
|
2301
|
+
|
|
2302
|
+
/**
|
|
2303
|
+
* Disables keyboard shortcut handling. This detaches the relevant event handlers from this view's `node`.
|
|
2304
|
+
* The node's `tabIndex` property will not be cleared by this method.
|
|
2305
|
+
*/
|
|
2306
|
+
_disableKeyboardShortcuts() {
|
|
2307
|
+
this.node.removeEventListener('keydown', this._keypressHandler);
|
|
2308
|
+
this._keyboardShortcutsEnabled = NO;
|
|
2309
|
+
},
|
|
2310
|
+
|
|
2311
|
+
// #endregion
|
|
2312
|
+
|
|
2111
2313
|
// #region Constraints and Cassowary Variables
|
|
2112
2314
|
|
|
2113
2315
|
|