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.
- package/CHANGELOG.md +1624 -0
- package/LICENSE +21 -0
- package/README.md +145 -0
- package/build/@types/index.d.ts +14078 -0
- package/build/BMCodeEditor/BMCodeEditor.js +707 -0
- package/build/BMCollectionView/BMCollectionView.js +5861 -0
- package/build/BMCollectionView/BMCollectionViewCell.js +688 -0
- package/build/BMCollectionView/BMCollectionViewFlowLayout.js +4467 -0
- package/build/BMCollectionView/BMCollectionViewLayout.js +830 -0
- package/build/BMCollectionView/BMCollectionViewLayoutAttributes.js +673 -0
- package/build/BMCollectionView/BMCollectionViewMasonryLayout.js +491 -0
- package/build/BMCollectionView/BMCollectionViewStackLayout.js +634 -0
- package/build/BMCollectionView/BMCollectionViewTileLayout.js +1121 -0
- package/build/BMCoreUI.css +2833 -0
- package/build/BMView/BMAttributedLabelView.js +310 -0
- package/build/BMView/BMLayoutConstraint_v2.5.js +1813 -0
- package/build/BMView/BMLayoutGuide.js +192 -0
- package/build/BMView/BMLayoutSizeClass.js +466 -0
- package/build/BMView/BMMenu.js +574 -0
- package/build/BMView/BMScrollView.js +247 -0
- package/build/BMView/BMTextField.js +512 -0
- package/build/BMView/BMTextFieldDelegate.js +71 -0
- package/build/BMView/BMView_v2.5.js +3572 -0
- package/build/BMView/BMViewport.js +221 -0
- package/build/BMViewLayoutEditor/BMLayoutEditor.js +6117 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorConstraintSettings.js +417 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorDelegate.js +59 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettingCells.js +1653 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettings.js +1480 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettingsComplexCells.js +431 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorSettingsDelegate.js +46 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorVariablesController.js +460 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorViewGroupSettings.js +293 -0
- package/build/BMViewLayoutEditor/BMLayoutEditorViewSettings.js +382 -0
- package/build/BMViewLayoutEditor/BMLayoutVariableProvider.js +202 -0
- package/build/BMWindow/BMConfirmationPopup.js +477 -0
- package/build/BMWindow/BMKeyboardShortcut.js +151 -0
- package/build/BMWindow/BMPopover/BMPopover.js +492 -0
- package/build/BMWindow/BMToolWindow.js +71 -0
- package/build/BMWindow/BMWindow.js +2000 -0
- package/build/Core/BMAnimationContext.js +1181 -0
- package/build/Core/BMColor.js +991 -0
- package/build/Core/BMCoreUI.js +470 -0
- package/build/Core/BMFunctionCollection.js +110 -0
- package/build/Core/BMIndexPath.js +165 -0
- package/build/Core/BMInset.js +138 -0
- package/build/Core/BMKeyPath.js +100 -0
- package/build/Core/BMPoint.js +291 -0
- package/build/Core/BMRect.js +556 -0
- package/build/Core/BMSize.js +137 -0
- package/build/iScroll/LICENSE +22 -0
- package/build/iScroll/iscroll-probe.js +2154 -0
- package/build/images/AlignBottom.png +0 -0
- package/build/images/AlignCenterX.png +0 -0
- package/build/images/AlignCenterY.png +0 -0
- package/build/images/AlignLeading.png +0 -0
- package/build/images/AlignTop.png +0 -0
- package/build/images/AlignTrailing.png +0 -0
- package/build/images/AllConstraints.png +0 -0
- package/build/images/BottomConstraint.png +0 -0
- package/build/images/CenterXConstraint.png +0 -0
- package/build/images/CenterYConstraint.png +0 -0
- package/build/images/CoreUI2.png +0 -0
- package/build/images/CoreUI2@2x.png +0 -0
- package/build/images/Desktop.png +0 -0
- package/build/images/DesktopMini.png +0 -0
- package/build/images/EqualHeight.png +0 -0
- package/build/images/EqualHorizontalSpacing.png +0 -0
- package/build/images/EqualHorizontalSpacingInSuperview.png +0 -0
- package/build/images/EqualVerticalSpacing.png +0 -0
- package/build/images/EqualVerticalSpacingInSuperview.png +0 -0
- package/build/images/EqualWidth.png +0 -0
- package/build/images/HeightConstraint.png +0 -0
- package/build/images/InactiveConstraints.png +0 -0
- package/build/images/Layout.png +0 -0
- package/build/images/LayoutVariables.png +0 -0
- package/build/images/LeftConstraint.png +0 -0
- package/build/images/OwnConstraints.png +0 -0
- package/build/images/Phone.png +0 -0
- package/build/images/PhoneLandscape.png +0 -0
- package/build/images/PhoneLandscapeMini.png +0 -0
- package/build/images/PhoneMini.png +0 -0
- package/build/images/PhonePortrait.png +0 -0
- package/build/images/PhonePortraitMini.png +0 -0
- package/build/images/Properties.png +0 -0
- package/build/images/RightConstraint.png +0 -0
- package/build/images/SubviewConstraints.png +0 -0
- package/build/images/Tablet.png +0 -0
- package/build/images/TabletLandscape.png +0 -0
- package/build/images/TabletLandscapeMini.png +0 -0
- package/build/images/TabletMini.png +0 -0
- package/build/images/TabletPortrait.png +0 -0
- package/build/images/TabletPortraitMini.png +0 -0
- package/build/images/TopConstraint.png +0 -0
- package/build/images/WidthConstraint.png +0 -0
- package/build/index.js +40 -0
- package/lib/@types/BMCoreUI.min.d.ts +14078 -0
- package/lib/BMCoreUI.min.js +1 -0
- package/package.json +58 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { NO } from "../Core/BMCoreUI";
|
|
2
|
+
|
|
3
|
+
// @ts-check
|
|
4
|
+
|
|
5
|
+
// @type BMKeyboardShortcutModifier
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Contains constants representing the modifier keys that can be used when registering keyboard shortcuts
|
|
9
|
+
* for views and windows.
|
|
10
|
+
*/
|
|
11
|
+
export const BMKeyboardShortcutModifier = Object.freeze({ // <enum>
|
|
12
|
+
/**
|
|
13
|
+
* Represents the command key on macOS and iOS, windows key on Windows and meta key on Linux.
|
|
14
|
+
*/
|
|
15
|
+
Command: {key: 'metaKey', value: 1}, // <enum>
|
|
16
|
+
/**
|
|
17
|
+
* Represents the option key on macOS and alt on other systems.
|
|
18
|
+
*/
|
|
19
|
+
Option: {key: 'altKey', value: 2}, // <enum>
|
|
20
|
+
/**
|
|
21
|
+
* Represents the shit key.
|
|
22
|
+
*/
|
|
23
|
+
Shift: {key: 'shiftKey', value: 4}, // <enum>
|
|
24
|
+
/**
|
|
25
|
+
* Represents the control key.
|
|
26
|
+
*/
|
|
27
|
+
Control: {key: 'ctrlKey', value: 8}, // <enum>
|
|
28
|
+
/**
|
|
29
|
+
* Represents the command key on macOS and iOS and control key on other systems.
|
|
30
|
+
*/
|
|
31
|
+
System: {key: 'systemKey', value: (navigator.platform.startsWith('Mac') || /iPhone|iPad|iPod/.test(navigator.platform)) ? 1 : 8}, // <enum>
|
|
32
|
+
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// @endtype
|
|
36
|
+
|
|
37
|
+
// @type BMKeyboardShortcut
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A keyboard shortcut object describes the keys that must be pressed for a keyboard shortcut to trigger as
|
|
41
|
+
* well as what should happen when that shortcut is triggered.
|
|
42
|
+
*/
|
|
43
|
+
export function BMKeyboardShortcut() {} // <constructor>
|
|
44
|
+
|
|
45
|
+
BMKeyboardShortcut.prototype = {
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The key that should be pressed for this keyboard shortcut.
|
|
49
|
+
*/
|
|
50
|
+
_keyCode: undefined, // <String>
|
|
51
|
+
get keyCode() {
|
|
52
|
+
return this._key;
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* An optional array of keyboard modifiers that must be pressed
|
|
57
|
+
* together with the key in order to trigger the shortcut.
|
|
58
|
+
*/
|
|
59
|
+
_modifiers: undefined, // <[BMKeyboardShortcutModifier]>
|
|
60
|
+
get modifiers() {
|
|
61
|
+
if (this._modifiers) return this._modifiers.slice();
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* A bitmap describing the modifiers that should be active.
|
|
66
|
+
*/
|
|
67
|
+
_modifierBitmap: 0, // <Number>
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* The object which will handle this keyboard shortcut.
|
|
71
|
+
*/
|
|
72
|
+
_target: undefined, // <AnyObject>
|
|
73
|
+
get target() {
|
|
74
|
+
return this._target;
|
|
75
|
+
},
|
|
76
|
+
set target(target) {
|
|
77
|
+
this._target = target;
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The name of the method that will be invoked on the target object when this keyboard
|
|
82
|
+
* shortcut is triggered. This method will receive the keyboard event that triggered
|
|
83
|
+
* this action as its single parameter.
|
|
84
|
+
*/
|
|
85
|
+
_action: undefined, // <String>
|
|
86
|
+
get action() {
|
|
87
|
+
return this._action;
|
|
88
|
+
},
|
|
89
|
+
set action(action) {
|
|
90
|
+
this._action = action;
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* When set to `YES`, the default action of the event that triggers this keyboard shortcut will be prevented.
|
|
95
|
+
*/
|
|
96
|
+
preventsDefault: NO, // <Boolean>
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Initializes this keyboard shortcut with the given key and optional modifiers, as well as
|
|
100
|
+
* the target and action that will handle it.
|
|
101
|
+
*
|
|
102
|
+
* In order to be triggered, this keyboard shortcut must be registered with an event handler such as a window
|
|
103
|
+
* that can receive keyboard focus and keypress events.
|
|
104
|
+
* @param key <String> The key that should be pressed for this keyboard shortcut. This maps to the `code` property of keyboard events.
|
|
105
|
+
* {
|
|
106
|
+
* @param modifiers <[BMKeyboardShortcutModifier], nullable> Defaults to an empty array. An optional array of keyboard modifiers that must be active.
|
|
107
|
+
* for the keyboard shortcut to fire.
|
|
108
|
+
* @param target <AnyObject> The object that will handle this keyboard shortcut action.
|
|
109
|
+
* @param action <String> The name of a method on the target object that will be invoked when this
|
|
110
|
+
* keyboard shortcut is triggered. That method will receive the keyboard event as its single parameter.
|
|
111
|
+
* @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.
|
|
112
|
+
* }
|
|
113
|
+
* @return <BMKeyboardShortcut> This keyboard shortcut.
|
|
114
|
+
*/
|
|
115
|
+
initWithKeyCode(key, {modifiers, target, action, preventsDefault} = {modifiers: []}) {
|
|
116
|
+
this._key = key;
|
|
117
|
+
this._modifiers = modifiers || [];
|
|
118
|
+
this._target = target;
|
|
119
|
+
this._action = action;
|
|
120
|
+
this.preventsDefault = preventsDefault || NO;
|
|
121
|
+
|
|
122
|
+
for (const modifier of this._modifiers) {
|
|
123
|
+
this._modifierBitmap = this._modifierBitmap | modifier.value;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Constructs and returns a keyboard shortcut with the given key and optional modifiers, as well as
|
|
133
|
+
* the target and action that will handle it.
|
|
134
|
+
*
|
|
135
|
+
* In order to be triggered, this keyboard shortcut must be registered with an event handler such as a window
|
|
136
|
+
* that can receive keyboard focus and keypress events.
|
|
137
|
+
* @param key <String> The key that should be pressed for this keyboard shortcut. This maps to the `code` property of keyboard events.
|
|
138
|
+
* {
|
|
139
|
+
* @param modifiers <[BMKeyboardShortcutModifier], nullable> Defaults to an empty array. An optional array of keyboard modifiers that must be active.
|
|
140
|
+
* for the keyboard shortcut to fire.
|
|
141
|
+
* @param target <AnyObject> The object that will handle this keyboard shortcut action.
|
|
142
|
+
* @param action <String> The name of a method on the target object that will be invoked when this
|
|
143
|
+
* keyboard shortcut is triggered. That method will receive the keyboard event as its single parameter.
|
|
144
|
+
* }
|
|
145
|
+
* @return <BMKeyboardShortcut> A keyboard shortcut.
|
|
146
|
+
*/
|
|
147
|
+
BMKeyboardShortcut.keyboardShortcutWithKeyCode = function (key, args) {
|
|
148
|
+
return (new BMKeyboardShortcut).initWithKeyCode(key, args);
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// @endtype
|
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import { BMExtend, NO, YES, BMCopyProperties, BMNumberByConstrainingNumberToBounds, BMUUIDMake } from "../../Core/BMCoreUI";
|
|
4
|
+
import { BMWindow } from "../BMWindow";
|
|
5
|
+
import { BMRectMakeWithOrigin, BMRectMakeWithNodeFrame, BMRectMake } from "../../Core/BMRect";
|
|
6
|
+
import { BMPointMake } from "../../Core/BMPoint";
|
|
7
|
+
import { BMHook, __BMVelocityAnimate } from "../../Core/BMAnimationContext";
|
|
8
|
+
import { BMView } from "../../BMView/BMView_v2.5";
|
|
9
|
+
import { BMInsetMakeWithEqualInsets } from "../../Core/BMInset";
|
|
10
|
+
|
|
11
|
+
// @type BMPopover extends BMWindow
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The popover is a kind of window that is visually linked to a source element.
|
|
15
|
+
* Unlike windows, popovers are always modal.
|
|
16
|
+
*
|
|
17
|
+
* Additionally, unlike with windows, subviews should not be directly added to the popover.
|
|
18
|
+
* Instead, they should be added to the popover's `contentView` property.
|
|
19
|
+
*/
|
|
20
|
+
function BMPopover() {} // <constructor>
|
|
21
|
+
|
|
22
|
+
BMPopover.prototype = BMExtend(Object.create(BMWindow.prototype), {
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The point from which this popover should originate, relative to the document. Either this property
|
|
26
|
+
* or `anchorNode` or `anchorRect` must be set before this popover is displayed.
|
|
27
|
+
*/
|
|
28
|
+
_anchorPoint: undefined, // <BMPoint, nullable>
|
|
29
|
+
|
|
30
|
+
get anchorPoint() {
|
|
31
|
+
return this._anchorPoint;
|
|
32
|
+
},
|
|
33
|
+
set anchorPoint(point) {
|
|
34
|
+
this._anchorPoint = point;
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The rect from which this popover should originate, relative to the document. Either this property
|
|
40
|
+
* or `anchorPoint` or `anchorNode` must be set before this popover is displayed.
|
|
41
|
+
*/
|
|
42
|
+
_anchorRect: undefined, // <BMRect, nullable>
|
|
43
|
+
|
|
44
|
+
get anchorRect() {
|
|
45
|
+
return this._anchorRect;
|
|
46
|
+
},
|
|
47
|
+
set anchorRect(rect) {
|
|
48
|
+
this._anchorRect = rect;
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The element from which this popover should originate. Either this property
|
|
54
|
+
* or `anchorPoint` or `anchorRect` must be set before this popover is displayed.
|
|
55
|
+
*/
|
|
56
|
+
_anchorNode: undefined, // <DOMNode, nullable>
|
|
57
|
+
|
|
58
|
+
get anchorNode() {
|
|
59
|
+
return this._anchorNode;
|
|
60
|
+
},
|
|
61
|
+
set anchorNode(node) {
|
|
62
|
+
this._anchorNode = node;
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* This popover's size. This property must be set prior to the
|
|
67
|
+
* popover being displayed.
|
|
68
|
+
*/
|
|
69
|
+
_size: undefined, // <BMSize>
|
|
70
|
+
get size() {
|
|
71
|
+
return this._size;
|
|
72
|
+
},
|
|
73
|
+
set size(size) {
|
|
74
|
+
this._size = size.copy();
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The size of the indicator. This property should be set prior to the
|
|
79
|
+
* popover being displayed.
|
|
80
|
+
*/
|
|
81
|
+
_indicatorSize: 16, // <Number>
|
|
82
|
+
get indicatorSize() {
|
|
83
|
+
return this._indicatorSize;
|
|
84
|
+
},
|
|
85
|
+
set indicatorSize(size) {
|
|
86
|
+
this._indicatorSize = size;
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Controls how rounded the popover's borders should be. This property should be set prior to the
|
|
91
|
+
* popover being displayed.
|
|
92
|
+
*/
|
|
93
|
+
_borderRadius: 4, // <Number>
|
|
94
|
+
get borderRadius() {
|
|
95
|
+
return this._borderRadius;
|
|
96
|
+
},
|
|
97
|
+
set borderRadius(radius) {
|
|
98
|
+
this._borderRadius = radius;
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Controls the spacing that this popover will maintain towards the edges of the viewport
|
|
103
|
+
* in cases where the popover would move outside the visible area in order to maintain its
|
|
104
|
+
* regular position.
|
|
105
|
+
*/
|
|
106
|
+
_edgeInsets: undefined, // <BMInset>
|
|
107
|
+
get edgeInsets() {
|
|
108
|
+
return this._edgeInsets;
|
|
109
|
+
},
|
|
110
|
+
set edgeInsets(margin) {
|
|
111
|
+
this._edgeInsets = margin || BMInsetMakeWithEqualInsets(8);
|
|
112
|
+
|
|
113
|
+
if (this.isVisible) {
|
|
114
|
+
// If this is updated while the popover is visible, update its position accordingly
|
|
115
|
+
// TODO: Only update when this would actually change the position
|
|
116
|
+
this._updatePosition();
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The drop shadow container.
|
|
122
|
+
*/
|
|
123
|
+
_dropShadowContainer: undefined, // <DOMNode>
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The drop shadow content.
|
|
127
|
+
*/
|
|
128
|
+
_dropShadowContent: undefined, // <DOMNode>
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The background node.
|
|
132
|
+
*/
|
|
133
|
+
_background: undefined, // <DOMNode>
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The dark mode fill node.
|
|
137
|
+
*/
|
|
138
|
+
_darkModeFill: undefined, // <DOMNode>
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The view to which subviews should be added.
|
|
142
|
+
*/
|
|
143
|
+
_contentView: undefined, // <BMView>
|
|
144
|
+
get contentView() {
|
|
145
|
+
return this._contentView;
|
|
146
|
+
},
|
|
147
|
+
set contentView(view) {
|
|
148
|
+
this._contentView = view;
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* A SVG object that defines the clip path that must be used on
|
|
153
|
+
* browsers that don't suport the `clip-path: path(...)` CSS values.
|
|
154
|
+
* This property will be `undefined` for all other browsers.
|
|
155
|
+
*/
|
|
156
|
+
_clipPathSVG: undefined, // <DOMNode, nullable>
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* When `_clipPathSVG` is defined, this represents the background path element.
|
|
160
|
+
*/
|
|
161
|
+
_clipPathBackgroundPath: undefined, // <DOMNode, nullable>
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* When `_clipPathSVG` is defined, this represents the outline path element.
|
|
165
|
+
*/
|
|
166
|
+
_clipPathOutlinePath: undefined, // <DOMNode, nullable>
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* A string that forms part of the HTML IDs that will be assigned to the clip paths
|
|
170
|
+
* to be used on browsers.
|
|
171
|
+
* This property will be `undefined` for all other browsers.
|
|
172
|
+
*/
|
|
173
|
+
_clipPathUUID: undefined, // <String, nullable>
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The content view's top edge constraint.
|
|
177
|
+
*/
|
|
178
|
+
_contentViewTopConstraint: undefined, // <BMLayoutConstraint>
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* The content view's bottom edge constraint.
|
|
182
|
+
*/
|
|
183
|
+
_contentViewBottomConstraint: undefined, // <BMLayoutConstraint>
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The computed height of the indicator.
|
|
187
|
+
*/
|
|
188
|
+
get _indicatorHeight() { // <Number>
|
|
189
|
+
const indicatorWidth = this.indicatorSize * Math.SQRT2;
|
|
190
|
+
return indicatorWidth / 2 | 0;
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Designated initializer. Initializes this popover with the given size.
|
|
195
|
+
*
|
|
196
|
+
* Note that the size will also contain the indicator size, so the actual usable size will be smaller,
|
|
197
|
+
* depending on how large the indicator is.
|
|
198
|
+
* @param size <BMSize> The popover's size.
|
|
199
|
+
* @return <BMPopover> This popover.
|
|
200
|
+
*/
|
|
201
|
+
initWithSize(size) {
|
|
202
|
+
const preliminaryFrame = BMRectMakeWithOrigin(BMPointMake(), {size});
|
|
203
|
+
BMWindow.prototype.initWithFrame.call(this, preliminaryFrame, {modal: YES, toolbar: NO});
|
|
204
|
+
|
|
205
|
+
this._edgeInsets = BMInsetMakeWithEqualInsets(8);
|
|
206
|
+
|
|
207
|
+
this._size = size.copy();
|
|
208
|
+
|
|
209
|
+
// Set up the popover container
|
|
210
|
+
this.node.className = 'BMPopover';
|
|
211
|
+
this.contentNode.className = 'BMPopoverContainer BMPopoverContainerView';
|
|
212
|
+
|
|
213
|
+
this._overlay.node.className = 'BMPopoverOverlay';
|
|
214
|
+
|
|
215
|
+
// A second layer is used to draw the drop shadow, due to the unusual shape of the popover window
|
|
216
|
+
const popoverDropShadowContainer = document.createElement('div');
|
|
217
|
+
popoverDropShadowContainer.className = 'BMPopoverContainerLayer BMPopoverDropShadowContainer';
|
|
218
|
+
const popoverDropShadowContent = document.createElement('div');
|
|
219
|
+
popoverDropShadowContent.className = 'BMPopoverDropShadowContent';
|
|
220
|
+
popoverDropShadowContainer.appendChild(popoverDropShadowContent);
|
|
221
|
+
|
|
222
|
+
// A third layer is used to draw the background
|
|
223
|
+
const popoverBackground = document.createElement('div');
|
|
224
|
+
popoverBackground.className = 'BMPopoverBackground';
|
|
225
|
+
|
|
226
|
+
// Two additional sublayers are used for dark mode and contain the outline and background color.
|
|
227
|
+
popoverBackground.innerHTML = '<div class="BMPopoverBackgroundDarkModeContainer"><div class="BMPopoverBackgroundDarkModeOutline"></div><div class="BMPopoverBackgroundDarkModeFill"></div></div>';
|
|
228
|
+
|
|
229
|
+
// Add the popover to the document
|
|
230
|
+
this.node.appendChild(popoverBackground);
|
|
231
|
+
this.node.appendChild(popoverDropShadowContainer);
|
|
232
|
+
this.node.appendChild(this.contentNode);
|
|
233
|
+
|
|
234
|
+
this._background = popoverBackground;
|
|
235
|
+
this._dropShadowContainer = popoverDropShadowContainer;
|
|
236
|
+
this._dropShadowContent = popoverDropShadowContent;
|
|
237
|
+
this._popoverDarkModeFill = this._background.querySelector('.BMPopoverBackgroundDarkModeFill');
|
|
238
|
+
|
|
239
|
+
const contentView = this._contentView = BMView.view();
|
|
240
|
+
this.addSubview(this._contentView);
|
|
241
|
+
|
|
242
|
+
contentView.leading.equalTo(this.leading).isActive = YES;
|
|
243
|
+
contentView.trailing.equalTo(this.trailing).isActive = YES;
|
|
244
|
+
this._contentViewTopConstraint = contentView.top.equalTo(this.top);
|
|
245
|
+
this._contentViewTopConstraint.isActive = YES;
|
|
246
|
+
|
|
247
|
+
this._contentViewBottomConstraint = contentView.bottom.equalTo(this.bottom);
|
|
248
|
+
this._contentViewBottomConstraint.isActive = YES;
|
|
249
|
+
|
|
250
|
+
return this;
|
|
251
|
+
},
|
|
252
|
+
|
|
253
|
+
// @override - BMWindow
|
|
254
|
+
initWithFrame(frame) {
|
|
255
|
+
return this.initWithSize(frame.size);
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Invoked by CoreUI to update this popover's position and recalculate the various paths used by it.
|
|
260
|
+
*/
|
|
261
|
+
_updatePosition() {
|
|
262
|
+
const frame = BMRectMake();
|
|
263
|
+
frame.size.height = this._size.height + this._indicatorHeight;
|
|
264
|
+
frame.size.width = this._size.width;
|
|
265
|
+
|
|
266
|
+
const nodeFrame = this.anchorRect || (this.anchorNode && BMRectMakeWithNodeFrame(this.anchorNode));
|
|
267
|
+
const location = this.anchorPoint ? this.anchorPoint.copy() : nodeFrame.center;
|
|
268
|
+
|
|
269
|
+
frame.origin.x = location.x - frame.size.width / 2 | 0;
|
|
270
|
+
|
|
271
|
+
if (frame.origin.x < this._edgeInsets.left) {
|
|
272
|
+
frame.origin.x = this._edgeInsets.left;
|
|
273
|
+
}
|
|
274
|
+
if (frame.right > window.innerWidth - this._edgeInsets.right) {
|
|
275
|
+
frame.origin.x = window.innerWidth - frame.size.width - this._edgeInsets.right;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const appearsBelow = location.y < window.innerHeight - this.size.height - this._indicatorHeight;
|
|
279
|
+
|
|
280
|
+
if (appearsBelow) {
|
|
281
|
+
frame.origin.y = this.anchorPoint ? location.y : nodeFrame.bottom - 2;
|
|
282
|
+
this._contentViewTopConstraint.constant = this._indicatorHeight;
|
|
283
|
+
this._contentViewBottomConstraint.constant = 0;
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
frame.origin.y = this.anchorPoint ? location.y - frame.size.height : nodeFrame.origin.y + 2 - frame.size.height;
|
|
287
|
+
this._contentViewTopConstraint.constant = 0;
|
|
288
|
+
this._contentViewBottomConstraint.constant = -this._indicatorHeight;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
this.frame = frame;
|
|
292
|
+
|
|
293
|
+
const innerFrame = frame.copy();
|
|
294
|
+
|
|
295
|
+
const knobPosition = BMNumberByConstrainingNumberToBounds(location.x - frame.origin.x, 12, frame.size.width - 12);
|
|
296
|
+
|
|
297
|
+
innerFrame.origin = BMPointMake();
|
|
298
|
+
|
|
299
|
+
const pathContent = `${this._pathForPopoverWithFrame(innerFrame, {widthIndicatorSize: this._indicatorSize, knobPosition, gravity: appearsBelow ? 'Top' : 'Bottom'})}`;
|
|
300
|
+
const outlinePathContent = `${this._pathForPopoverWithFrame(innerFrame, {widthIndicatorSize: this._indicatorSize, inset: 1, knobPosition, gravity: appearsBelow ? 'Top' : 'Bottom'})}`;
|
|
301
|
+
|
|
302
|
+
if (!this._clipPathUUID && !CSS.supports('clip-path', `path('${pathContent}')`)) {
|
|
303
|
+
// If inline path definitions are not supported by the browsers, create an UUID for a SVG clip path and create it
|
|
304
|
+
this._clipPathUUID = BMUUIDMake();
|
|
305
|
+
|
|
306
|
+
this._clipPathSVG = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
307
|
+
this._clipPathSVG.style.width = '0';
|
|
308
|
+
this._clipPathSVG.style.height = '0';
|
|
309
|
+
|
|
310
|
+
// Create and attach the main clip path
|
|
311
|
+
const clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
|
|
312
|
+
clipPath.setAttribute('id', 'popover-clip-path-' + this._clipPathUUID);
|
|
313
|
+
clipPath.setAttribute('clipPathUnits', 'userSpaceOnUse');
|
|
314
|
+
this._clipPathBackgroundPath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
315
|
+
clipPath.appendChild(this._clipPathBackgroundPath);
|
|
316
|
+
|
|
317
|
+
this._clipPathSVG.appendChild(clipPath);
|
|
318
|
+
|
|
319
|
+
// Create and attach the outline clip path
|
|
320
|
+
const outlineClipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
|
|
321
|
+
outlineClipPath.setAttribute('id', 'popover-outline-clip-path-' + this._clipPathUUID);
|
|
322
|
+
outlineClipPath.setAttribute('clipPathUnits', 'userSpaceOnUse');
|
|
323
|
+
this._clipPathOutlinePath = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
324
|
+
outlineClipPath.appendChild(this._clipPathOutlinePath);
|
|
325
|
+
|
|
326
|
+
this._clipPathSVG.appendChild(outlineClipPath);
|
|
327
|
+
|
|
328
|
+
document.body.appendChild(this._clipPathSVG);
|
|
329
|
+
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// For Chrome, update the path nodes
|
|
333
|
+
if (this._clipPathUUID) {
|
|
334
|
+
this._clipPathBackgroundPath.setAttribute('d', pathContent);
|
|
335
|
+
this._clipPathOutlinePath.setAttribute('d', outlinePathContent);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// For Blink/Chrome-based browsers clip-path: path() is not supported, but clip-path: url() can be used instead for that browser
|
|
339
|
+
const path = this._clipPathUUID ? `url(#popover-clip-path-${this._clipPathUUID})` : `path('${pathContent}')`;
|
|
340
|
+
const outlinePath = this._clipPathUUID ? `url(#popover--outline-clip-path-${this._clipPathUUID})` : `path('${outlinePathContent}')`;
|
|
341
|
+
|
|
342
|
+
// Assign the frame to the window, and to the drop shadow container
|
|
343
|
+
const positionStyle = {
|
|
344
|
+
left: innerFrame.origin.x + 'px',
|
|
345
|
+
top: innerFrame.origin.y + 'px',
|
|
346
|
+
width: innerFrame.size.width + 'px',
|
|
347
|
+
height: innerFrame.size.height + 'px'
|
|
348
|
+
};
|
|
349
|
+
BMCopyProperties(this.contentNode.style, positionStyle);
|
|
350
|
+
|
|
351
|
+
BMCopyProperties(this._background.style, positionStyle);
|
|
352
|
+
this._background.style.clipPath = path;
|
|
353
|
+
this._background.style.webkitClipPath = path;
|
|
354
|
+
|
|
355
|
+
const popoverDarkModeFill = this._background.querySelector('.BMPopoverBackgroundDarkModeFill');
|
|
356
|
+
popoverDarkModeFill.style.clipPath = outlinePath;
|
|
357
|
+
popoverDarkModeFill.style.webkitClipPath = outlinePath;
|
|
358
|
+
|
|
359
|
+
BMCopyProperties(this._dropShadowContainer.style, positionStyle);
|
|
360
|
+
this._dropShadowContent.style.clipPath = path;
|
|
361
|
+
this._dropShadowContent.style.webkitClipPath = path;
|
|
362
|
+
|
|
363
|
+
const popoverLayers = [this.contentNode, this._background, this._dropShadowContainer];
|
|
364
|
+
|
|
365
|
+
for (const layer of popoverLayers) {
|
|
366
|
+
layer.style.transformOrigin = ((knobPosition / this.frame.size.width) * 100) + '% ' + (appearsBelow ? '0%' : '100%');
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Builds the SVG path definition for a popover with the given frame. Note that the indicator will be positioned inside the frame, which will push the usable
|
|
372
|
+
* area of the frame downwards.
|
|
373
|
+
* @param frame <BMRect> The popover's frame.
|
|
374
|
+
* {
|
|
375
|
+
* @param widthIndicatorSize <Number, nullable> Defaults to `8`. The size of the popover's indicator.
|
|
376
|
+
* @param radius <Number, nullable> Defaults to `4`. Controls how rounded the corners are.
|
|
377
|
+
* @param inset <Number, nullable> Defaults to `0`. An optional inset to apply to the path.
|
|
378
|
+
* @param knobPosition <Number, nullable> Defaults to half of the frame's width. The position along the top frame on which to place the knob.
|
|
379
|
+
* This coordinate is relative to the popover's frame and represents the center position of the knob.
|
|
380
|
+
* This position should not overlap the specified corner radius.
|
|
381
|
+
* @param gravity <String, nullable> Defaults to `"Top"`. If set to `"Bottom"`, the popover knob will be placed at the bottom of the popover.
|
|
382
|
+
* }
|
|
383
|
+
* @return <String> The SVG path.
|
|
384
|
+
*/
|
|
385
|
+
_pathForPopoverWithFrame(frame, {widthIndicatorSize: size = 8, radius = 8, inset = 0, knobPosition = undefined, gravity = 'Top'} = {widthIndicatorSize: 8, radius: 4, inset: 0}) {
|
|
386
|
+
let top = gravity === 'Bottom' ? 0 : size * Math.SQRT2 / 2 | 0;
|
|
387
|
+
let bottom = gravity === 'Bottom' ? frame.size.height - size * Math.SQRT2 / 2 | 0 : frame.size.height;
|
|
388
|
+
const left = inset;
|
|
389
|
+
|
|
390
|
+
if (inset) {
|
|
391
|
+
size = size - inset;
|
|
392
|
+
frame = frame.copy();
|
|
393
|
+
frame.insetWithInset(BMInsetMakeWithEqualInsets(inset));
|
|
394
|
+
radius = radius + inset;
|
|
395
|
+
top += inset;
|
|
396
|
+
bottom -= inset;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
if (knobPosition === undefined) {
|
|
400
|
+
knobPosition = frame.size.width / 2 | 0;
|
|
401
|
+
}
|
|
402
|
+
else {
|
|
403
|
+
knobPosition = (knobPosition - inset) | 0;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const knobWidth = size * Math.SQRT2;
|
|
407
|
+
const knobHeight = knobWidth / 2 | 0;
|
|
408
|
+
|
|
409
|
+
// bottom should be equivalent to frame.size.height + inset
|
|
410
|
+
const pathTop = gravity === 'Bottom' ?
|
|
411
|
+
`M${left + radius},${top} L${frame.size.width + left - radius},${top} ` :
|
|
412
|
+
`M${left + radius},${top} L${(knobPosition - knobWidth / 2 + left)},${top} l${(knobWidth / 2)},${-knobHeight} l${(knobWidth / 2)},${knobHeight} L${frame.size.width + left - radius},${top} `;
|
|
413
|
+
|
|
414
|
+
const pathBottom = gravity === 'Bottom' ?
|
|
415
|
+
`Q${frame.size.width + left},${bottom} ${frame.size.width + left - radius},${bottom} L${(knobPosition + knobWidth / 2 + left)},${bottom} l${(-knobWidth / 2)},${knobHeight} l${(-knobWidth / 2)},${-knobHeight} L${radius},${bottom} ` :
|
|
416
|
+
`Q${frame.size.width + left},${bottom} ${frame.size.width + left - radius},${bottom} L${radius},${bottom} `;
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
let path = pathTop;
|
|
420
|
+
path += `Q${frame.size.width + left},${top} ${frame.size.width + left},${top + radius} L${frame.size.width + left},${bottom - radius} `;
|
|
421
|
+
path += pathBottom;
|
|
422
|
+
path += `Q${left},${bottom} ${left},${bottom - radius} L${left},${top + radius} Q${left},${top} ${radius},${top} Z`;
|
|
423
|
+
|
|
424
|
+
return path;
|
|
425
|
+
},
|
|
426
|
+
|
|
427
|
+
// @override - BMWindow
|
|
428
|
+
animateInWithCompletionHandler(completionHandler) {
|
|
429
|
+
const popoverLayers = [this.contentNode, this._background, this._dropShadowContainer];
|
|
430
|
+
|
|
431
|
+
this.node.style.opacity = 1;
|
|
432
|
+
|
|
433
|
+
let first = YES;
|
|
434
|
+
for (const layer of popoverLayers) {
|
|
435
|
+
BMHook(layer, {scaleX: .75, scaleY: .75, opacity: 0});
|
|
436
|
+
|
|
437
|
+
__BMVelocityAnimate(layer, {scaleX: 1, scaleY: 1, opacity: 1}, {duration: 300, easing: [0,1.59,.49,1], complete: first ? completionHandler : undefined}, YES);
|
|
438
|
+
first = NO;
|
|
439
|
+
}
|
|
440
|
+
},
|
|
441
|
+
|
|
442
|
+
// @override - BMWindow
|
|
443
|
+
animateOutWithCompletionHandler(completionHandler) {
|
|
444
|
+
const popoverLayers = [this.contentNode, this._background, this._dropShadowContainer];
|
|
445
|
+
|
|
446
|
+
const self = this;
|
|
447
|
+
|
|
448
|
+
let first = YES;
|
|
449
|
+
for (const layer of popoverLayers) {
|
|
450
|
+
layer.style.pointerEvents = 'none';
|
|
451
|
+
|
|
452
|
+
const runCompletionHandler = first;
|
|
453
|
+
__BMVelocityAnimate(layer, {scaleX: .9, scaleY: .9, opacity: 0}, {duration: 200, easing: 'easeInOutQuart', complete() {
|
|
454
|
+
if (runCompletionHandler) {
|
|
455
|
+
self.node.style.opacity = 0;
|
|
456
|
+
completionHandler();
|
|
457
|
+
}
|
|
458
|
+
}}, YES);
|
|
459
|
+
first = NO;
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
// @override - BMWindow
|
|
464
|
+
bringToFrontAnimated(animated, args) {
|
|
465
|
+
if (!this.anchorNode && !this.anchorPoint && !this.anchorRect) throw new Error('The anchorPoint, anchorRect or anchorNode must be set prior to showing this popover.');
|
|
466
|
+
|
|
467
|
+
this._updatePosition();
|
|
468
|
+
|
|
469
|
+
BMWindow.prototype.bringToFrontAnimated.apply(this, arguments);
|
|
470
|
+
},
|
|
471
|
+
|
|
472
|
+
// @override - BMWindow
|
|
473
|
+
release() {
|
|
474
|
+
if (this._clipPathUUID) {
|
|
475
|
+
this._clipPathSVG.remove();
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return BMWindow.prototype.release.apply(this, arguments);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* Constructs and returns a popover with the given size.
|
|
485
|
+
* @param size <BMSize> The popover's size.
|
|
486
|
+
* @return <BMPopover> A popover.
|
|
487
|
+
*/
|
|
488
|
+
BMPopover.popoverWithSize = function (size) {
|
|
489
|
+
return (new BMPopover).initWithSize(size);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// @endtype
|