bm-core-ui 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +1624 -0
  2. package/LICENSE +21 -0
  3. package/README.md +145 -0
  4. package/build/@types/index.d.ts +14078 -0
  5. package/build/BMCodeEditor/BMCodeEditor.js +707 -0
  6. package/build/BMCollectionView/BMCollectionView.js +5861 -0
  7. package/build/BMCollectionView/BMCollectionViewCell.js +688 -0
  8. package/build/BMCollectionView/BMCollectionViewFlowLayout.js +4467 -0
  9. package/build/BMCollectionView/BMCollectionViewLayout.js +830 -0
  10. package/build/BMCollectionView/BMCollectionViewLayoutAttributes.js +673 -0
  11. package/build/BMCollectionView/BMCollectionViewMasonryLayout.js +491 -0
  12. package/build/BMCollectionView/BMCollectionViewStackLayout.js +634 -0
  13. package/build/BMCollectionView/BMCollectionViewTileLayout.js +1121 -0
  14. package/build/BMCoreUI.css +2833 -0
  15. package/build/BMView/BMAttributedLabelView.js +310 -0
  16. package/build/BMView/BMLayoutConstraint_v2.5.js +1813 -0
  17. package/build/BMView/BMLayoutGuide.js +192 -0
  18. package/build/BMView/BMLayoutSizeClass.js +466 -0
  19. package/build/BMView/BMMenu.js +574 -0
  20. package/build/BMView/BMScrollView.js +247 -0
  21. package/build/BMView/BMTextField.js +512 -0
  22. package/build/BMView/BMTextFieldDelegate.js +71 -0
  23. package/build/BMView/BMView_v2.5.js +3572 -0
  24. package/build/BMView/BMViewport.js +221 -0
  25. package/build/BMViewLayoutEditor/BMLayoutEditor.js +6117 -0
  26. package/build/BMViewLayoutEditor/BMLayoutEditorConstraintSettings.js +417 -0
  27. package/build/BMViewLayoutEditor/BMLayoutEditorDelegate.js +59 -0
  28. package/build/BMViewLayoutEditor/BMLayoutEditorSettingCells.js +1653 -0
  29. package/build/BMViewLayoutEditor/BMLayoutEditorSettings.js +1480 -0
  30. package/build/BMViewLayoutEditor/BMLayoutEditorSettingsComplexCells.js +431 -0
  31. package/build/BMViewLayoutEditor/BMLayoutEditorSettingsDelegate.js +46 -0
  32. package/build/BMViewLayoutEditor/BMLayoutEditorVariablesController.js +460 -0
  33. package/build/BMViewLayoutEditor/BMLayoutEditorViewGroupSettings.js +293 -0
  34. package/build/BMViewLayoutEditor/BMLayoutEditorViewSettings.js +382 -0
  35. package/build/BMViewLayoutEditor/BMLayoutVariableProvider.js +202 -0
  36. package/build/BMWindow/BMConfirmationPopup.js +477 -0
  37. package/build/BMWindow/BMKeyboardShortcut.js +151 -0
  38. package/build/BMWindow/BMPopover/BMPopover.js +492 -0
  39. package/build/BMWindow/BMToolWindow.js +71 -0
  40. package/build/BMWindow/BMWindow.js +2000 -0
  41. package/build/Core/BMAnimationContext.js +1181 -0
  42. package/build/Core/BMColor.js +991 -0
  43. package/build/Core/BMCoreUI.js +470 -0
  44. package/build/Core/BMFunctionCollection.js +110 -0
  45. package/build/Core/BMIndexPath.js +165 -0
  46. package/build/Core/BMInset.js +138 -0
  47. package/build/Core/BMKeyPath.js +100 -0
  48. package/build/Core/BMPoint.js +291 -0
  49. package/build/Core/BMRect.js +556 -0
  50. package/build/Core/BMSize.js +137 -0
  51. package/build/iScroll/LICENSE +22 -0
  52. package/build/iScroll/iscroll-probe.js +2154 -0
  53. package/build/images/AlignBottom.png +0 -0
  54. package/build/images/AlignCenterX.png +0 -0
  55. package/build/images/AlignCenterY.png +0 -0
  56. package/build/images/AlignLeading.png +0 -0
  57. package/build/images/AlignTop.png +0 -0
  58. package/build/images/AlignTrailing.png +0 -0
  59. package/build/images/AllConstraints.png +0 -0
  60. package/build/images/BottomConstraint.png +0 -0
  61. package/build/images/CenterXConstraint.png +0 -0
  62. package/build/images/CenterYConstraint.png +0 -0
  63. package/build/images/CoreUI2.png +0 -0
  64. package/build/images/CoreUI2@2x.png +0 -0
  65. package/build/images/Desktop.png +0 -0
  66. package/build/images/DesktopMini.png +0 -0
  67. package/build/images/EqualHeight.png +0 -0
  68. package/build/images/EqualHorizontalSpacing.png +0 -0
  69. package/build/images/EqualHorizontalSpacingInSuperview.png +0 -0
  70. package/build/images/EqualVerticalSpacing.png +0 -0
  71. package/build/images/EqualVerticalSpacingInSuperview.png +0 -0
  72. package/build/images/EqualWidth.png +0 -0
  73. package/build/images/HeightConstraint.png +0 -0
  74. package/build/images/InactiveConstraints.png +0 -0
  75. package/build/images/Layout.png +0 -0
  76. package/build/images/LayoutVariables.png +0 -0
  77. package/build/images/LeftConstraint.png +0 -0
  78. package/build/images/OwnConstraints.png +0 -0
  79. package/build/images/Phone.png +0 -0
  80. package/build/images/PhoneLandscape.png +0 -0
  81. package/build/images/PhoneLandscapeMini.png +0 -0
  82. package/build/images/PhoneMini.png +0 -0
  83. package/build/images/PhonePortrait.png +0 -0
  84. package/build/images/PhonePortraitMini.png +0 -0
  85. package/build/images/Properties.png +0 -0
  86. package/build/images/RightConstraint.png +0 -0
  87. package/build/images/SubviewConstraints.png +0 -0
  88. package/build/images/Tablet.png +0 -0
  89. package/build/images/TabletLandscape.png +0 -0
  90. package/build/images/TabletLandscapeMini.png +0 -0
  91. package/build/images/TabletMini.png +0 -0
  92. package/build/images/TabletPortrait.png +0 -0
  93. package/build/images/TabletPortraitMini.png +0 -0
  94. package/build/images/TopConstraint.png +0 -0
  95. package/build/images/WidthConstraint.png +0 -0
  96. package/build/index.js +40 -0
  97. package/lib/@types/BMCoreUI.min.d.ts +14078 -0
  98. package/lib/BMCoreUI.min.js +1 -0
  99. package/package.json +58 -0
@@ -0,0 +1,1480 @@
1
+ // @ts-check
2
+
3
+ import {YES, NO, BMExtend} from '../Core/BMCoreUI'
4
+ import {BMPointMake} from '../Core/BMPoint'
5
+ import {BMSizeMake} from '../Core/BMSize'
6
+ import {BMIndexPathMakeWithRow} from '../Core/BMIndexPath'
7
+ import {__BMVelocityAnimate, BMAnimationContextGetCurrent, BMHook, BMAnimationBeginWithDuration, BMAnimationApply, BMAnimationContextBeginStatic} from '../Core/BMAnimationContext'
8
+ import {BMView} from '../BMView/BMView_v2.5'
9
+ import {BMCollectionViewFlowLayoutSupplementaryView, BMCollectionViewFlowLayoutGravity, BMCollectionViewFlowLayoutAlignment} from '../BMCollectionView/BMCollectionViewFlowLayout'
10
+ import {BMCollectionView} from '../BMCollectionView/BMCollectionView'
11
+ import { BMLayoutEditorSettingsCell, BMLayoutEditorSettingsConstraintCell, BMLayoutEditorSettingsFooter, BMLayoutEditorSettingsTitleCell, BMLayoutEditorSettingsIntegerCell, BMLayoutEditorSettingsReadonlyCell, BMLayoutEditorSettingsDeactivateConstraintsCell, BMLayoutEditorSettingsSegmentCell, BMLayoutEditorSettingsBooleanCell, BMLayoutEditorSettingsStringCell, BMLayoutEditorSettingsNumberCell, BMLayoutEditorSettingsViewCell, BMLayoutEditorSettingsDropdownCell, BMLayoutEditorSettingsConstantCell, BMLayoutEditorSettingsDeleteConstraintCell } from './BMLayoutEditorSettingCells'
12
+ import { _BMLayoutEditorViewLayoutSettingsTab, _BMLayoutEditorViewSettingsPanel } from './BMLayoutEditorViewSettings'
13
+ import { _BMLayoutEditorConstraintSettingsPanel } from './BMLayoutEditorConstraintSettings'
14
+ import { _BMLayoutEditorViewGroupSettingsPanel } from './BMLayoutEditorViewGroupSettings'
15
+ import { BMCollectionViewCell } from '../BMCollectionView/BMCollectionViewCell'
16
+ import { BMLayoutEditorSettingsInsetCell, BMLayoutEditorSettingsSizeCell } from './BMLayoutEditorSettingsComplexCells'
17
+
18
+ /**
19
+ * Returns the URL to the given image based on whether CoreUI is running within thingworx or standalone.
20
+ * @path <String> The image path.
21
+ * @return <String> The appropriate URL.
22
+ */
23
+ export function _BMURLOfImageAtPath(path) {
24
+ return (('TW' in window) ? '/Thingworx/Common/extensions/BMCoreUI/ui/BMCoreUI/' : '') + path;
25
+ }
26
+
27
+ // @type _BMLayoutEditorSettingsView
28
+
29
+ /**
30
+ * The layout editor settings view manages the settings sidebar of the layout editor. It is responsible for creating the various
31
+ * settings panels that display the actual settings and maintaining a back stack used for navigation when selecting between the
32
+ * various options displayed by those panels.
33
+ */
34
+ export function _BMLayoutEditorSettingsView() {} // <constructor>
35
+
36
+ _BMLayoutEditorSettingsView.prototype = BMExtend(Object.create(BMView.prototype), {
37
+ constructor: _BMLayoutEditorSettingsView,
38
+
39
+ /**
40
+ * The layout editor managing this view.
41
+ */
42
+ _editor: undefined, // <BMLayoutEditor>
43
+
44
+ /**
45
+ * An array of settings panels that are currently in the setting pane's backstack.
46
+ */
47
+ _panels: undefined, // <[_BMLayoutEditorSettingsPanel]>
48
+
49
+ /**
50
+ * When this settings view is displayed in a tool window, this property contains a reference
51
+ * to that window.
52
+ */
53
+ _window: undefined, // <BMWindow, nullable>
54
+
55
+ /**
56
+ * Set to `YES` after the user dismisses the settings window via the close button.
57
+ * After it is set to `YES`, the window will no longer open after selecting a view.
58
+ */
59
+ _dismissedByUser: NO, // <Boolean>
60
+
61
+ get window() {
62
+ return this._window;
63
+ },
64
+
65
+ /**
66
+ * The currently displayed settings panel.
67
+ */
68
+ get currentPanel() { // <_BMLayoutEditorSettingsPanel, nullable>
69
+ return this._panels[this._panels.length - 1];
70
+ },
71
+
72
+ /**
73
+ * Initializes this settings view with the given DOM node.
74
+ * @param node <DOMNode> The DOM node to be used by the settings view.
75
+ * {
76
+ * @param editor <BMLayoutEditor> The layout editor managing this view.
77
+ * }
78
+ * @return <_BMLayoutEditorSettingsView> This settings view.
79
+ */
80
+ initWithDOMNode(node, {forEditor: editor}) {
81
+ BMView.prototype.initWithDOMNode.call(this, node);
82
+
83
+ this.node.style.overflow = 'hidden';
84
+ this._editor = editor;
85
+ this._panels = [];
86
+
87
+ return this;
88
+ },
89
+
90
+ // @override - BMWindowDelegate
91
+ async windowWillAppear(window) {
92
+ if (this._panels.length && this._panels[this._panels.length - 1]._awaitsLayout) {
93
+ await 0;
94
+ const panel = this._panels[this._panels.length - 1];
95
+ panel.view.needsLayout = YES;
96
+ panel.view.layoutIfNeeded();
97
+ panel.settingsPanelDidLayoutView();
98
+ panel._awaitsLayout = NO;
99
+ }
100
+ },
101
+
102
+ /**
103
+ * Prepares the given settings panel for display.
104
+ * @param panel <_BMLayoutEditorSettingsPanel> The panel to display.
105
+ * {
106
+ * @param withBackButton <Boolean, nullable> Controls whether this panel will contain a back button.
107
+ * }
108
+ */
109
+ _provisionPanel(panel, {withBackButton: backButton} = {withBackButton: NO}) {
110
+ panel._settingsView = this;
111
+
112
+ // Create a container that will contain both the panel's content and its title view
113
+ const container = BMView.view();
114
+ this.addSubview(container);
115
+ container.leading.equalTo(this.leading).isActive = YES;
116
+ container.trailing.equalTo(this.trailing).isActive = YES;
117
+ container.top.equalTo(this.top).isActive = YES;
118
+ container.bottom.equalTo(this.bottom).isActive = YES;
119
+ panel._container = container;
120
+ container.node.classList.add('BMLayoutEditorDetails');
121
+ container.node.style.cssText = 'border: none !important;'
122
+ container.node.style.overflow = 'hidden';
123
+
124
+ // Create a view that will contain the panel's content
125
+ const view = BMView.view();
126
+ container.addSubview(view);
127
+
128
+ view.leading.equalTo(container.leading).isActive = YES;
129
+ view.trailing.equalTo(container.trailing).isActive = YES;
130
+ view.bottom.equalTo(container.bottom).isActive = YES;
131
+
132
+ panel._view = view;
133
+
134
+ // Create a title view for the panel
135
+ const titleView = BMView.view();
136
+ titleView.node.innerText = (backButton ? '‹ ' : '') + panel.title || '';
137
+ titleView.node.className = 'BMWindowTitle BMLayoutEditorDetailsTitle';
138
+ let titleViewLeading = 64;
139
+ if (backButton) {
140
+ titleView.node.classList.add('BMLayoutEditorDetailsItemReadonlyValueClickable');
141
+ titleView.node.classList.add('BMLayoutEditorDetailsBackButton');
142
+ titleViewLeading -= 8;
143
+ }
144
+ titleView.node.style.zIndex = 1;
145
+ titleView.supportsAutomaticIntrinsicSize = YES;
146
+ container.addSubview(titleView);
147
+
148
+ titleView.leading.equalTo(container.leading, {plus: titleViewLeading}).isActive = YES;
149
+ titleView.trailing.lessThanOrEqualTo(container.trailing, {plus: -16}).isActive = YES;
150
+ titleView.height.greaterThanOrEqualTo(32).isActive = YES;
151
+ titleView.top.equalTo(container.top, {plus: 12}).isActive = YES;
152
+ panel._titleView = titleView;
153
+
154
+ view.top.equalTo(titleView.bottom, {plus: 12}).isActive = YES;
155
+
156
+ if (backButton) {
157
+ titleView.node.addEventListener('click', e => this.popPanel());
158
+ }
159
+
160
+ // Instruct the panel to set up its contents
161
+ panel.settingsPanelDidLoad();
162
+
163
+ if (this._window && !this._window._visible) {
164
+ panel._awaitsLayout = YES;
165
+ }
166
+ else {
167
+ // Perform layout
168
+ this.layoutIfNeeded();
169
+ panel.settingsPanelDidLayoutView();
170
+ }
171
+ },
172
+
173
+ /**
174
+ * Selects the given view, creating a settings panel for it and pushing it onto the settings stack.
175
+ * @param view <BMView> The view to select. This must be part of the layout hierarchy managed by this
176
+ * settings view's layout editor.
177
+ */
178
+ selectView(view) {
179
+ // Prevent the settings view from reacting to this selection
180
+ this._ignoresSelection = YES;
181
+ this._editor.selectView(view);
182
+
183
+ // Create and push a panel for this view
184
+ const panel = (new _BMLayoutEditorViewSettingsPanel).initWithSettingsView(this, {forView: view});
185
+ this._provisionPanel(panel, {withBackButton: YES});
186
+ this.pushPanel(panel);
187
+
188
+ // Restore the usual selection behaviour
189
+ this._ignoresSelection = NO;
190
+ },
191
+
192
+ /**
193
+ * Invoked upon the layout editor selecting a view. Clears the current settings stack, then creates a settings
194
+ * panel for the selected view and pushes it onto the stack.
195
+ * @param view <BMView> The view that was selected.
196
+ */
197
+ layoutEditorDidSelectView(view) {
198
+ if (this._ignoresSelection) return;
199
+
200
+ let animated = YES;
201
+
202
+ if (this._window && !this._window._visible) {
203
+ animated = NO;
204
+ }
205
+
206
+ const panel = (new _BMLayoutEditorViewSettingsPanel).initWithSettingsView(this, {forView: view});
207
+ this._provisionPanel(panel);
208
+
209
+ this.resetToPanel(panel, {animated});
210
+ },
211
+
212
+ /**
213
+ * Invoked upon the layout editor selecting a goup views. Clears the current settings stack, then creates a settings
214
+ * panel for multiple views and pushes it onto the stack.
215
+ * @param views <[BMView]> The views that were selected.
216
+ */
217
+ layoutEditorDidSelectViews(views) {
218
+ if (this._panels[0] instanceof _BMLayoutEditorViewGroupSettingsPanel) {
219
+ this._panels[0].views = views;
220
+ return;
221
+ }
222
+ if (this._ignoresSelection) return;
223
+
224
+ let animated = YES;
225
+
226
+ if (this._window && !this._window._visible) {
227
+ animated = NO;
228
+ }
229
+
230
+ const panel = (new _BMLayoutEditorViewGroupSettingsPanel).initWithSettingsView(this, {forViews: views});
231
+ this._provisionPanel(panel);
232
+
233
+ this.resetToPanel(panel, {animated});
234
+ },
235
+
236
+ /**
237
+ * Selects the given constraint with the given view as a reference, creating a settings panel for it and pushing it
238
+ * onto the settings stack.
239
+ * @param constraint <BMLayoutConstraint> The constraint to select. This must be part of the layout hierarchy managed by
240
+ * this setting view's layout editor.
241
+ * {
242
+ * @param withReferenceView <BMView> The view from which the constraint was selected.
243
+ * }
244
+ */
245
+ selectConstraint(constraint, {withReferenceView: view}) {
246
+ // Prevent the settings view from reacting to this selection
247
+ this._ignoresSelection = YES;
248
+ this._editor.selectConstraint(constraint, {withReferenceView: view});
249
+
250
+ // Create and push a panel for this view
251
+ const panel = (new _BMLayoutEditorConstraintSettingsPanel).initWithSettingsView(this, {forConstraint: constraint, withReferenceView: view});
252
+ this._provisionPanel(panel, {withBackButton: YES});
253
+ this.pushPanel(panel);
254
+
255
+ // Restore the usual selection behaviour
256
+ this._ignoresSelection = NO;
257
+ },
258
+
259
+ /**
260
+ * Invoked upon the layout editor selecting a constraint. Clears the current settings stack, then creates a settings
261
+ * panel for the selected constraint and pushes it onto the stack.
262
+ * @param constrant <BMLayoutConstraint> The constraint that was selected.
263
+ * {
264
+ * @param withReferenceView <BMView> The view from which the constraint was selected.
265
+ * }
266
+ */
267
+ layoutEditorDidSelectConstraint(constraint, {withReferenceView: view}) {
268
+ if (this._ignoresSelection) return;
269
+
270
+ let animated = YES;
271
+
272
+ if (this._window && !this._window._visible) {
273
+ animated = NO;
274
+ }
275
+
276
+ const panel = (new _BMLayoutEditorConstraintSettingsPanel).initWithSettingsView(this, {forConstraint: constraint, withReferenceView: view});
277
+ this._provisionPanel(panel, {withBackButton: YES});
278
+
279
+ this.pushPanel(panel, {animated});
280
+ },
281
+
282
+ /**
283
+ * Invoked upon the layout editor changing the currently selected layout size class.
284
+ * This method in turns forwards this message to the settings panels currently in the back stack,
285
+ * allowing them to customize their options for this size class change.
286
+ * @param sizeClass <BMLayoutSizeClass> The size class that was selected by the layout editor.
287
+ */
288
+ layoutEditorDidSelectSizeClass(sizeClass) {
289
+ for (const panel of this._panels) {
290
+ panel.layoutEditorDidSelectSizeClass(sizeClass);
291
+ }
292
+ },
293
+
294
+ /**
295
+ * Clears the back stack, then pushes the given panel onto the stack, making it visible. This change will be animated.
296
+ * @param panel <_BMLayoutEditorSettingsPanel, nullable> The panel to display. If `undefined`, the settings view will be cleared.
297
+ * {
298
+ * @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated.
299
+ * }
300
+ * @return <Promise<void>> A promise that resolves when this operation completes.
301
+ */
302
+ async resetToPanel(panel, {animated = YES} = {animated: YES}) {
303
+ // Unload everything except the current panel
304
+ const currentPanel = this.currentPanel;
305
+
306
+ for (const panel of this._panels) {
307
+ if (panel != currentPanel) {
308
+ panel.settingsPanelWillUnload();
309
+ panel._container.release();
310
+ }
311
+ }
312
+
313
+ // If there is no current panel, just display the given panel immediately
314
+ if (!currentPanel) {
315
+ if (panel) {
316
+ this._panels.push(panel);
317
+ this._ignoresSelection = YES;
318
+ panel.settingsPanelWillAppear(NO);
319
+ this._ignoresSelection = NO;
320
+ panel.settingsPanelDidAppear(NO);
321
+ }
322
+ return;
323
+ }
324
+
325
+ this._panels = panel ? [panel] : [];
326
+
327
+ if (currentPanel) {
328
+ // Animate the outgoing panel, if it exists
329
+ currentPanel.settingsPanelWillDisappear(YES);
330
+ if (animated) BMAnimationBeginWithDuration(200, {easing: 'easeInOutQuad'});
331
+
332
+ //const frame = currentPanel._container.frame.copy();
333
+ //frame.offsetWithX(0, {y: 128});
334
+ //currentPanel._container.frame = frame;
335
+ currentPanel._container.opacity = 0;
336
+ if (animated) {
337
+ const controller = BMAnimationContextGetCurrent().controllerForObject(currentPanel._container, {node: currentPanel._container.node});
338
+ controller.registerBuiltInPropertiesWithDictionary({scaleX: .85, scaleY: .85});
339
+ }
340
+ else {
341
+ BMHook(currentPanel._container.node, {scaleX: .85, scaleY: .85});
342
+ }
343
+ }
344
+ else {
345
+ if (panel && animated) BMAnimationBeginWithDuration(200, {easing: 'easeInOutQuad'});
346
+ }
347
+
348
+ if (panel) {
349
+ // Prepare the incoming panel for the animation
350
+ BMAnimationContextBeginStatic();
351
+ this._ignoresSelection = YES;
352
+ panel.settingsPanelWillAppear(YES);
353
+ this._ignoresSelection = NO;
354
+ /*const initialFrame = panel._container.frame.copy();
355
+ const animationFrame = initialFrame.copy();
356
+ animationFrame.offsetWithX(0, {y: 256});
357
+ panel._container.frame = animationFrame;*/
358
+ panel._container.opacity = 0;
359
+
360
+ BMHook(panel._container.node, {scaleX: 1.23, scaleY: 1.23});
361
+ if (animated) BMAnimationApply();
362
+
363
+ // Animate the incoming panel
364
+ if (animated) {
365
+ const controller = BMAnimationContextGetCurrent().controllerForObject(panel._container, {node: panel._container.node});
366
+ controller.registerBuiltInPropertiesWithDictionary({scaleX: 1, scaleY: 1});
367
+ }
368
+ else {
369
+ BMHook(panel._container.node, {scaleX: 1, scaleY: 1});
370
+ }
371
+ //panel._container.frame = initialFrame;
372
+ panel._container.opacity = 1;
373
+ }
374
+
375
+ if ((panel || currentPanel) && animated) await BMAnimationApply();
376
+
377
+ if (currentPanel) {
378
+ // Perform any cleanup needed after the animation
379
+ currentPanel.settingsPanelDidDisappear(YES);
380
+ currentPanel.settingsPanelWillUnload();
381
+ currentPanel._container.release();
382
+ }
383
+
384
+ if (panel) panel.settingsPanelDidAppear(YES);
385
+ },
386
+
387
+ /**
388
+ * Pushes the given panel onto the back stack, making it visible. This change will be animated.
389
+ * @param panel <_BMLayoutEditorSettingsPanel> The panel to push.
390
+ * {
391
+ * @param animated <Boolean, nullable> Defaults to `YES`. If set to `YES`, this change will be animated.
392
+ * }
393
+ * @return <Promise<void>> A promise that resolves when this operation completes.
394
+ */
395
+ async pushPanel(panel, {animated = YES} = {animated: YES}) {
396
+ const currentPanel = this.currentPanel;
397
+ this._panels.push(panel);
398
+
399
+ if (currentPanel) {
400
+ // Animate the outgoing panel, if it exists
401
+ if (animated) BMAnimationBeginWithDuration(200, {easing: 'easeInOutQuad'});
402
+ currentPanel.settingsPanelWillDisappear(YES);
403
+
404
+ const frame = currentPanel._container.frame.copy();
405
+ frame.offsetWithX(-frame.width / 2 | 0, {y: 0});
406
+ currentPanel._container.frame = frame;
407
+ currentPanel._container.opacity = 0;
408
+ }
409
+ else {
410
+ if (animated) BMAnimationBeginWithDuration(200, {easing: 'easeInOutQuad'});
411
+ }
412
+
413
+ // Prepare the incoming panel for the animation
414
+ BMAnimationContextBeginStatic();
415
+ const initialFrame = panel._container.frame.copy();
416
+ const animationFrame = initialFrame.copy();
417
+ animationFrame.offsetWithX(initialFrame.width, {y: 0});
418
+ panel._container.frame = animationFrame;
419
+ BMAnimationApply();
420
+ this._ignoresSelection = YES;
421
+ panel.settingsPanelWillAppear(YES);
422
+ this._ignoresSelection = NO;
423
+
424
+ // Animate the incoming panel
425
+ panel._container.frame = initialFrame;
426
+
427
+ if (animated) await BMAnimationApply();
428
+
429
+ // Perform any cleanup needed after the animation
430
+ currentPanel.settingsPanelDidDisappear(YES);
431
+ currentPanel._container.isVisible = NO;
432
+
433
+ panel.settingsPanelDidAppear(YES);
434
+ },
435
+
436
+ /**
437
+ * Pops the topmost settings panel from the back stack, revealing the previous one. This change will be animated.
438
+ * @return <Promise<void>> A promise that resolves when this operation completes.
439
+ */
440
+ async popPanel() {
441
+ const currentPanel = this.currentPanel;
442
+ currentPanel._container.node.style.pointerEvents = 'none';
443
+ this._panels.pop();
444
+
445
+ // Animate the outgoing panel
446
+ BMAnimationBeginWithDuration(200, {easing: 'easeInOutQuad'});
447
+ currentPanel.settingsPanelWillDisappear(YES);
448
+
449
+ const frame = this.frame.copy();
450
+ frame.origin = BMPointMake();
451
+ frame.offsetWithX(frame.width , {y: 0});
452
+ currentPanel._container.frame = frame;
453
+
454
+ const previousPanel = this.currentPanel;
455
+
456
+ if (previousPanel) {
457
+ // Prepare the incoming panel for the animationƒ
458
+ BMAnimationContextBeginStatic();
459
+ const initialFrame = this.frame.copy();
460
+ initialFrame.origin = BMPointMake();
461
+ const animationFrame = initialFrame.copy();
462
+ animationFrame.offsetWithX(-frame.width / 2 | 0, {y: 0});
463
+ previousPanel._container.frame = animationFrame;
464
+ previousPanel._container.opacity = 0;
465
+ previousPanel._container.isVisible = YES;
466
+ BMAnimationApply();
467
+ this._ignoresSelection = YES;
468
+ previousPanel.settingsPanelWillAppear(YES);
469
+ this._ignoresSelection = NO;
470
+
471
+ // Animate the incoming panel
472
+ previousPanel._container.frame = initialFrame;
473
+ previousPanel._container.opacity = 1;
474
+
475
+ if (previousPanel._awaitsLayout) {
476
+ BMAnimationContextBeginStatic();
477
+ previousPanel._titleView.invalidateIntrinsicSize();
478
+ previousPanel.view.needsLayout = YES;
479
+ previousPanel.view.layoutIfNeeded();
480
+ previousPanel.settingsPanelDidLayoutView();
481
+ previousPanel._awaitsLayout = NO;
482
+ BMAnimationApply();
483
+ }
484
+ }
485
+
486
+ await BMAnimationApply();
487
+
488
+ // Perform any cleanup needed after the animation
489
+ currentPanel.settingsPanelDidDisappear(YES);
490
+ currentPanel.settingsPanelWillUnload();
491
+ currentPanel._container.release();
492
+
493
+ if (previousPanel) previousPanel.settingsPanelDidAppear(YES);
494
+ }
495
+ });
496
+
497
+ /**
498
+ * Constructs and initializes a settings view for the given node.
499
+ * @param node <DOMNode> The DOM node to be used by the settings view.
500
+ * {
501
+ * @param editor <BMLayoutEditor> The layout editor managing this view.
502
+ * }
503
+ * @return <_BMLayoutEditorSettingsView> A settings view.
504
+ */
505
+ _BMLayoutEditorSettingsView.settingsViewWithNode = function (node, {forEditor: editor}) {
506
+ return (new _BMLayoutEditorSettingsView).initWithDOMNode(node, {forEditor: editor});
507
+ }
508
+
509
+ // @endtype
510
+
511
+ // @type _BMLayoutEditorSettingsPanel
512
+
513
+ /**
514
+ * A settings panel controls the settings that are available on the settings view.
515
+ * A different subclass of the settings panel is typically used depending on the selected item.
516
+ */
517
+ export function _BMLayoutEditorSettingsPanel() {} // <constructor>
518
+
519
+ _BMLayoutEditorSettingsPanel.prototype = {
520
+ constructor: _BMLayoutEditorSettingsPanel,
521
+
522
+ /**
523
+ * The settings view to which this panel belongs.
524
+ */
525
+ _settingsView: undefined, // <_BMLayoutEditorSettingsView>
526
+
527
+ /**
528
+ * This panel's title.
529
+ */
530
+ _title: undefined, // <String>
531
+
532
+ get title() {
533
+ return this._title;
534
+ },
535
+ set title(title) {
536
+ this._title = title;
537
+ if (this._titleView) {
538
+ this._titleView.node.innerText = title;
539
+ this._titleView.invalidateIntrinsicSize();
540
+ }
541
+ },
542
+
543
+ /**
544
+ * The view representing this panel.
545
+ */
546
+ _container: undefined, // <BMView, nullable>
547
+
548
+ /**
549
+ * The title view associated to this panel.
550
+ */
551
+ _titleView: undefined, // <BMView, nullable>
552
+
553
+ /**
554
+ * The view to which this panel's content should be added.
555
+ */
556
+ _view: undefined, // <BMView, nullable>
557
+
558
+ get view() {
559
+ return this._view;
560
+ },
561
+
562
+ /**
563
+ * The layout editor to which this panel belongs.
564
+ */
565
+ get layoutEditor() { // <BMLayoutEditor>
566
+ return this._settingsView._editor;
567
+ },
568
+
569
+ /**
570
+ * Designated initializer. Initializes this settings panel with the given settings view.
571
+ * @param view <_BMLayoutEditorSettingsView> The settings view.
572
+ * @return <_BMLayoutEditorSettingsPanel> This setttings panel.
573
+ */
574
+ initWithSettingsView(view) {
575
+ this._settingsView = view;
576
+
577
+ return this;
578
+ },
579
+
580
+ /**
581
+ * Invoked whenever this panel is about to be added to the DOM.
582
+ * When this method is invoked, a new view has been created from this panel
583
+ * and should be used to add new subviews to it.
584
+ * The default implementation does nothing.
585
+ */
586
+ settingsPanelDidLoad() {
587
+
588
+ },
589
+
590
+ /**
591
+ * Invoked after the view for settings panel has finished a layout operation.
592
+ */
593
+ settingsPanelDidLayoutView() {
594
+
595
+ },
596
+
597
+ /**
598
+ * Invoked prior to this settings panel becoming visible, before any animation begins.
599
+ * At this point, the `view` property will return a view to which this panel's subviews can be added.
600
+ * The default implementation does nothing.
601
+ * @param animated <Boolean> Set to `YES` if this change is animated. If this parameter is `YES`, this
602
+ * method will be invoked from within an animation block.
603
+ */
604
+ settingsPanelWillAppear(animated) {
605
+
606
+ },
607
+
608
+ /**
609
+ * Invoked after this settings panel has become visible, after any animation has finished running.
610
+ * The default implementation does nothing.
611
+ * @param animated <Boolean> Set to `YES` if this change was animated.
612
+ */
613
+ settingsPanelDidAppear(animated) {
614
+
615
+ },
616
+
617
+ /**
618
+ * Invoked before this settings panel will disappear, before any associated animation starts.
619
+ * The default implementation does nothing.
620
+ * @param animated <Boolean> Set to `YES` if this change is animated. If this parameter is `YES`, this
621
+ * method will be invoked from within an animation block.
622
+ */
623
+ settingsPanelWillDisappear(animated) {
624
+
625
+ },
626
+
627
+ /**
628
+ * Invoked after this settings panel has disappeared, after any associated animation has finished running.
629
+ * After this method returns, this panel's `view` may be removed from its hierarchy.
630
+ * The default implementation does nothing.
631
+ * @param animated <Boolean> Set to `YES` if this change was animated.
632
+ */
633
+ settingsPanelDidDisappear(animated) {
634
+
635
+ },
636
+
637
+ /**
638
+ * Invoked when the content managed by this settings panel is about to be removed from the DOM.
639
+ * After this method returns, this panel's `view` will be removed from its view hierarchy.
640
+ * The default implementation does nothing.
641
+ */
642
+ settingsPanelWillUnload() {
643
+
644
+ }
645
+ }
646
+
647
+ // @endtype
648
+
649
+ // @type _BMLayoutEditorCollectionSettingsPanel
650
+
651
+ export function _BMLayoutEditorCollectionSettingsPanel() {} // <constructor>
652
+
653
+ /**
654
+ * A subclass of settings panel that manages a list of settings tabs, associating a collection
655
+ * view of settings with each of the tabs.
656
+ */
657
+ _BMLayoutEditorCollectionSettingsPanel.prototype = BMExtend(Object.create(_BMLayoutEditorSettingsPanel.prototype), {
658
+
659
+
660
+ /**
661
+ * An array of settings tab controlling the settings
662
+ * available for this panel.
663
+ */
664
+ _tabs: undefined, // <[BMLayoutEditorSettingsTab]>
665
+
666
+ /**
667
+ * The currently visible tab.
668
+ */
669
+ _currentTab: undefined, // <BMLayoutEditorSettingsTab>
670
+
671
+ /**
672
+ * Configures the layout settings of the given collection view.
673
+ * @param collectionView <BMCollectionView> The collection view.
674
+ * {
675
+ * @param forTab <BMLayoutEditorSettingsTab> The tab for which to configure this collection view.
676
+ * }
677
+ */
678
+ _configureCollectionViewLayout(collectionView, {forTab: tab}) {
679
+ collectionView.node.style.position = 'absolute';
680
+
681
+ // Configure the layout
682
+ // Automatic cell size is only used on the constraints tab
683
+ if (tab instanceof _BMLayoutEditorViewLayoutSettingsTab) {
684
+ collectionView.layout.expectedCellSize = BMSizeMake(384, 48);
685
+ }
686
+ else {
687
+ collectionView.layout.expectedCellSize = BMSizeMake(384, 32);
688
+ }
689
+ collectionView.layout.maximumCellsPerRow = 1;
690
+ collectionView.layout.gravity = BMCollectionViewFlowLayoutGravity.Expand;
691
+ collectionView.layout.rowSpacing = 0;
692
+ collectionView.layout.contentGravity = BMCollectionViewFlowLayoutAlignment.Start;
693
+ collectionView.layout.showsFooters = YES;
694
+ collectionView.layout.footerHeight = 17;
695
+ collectionView.layout.topPadding = 8;
696
+
697
+ collectionView.identityComparator = (setting1, setting2) => {
698
+ if (!setting1 && !setting2) return YES;
699
+ if (!setting1 || !setting2) return NO;
700
+ return setting1.isEqualToSetting(setting2);
701
+ }
702
+
703
+ // Register the classes for the default cell types
704
+ for (const key in BMLayoutEditorSettingKind) {
705
+ collectionView.registerCellClass(BMLayoutEditorSettingsCell, {forReuseIdentifier: BMLayoutEditorSettingKind[key]});
706
+ }
707
+ collectionView.registerCellClass(BMLayoutEditorSettingsConstraintCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Constraint});
708
+ collectionView.registerCellClass(BMLayoutEditorSettingsTitleCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Title});
709
+ collectionView.registerCellClass(BMLayoutEditorSettingsIntegerCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Integer});
710
+ collectionView.registerCellClass(BMLayoutEditorSettingsNumberCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Number});
711
+ collectionView.registerCellClass(BMLayoutEditorSettingsConstantCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Constant});
712
+ collectionView.registerCellClass(BMLayoutEditorSettingsStringCell, {forReuseIdentifier: BMLayoutEditorSettingKind.String});
713
+ collectionView.registerCellClass(BMLayoutEditorSettingsBooleanCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Boolean});
714
+ collectionView.registerCellClass(BMLayoutEditorSettingsReadonlyCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Info});
715
+ collectionView.registerCellClass(BMLayoutEditorSettingsDeleteConstraintCell, {forReuseIdentifier: BMLayoutEditorSettingKind.DeleteConstraintButton})
716
+ collectionView.registerCellClass(BMLayoutEditorSettingsDeactivateConstraintsCell, {forReuseIdentifier: BMLayoutEditorSettingKind.DeactivateConstraintsButton});
717
+ collectionView.registerCellClass(BMLayoutEditorSettingsSegmentCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Segment});
718
+ collectionView.registerCellClass(BMLayoutEditorSettingsViewCell, {forReuseIdentifier: BMLayoutEditorSettingKind.View});
719
+ collectionView.registerCellClass(BMLayoutEditorSettingsDropdownCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Enum});
720
+ collectionView.registerCellClass(BMLayoutEditorSettingsInsetCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Insets});
721
+ collectionView.registerCellClass(BMLayoutEditorSettingsSizeCell, {forReuseIdentifier: BMLayoutEditorSettingKind.Size});
722
+ collectionView.registerSupplementaryViewClass(BMLayoutEditorSettingsFooter, {forReuseIdentifier: BMCollectionViewFlowLayoutSupplementaryView.Footer});
723
+ collectionView.registerSupplementaryViewClass(BMLayoutEditorSettingsFooter, {forReuseIdentifier: BMCollectionViewFlowLayoutSupplementaryView.Empty});
724
+
725
+ // Register the custom cell classes
726
+ for (const key in this.layoutEditor._settingCellClasses) {
727
+ collectionView.registerCellClass(this.layoutEditor._settingCellClasses[key], {forReuseIdentifier: key});
728
+ }
729
+ },
730
+
731
+ // @override - BMLayoutEditorSettingsPanel
732
+ settingsPanelDidLoad() {
733
+ // Create the topmost tab selector
734
+ const tabView = BMView.view();
735
+ tabView.CSSClass = 'BMLayoutEditorDetailsTabView';
736
+ this.view.addSubview(tabView);
737
+
738
+ // Set up the constraints for the tab view
739
+ tabView.height.equalTo(48).isActive = YES;
740
+ tabView.leading.equalTo(this.view.leading).isActive = YES;
741
+ tabView.trailing.equalTo(this.view.trailing).isActive = YES;
742
+ tabView.top.equalTo(this.view.top).isActive = YES;
743
+
744
+ // Create the tab host, to which individual tabs will be added
745
+ const tabHost = BMView.view();
746
+ this.view.addSubview(tabHost);
747
+
748
+ // Set up the constraints for the tab host
749
+ tabHost.top.equalTo(tabView.bottom).isActive = YES;
750
+ tabHost.leading.equalTo(this.view.leading).isActive = YES;
751
+ tabHost.trailing.equalTo(this.view.trailing).isActive = YES;
752
+ tabHost.bottom.equalTo(this.view.bottom).isActive = YES;
753
+
754
+ for (const tab of this._tabs) {
755
+ // Create a collection view for each tab that will display its contents
756
+ const collectionView = BMCollectionView.collectionView();
757
+
758
+ collectionView.allowsOffscreenLayout = NO;
759
+
760
+ tabHost.addSubview(collectionView);
761
+
762
+ this._configureCollectionViewLayout(collectionView, {forTab: tab});
763
+
764
+ collectionView.leading.equalTo(tabHost.leading).isActive = true;
765
+ collectionView.trailing.equalTo(tabHost.trailing).isActive = true;
766
+ collectionView.top.equalTo(tabHost.top).isActive = true;
767
+ collectionView.bottom.equalTo(tabHost.bottom).isActive = true;
768
+
769
+ tab._collectionView = collectionView;
770
+ collectionView._settingsTab = tab;
771
+
772
+ // Default to showing the current tab, hiding all other content
773
+ if (tab != this._currentTab) {
774
+ collectionView.isVisible = NO;
775
+ }
776
+
777
+ // Create the clickable tabs
778
+ const tabNode = document.createElement('img');
779
+ tabNode.className = 'BMLayoutEditorDetailsTab';
780
+ tabNode.src = tab.icon;
781
+ tabView.contentNode.appendChild(tabNode);
782
+
783
+ // Default to showing the current tab
784
+ if (tab == this._currentTab) {
785
+ tabNode.classList.add('BMLayoutEditorDetailsTabSelected');
786
+ }
787
+
788
+ tab._tabNode = tabNode;
789
+
790
+ // Switch between the tabs on click
791
+ tabNode.addEventListener('click', event => {
792
+ if (tabNode == this._currentTab) return;
793
+ // Deselect the current tab
794
+ this._currentTab._collectionView.isVisible = NO;
795
+ this._currentTab._tabNode.classList.remove('BMLayoutEditorDetailsTabSelected');
796
+ // Then select the new one
797
+ this._currentTab = tab;
798
+ collectionView.isVisible = YES;
799
+ tabNode.classList.add('BMLayoutEditorDetailsTabSelected');
800
+ });
801
+ }
802
+ },
803
+
804
+ // @override - BMLayoutEditorSettingsPanel
805
+ settingsPanelDidLayoutView() {
806
+ for (const tab of this._tabs) {
807
+ tab.collectionView = tab._collectionView;
808
+ }
809
+ },
810
+
811
+ // @override - BMLayoutEditorSettingsPanel
812
+ settingsPanelWillUnload() {
813
+ for (const tab of this._tabs) {
814
+ tab._collectionView.release();
815
+ tab._collectionView = undefined;
816
+ }
817
+ },
818
+
819
+ // @override - BMLayoutEditorSettingsPanel
820
+ layoutEditorDidSelectSizeClass(sizeClass) {
821
+ for (const tab of this._tabs) {
822
+ tab.layoutEditorDidSelectSizeClass(sizeClass);
823
+ }
824
+ }
825
+ });
826
+
827
+ // @endtype
828
+
829
+ // @type BMLayoutEditorSettingsTab implements BMCollectionViewDataSet, BMCollectionViewDelegate
830
+
831
+ /**
832
+ * A settings tab represents a page of settings for the layout editor.
833
+ */
834
+ export function BMLayoutEditorSettingsTab() {} // <constructor>
835
+
836
+ BMLayoutEditorSettingsTab.prototype = {
837
+
838
+ /**
839
+ * The name of the tab.
840
+ */
841
+ name: undefined, // <String>
842
+
843
+ /**
844
+ * The URL pointing to an icon that represents this tab.
845
+ */
846
+ icon: undefined, // <String>
847
+
848
+ /**
849
+ * The constraint to which this tab applies.
850
+ */
851
+ _constraint: undefined, // <BMLayoutConstraint, nullable>
852
+ get constraint() {
853
+ return this._constraint;
854
+ },
855
+
856
+ /**
857
+ * The view to which this tab applies.
858
+ */
859
+ _view: undefined, // <BMView, nullable>
860
+ get view() {
861
+ return this._view;
862
+ },
863
+
864
+ /**
865
+ * An array of sections making up this settings tab.
866
+ */
867
+ _settingSections: undefined, // <[BMLayoutEditorSettingsSection]>
868
+
869
+ /**
870
+ * The array of setting sections making up this settings tab.
871
+ * This also includes settings created for variations.
872
+ */
873
+ _compiledSettingSections: undefined, // <[BMLayoutEditorSettingsSection]>
874
+
875
+ /**
876
+ * The collection view managed by this settings tab, which displays
877
+ * the actual content.
878
+ */
879
+ _collectionView: undefined, // <BMCollectionView, nullable>
880
+
881
+ get collectionView() {
882
+ return this._collectionView;
883
+ },
884
+ set collectionView(view) {
885
+ this._compiledSettingSections.length = 0;
886
+
887
+ // Compile the settings when first being assigned a collection view, as the layout editor
888
+ // will not have been available the first time `commitUpdates` was invoked
889
+ for (const section of this._settingSections) {
890
+ this._compiledSettingSections.push(section._compiledSectionForSizeClass(this.layoutEditor.activeSizeClass));
891
+ }
892
+
893
+ this._collectionView = view;
894
+ view.dataSet = this;
895
+ view.delegate = this;
896
+ },
897
+
898
+ /**
899
+ * The settings panel to which this tab was added.
900
+ */
901
+ _settingsPanel: undefined, // <_BMLayoutEditorSettingsPanel, nullable>
902
+
903
+ /**
904
+ * The layout editor to which this tab was added.
905
+ */
906
+ get layoutEditor() { // <BMLayoutEditor, nullable>
907
+ if (this._settingsPanel) {
908
+ return this._settingsPanel.layoutEditor;
909
+ }
910
+ },
911
+
912
+ /**
913
+ * Initializes this tab with the given name and icon.
914
+ * @param name <String> The name of the tab.
915
+ * {
916
+ * @param icon <String> The URL of the icon representing this tab.
917
+ * }
918
+ * @return <BMLayoutEditorSettingsTab> This settings tab.
919
+ */
920
+ initWithName(name, {icon}) {
921
+ this.name = name;
922
+ this.icon = icon;
923
+ this._settingSections = [];
924
+ this._compiledSettingSections = [];
925
+
926
+ return this;
927
+ },
928
+
929
+ /**
930
+ * An identifier unique to each update settings request.
931
+ */
932
+ _updateSettingsID: 0, // <Number>
933
+
934
+ /**
935
+ * Should be invoked to cause this tab to reload its settings whenever a setting is added or removed.
936
+ */
937
+ async updateSettings() {
938
+ this._updateSettingsID = this._updateSettingsID + 1;
939
+ const ID = this._updateSettingsID;
940
+
941
+ // Await for any current data update
942
+ while (this._collectionView && this._collectionView.isUpdatingData) {
943
+ await this._collectionView._dataUpdatePromise;
944
+ }
945
+
946
+ // If a new update request arrives before this one gets a chance to execute, omit this update
947
+ if (this._updateSettingsID != ID) return;
948
+
949
+ this.beginUpdates();
950
+ this._settingsPanel._updateSettingsForTab(this);
951
+ this.commitUpdates();
952
+ },
953
+
954
+ // @override - BMCollectionViewDataSet
955
+ numberOfSections() {
956
+ return this._compiledSettingSections.length;
957
+ },
958
+
959
+ // @override - BMCollectionViewDataSet
960
+ numberOfObjectsInSectionAtIndex(index) {
961
+ return this._compiledSettingSections[index]._settings.length + (this._compiledSettingSections[index].name ? 1 : 0);
962
+ },
963
+
964
+ // @override - BMCollectionViewDataSet
965
+ indexPathForObjectAtRow(row, {inSectionAtIndex: section}) {
966
+ if (this._compiledSettingSections[section].name) {
967
+ if (row == 0) {
968
+ return BMIndexPathMakeWithRow(row, {section, forObject: this._compiledSettingSections[section]._nameSetting});
969
+ }
970
+ return BMIndexPathMakeWithRow(row, {section, forObject: this._compiledSettingSections[section]._settings[row - 1]});
971
+ }
972
+ return BMIndexPathMakeWithRow(row, {section, forObject: this._compiledSettingSections[section]._settings[row]});
973
+ },
974
+
975
+ // @override - BMCollectionViewDataSet
976
+ indexPathForObject(object) {
977
+ for (const [sectionIndex, section] of this._compiledSettingSections.entries()) {
978
+ for (const [settingIndex, setting] of section._settings.entries()) {
979
+ if (setting == section._nameSetting) {
980
+ return BMIndexPathMakeWithRow(0, {section: sectionIndex, forObject: object});
981
+ }
982
+ if (setting.isEqualToSetting(object)) {
983
+ if (section.name) {
984
+ return BMIndexPathMakeWithRow(settingIndex + 1, {section: sectionIndex, forObject: object});
985
+ }
986
+ return BMIndexPathMakeWithRow(settingIndex, {section: sectionIndex, forObject: object});
987
+ }
988
+ }
989
+ }
990
+ },
991
+
992
+ // @override - BMCollectionViewDataSet
993
+ cellForItemAtIndexPath(indexPath) {
994
+ const cell = this._collectionView.dequeueCellForReuseIdentifier(indexPath.object.kind);
995
+ cell.node.classList.add('BMLayoutEditorDetailsCell');
996
+ cell._tab = this;
997
+ cell.setting = indexPath.object;
998
+ return cell;
999
+ },
1000
+
1001
+ // @override - BMCollectionViewDataSet
1002
+ cellForSupplementaryViewWithIdentifier(identifier, {atIndexPath: indexPath}) {
1003
+ const cell = this._collectionView.dequeueCellForSupplementaryViewWithIdentifier(identifier);
1004
+ cell._tab = this;
1005
+ cell.section = this._compiledSettingSections[indexPath.section];
1006
+ return cell;
1007
+ },
1008
+
1009
+ // @override - BMCollectionViewDataSet
1010
+ useOldData(use) {
1011
+ if (!this._oldData) {
1012
+ throw new Error('[BMLayoutEditorSettingsTab] Illegal state - Data update performed outside of a transaction.');
1013
+ }
1014
+
1015
+ this._compiledSettingSections = use ? this._oldData : this._newData;
1016
+ },
1017
+
1018
+ // @override - BMCollectionViewDataSet
1019
+ isUsingOldData() {
1020
+ if (!this._oldData) {
1021
+ throw new Error('[BMLayoutEditorSettingsTab] Illegal state - Data update performed outside of a transaction.');
1022
+ }
1023
+ return this._compiledSettingSections === this._oldData;
1024
+ },
1025
+
1026
+ /**
1027
+ * This method must be invoked whenever the settings available in this tab are modified.
1028
+ * After this method is invoked, the settings sections and their contents can be modified freely.
1029
+ *
1030
+ * After the changes have been performed, it is required to invoke the `commitUpdates()` method to
1031
+ * actually update the display.
1032
+ */
1033
+ beginUpdates() {
1034
+ if (!this._collectionView) return;
1035
+
1036
+ this._oldData = this._compiledSettingSections.map(s => BMLayoutEditorSettingsSection.sectionWithSection(s));
1037
+ this._compiledSettingSections = [];
1038
+ this._newData = this._compiledSettingSections;
1039
+ },
1040
+
1041
+ /**
1042
+ * This method must be invoked after every call to `beginUpdates()` to commit the changes that were pending.
1043
+ * @return <Promise<void>> A promise that resolves when this operation completes.
1044
+ */
1045
+ async commitUpdates() {
1046
+ if (!this.layoutEditor) return;
1047
+
1048
+ if (!this._collectionView) return;
1049
+
1050
+ for (const section of this._settingSections) {
1051
+ this._compiledSettingSections.push(section._compiledSectionForSizeClass(this.layoutEditor.activeSizeClass));
1052
+ }
1053
+
1054
+ // Await for any current data update
1055
+ while (this._collectionView.isUpdatingData) {
1056
+ await this._collectionView._dataUpdatePromise;
1057
+ }
1058
+
1059
+ const promise = this._collectionView.updateEntireDataAnimated(YES);
1060
+ this._oldData = undefined;
1061
+ this._compiledSettingSections = this._newData;
1062
+ this._newData = undefined;
1063
+ await promise;
1064
+ },
1065
+
1066
+ /**
1067
+ * Invoked upon the layout editor selecting a different size class.
1068
+ * Causes this tab to create additional settings for those settings that support variations
1069
+ * and have their `automaticallyExpandsVariations` property set to `YES`.
1070
+ * @param sizeClass <BMLayoutSizeClass, nullable> The newly selected size class, or `undefined` if `All Size Classes` was selected.
1071
+ */
1072
+ layoutEditorDidSelectSizeClass(sizeClass) {
1073
+ for (const section of this._settingSections) {
1074
+ for (const setting of section._settings) {
1075
+ if (setting.variations && setting.automaticallyExpandsVariations) {
1076
+ this.beginUpdates();
1077
+ this.commitUpdates();
1078
+ return;
1079
+ }
1080
+ }
1081
+ }
1082
+ }
1083
+ }
1084
+
1085
+ /**
1086
+ * Constructs and returns a settings tab with the given name and icon.
1087
+ * @param name <String> The name of the tab.
1088
+ * {
1089
+ * @param icon <String> The URL of the icon representing this tab.
1090
+ * }
1091
+ * @return <BMLayoutEditorSettingsTab> A settings tab.
1092
+ */
1093
+ BMLayoutEditorSettingsTab.tabWithName = function (name, {icon}) {
1094
+ return (new BMLayoutEditorSettingsTab).initWithName(name, {icon});
1095
+ }
1096
+
1097
+ // @endtype
1098
+
1099
+ // @type BMLayoutEditorSettingsSection
1100
+
1101
+ export function BMLayoutEditorSettingsSection() {} // <constructor>
1102
+
1103
+ BMLayoutEditorSettingsSection.prototype = {
1104
+ /**
1105
+ * The name of this settings section. If omitted, this section
1106
+ * will not show a name in the UI.
1107
+ */
1108
+ _name: undefined, // <String, nullable>
1109
+
1110
+ get name() {
1111
+ return this._name;
1112
+ },
1113
+ set name(name) {
1114
+ this._name = name;
1115
+ if (name) {
1116
+ this._nameSetting = BMLayoutEditorSetting.settingWithName(name, {kind: BMLayoutEditorSettingKind.Title, target: this});
1117
+ this._nameSetting._section = this;
1118
+ }
1119
+ else {
1120
+
1121
+ }
1122
+ },
1123
+
1124
+ /**
1125
+ * Controls whether this section can be collapsed and expanded.
1126
+ */
1127
+ collapsible: NO, // <Boolean>
1128
+
1129
+ /**
1130
+ * Controls whether this section is collapsed.
1131
+ */
1132
+ _collapsed: NO, // <Boolean>
1133
+
1134
+ get isCollapsed() {
1135
+ return this._collapsed;
1136
+ },
1137
+ set isCollapsed(collapsed) {
1138
+ if (this._tab) {
1139
+ if (collapsed) {
1140
+ this.collapse();
1141
+ }
1142
+ else {
1143
+ this.expand();
1144
+ }
1145
+ }
1146
+ this._collapsed = collapsed;
1147
+ },
1148
+
1149
+ /**
1150
+ * An array of settings belonging to this section. You should not directly add settings to this
1151
+ * array; instead you should assign to this property directly.
1152
+ */
1153
+ _settings: undefined, // <[BMLayoutEditorSetting]>
1154
+ get settings() {
1155
+ return this._settings.slice();
1156
+ },
1157
+ set settings(settings) {
1158
+ this._settings = settings.slice();
1159
+ },
1160
+
1161
+ /**
1162
+ * Returns a copy of this section that contains additional settings for all defined and
1163
+ * automatically expanding variations.
1164
+ * @param sizeClass <BMLayoutSizeClass> The size class for which this section should be compiled.
1165
+ * @return <BMLayoutEditorSettingsSection> A settings section.
1166
+ */
1167
+ _compiledSectionForSizeClass(sizeClass) {
1168
+ const section = BMLayoutEditorSettingsSection.section();
1169
+
1170
+ section._name = this._name;
1171
+ section._nameSetting = this._nameSetting;
1172
+ section.collapsible = section.collapsible;
1173
+ section._settings = [];
1174
+
1175
+ for (const setting of this._settings) {
1176
+ section._settings.push(setting);
1177
+ // If the setting supports variations, create additional associated settings for each defined variation
1178
+ if (setting.variations) {
1179
+ for (const key in setting.target._variations) {
1180
+ const variation = setting.target._variations[key];
1181
+ // When the setting supports automatically expanding variations, skip the current variation as a setting will be created for it regardless of whether
1182
+ // it had been previously defined or not
1183
+ if (variation.sizeClass == sizeClass && setting.automaticallyExpandsVariations) continue;
1184
+
1185
+ // If this variation contains the target property, create a setting for it
1186
+ if (setting.property in variation) {
1187
+ const variationSetting = BMLayoutEditorSetting.settingWithName(setting.name, {kind: setting.kind, target: setting.target, variations: NO, nullable: setting.nullable, property: setting.property});
1188
+ variationSetting._sizeClass = variation.sizeClass;
1189
+ variationSetting._associatedSetting = setting;
1190
+ section._settings.push(variationSetting);
1191
+ }
1192
+ }
1193
+
1194
+ // If the setting automatically expands variations, create setting for the current size class
1195
+ if (setting.automaticallyExpandsVariations && sizeClass) {
1196
+ const variationSetting = BMLayoutEditorSetting.settingWithName(setting.name, {kind: setting.kind, target: setting.target, variations: NO, nullable: YES, property: setting.property});
1197
+ variationSetting._sizeClass = sizeClass;
1198
+ variationSetting._associatedSetting = setting;
1199
+ section._settings.push(variationSetting);
1200
+ }
1201
+ }
1202
+ }
1203
+
1204
+ return section;
1205
+ }
1206
+ }
1207
+
1208
+ /**
1209
+ * Constructs and returns a settings section.
1210
+ * @return <BMLayoutEditorSettingsSection> A section.
1211
+ */
1212
+ BMLayoutEditorSettingsSection.section = function () {
1213
+ const section = new BMLayoutEditorSettingsSection;
1214
+
1215
+ section._settings = [];
1216
+
1217
+ return section;
1218
+ }
1219
+
1220
+ /**
1221
+ * Constructs and returns a section by copying the contents of the given section.
1222
+ */
1223
+ BMLayoutEditorSettingsSection.sectionWithSection = function (section) {
1224
+ const newSection = new BMLayoutEditorSettingsSection;
1225
+
1226
+ newSection.name = section.name;
1227
+ newSection.collapsible = section.collapsible;
1228
+ newSection._settings = section._settings.slice();
1229
+
1230
+ return newSection;
1231
+ }
1232
+
1233
+ // @endtype
1234
+
1235
+
1236
+ // @type BMLayoutEditorSettingKind
1237
+
1238
+ /**
1239
+ * Constants representing the various kinds of settings that can be used by layout editor settings. These constants refer to the setting
1240
+ * kinds available, but custom setting kinds can be used by specifying a custom identifier string when creating a setting and registering
1241
+ * a setting cell class with the same name. Note that underscore prefixed identifiers are reserved by CoreUI.
1242
+ */
1243
+ export var BMLayoutEditorSettingKind = Object.freeze({ // <enum>
1244
+
1245
+ /**
1246
+ * A string setting kind.
1247
+ */
1248
+ String: '_String', // <enum>
1249
+
1250
+ /**
1251
+ * An integer setting kind.
1252
+ */
1253
+ Integer: '_Integer', // <enum>
1254
+
1255
+ /**
1256
+ * A number setting kind.
1257
+ */
1258
+ Number: '_Number', // <enum>
1259
+
1260
+ /**
1261
+ * A boolean setting kind.
1262
+ */
1263
+ Boolean: '_Boolean', // <enum>
1264
+
1265
+ /**
1266
+ * A `BMSize` setting kind.
1267
+ */
1268
+ Size: '_BMSize', // <enum>
1269
+
1270
+ /**
1271
+ * A setting kind representing the possible values for a constraint constant.
1272
+ */
1273
+ Constant: '_Constant', // <enum>
1274
+
1275
+ /**
1276
+ * A setting kind representing a constraint.
1277
+ */
1278
+ Constraint: '_Constraint', // <enum>
1279
+
1280
+ /**
1281
+ * A setting kind representing a clickable link to a view.
1282
+ */
1283
+ View: '_View', // <enum>
1284
+
1285
+ /**
1286
+ * A setting kind representing a list of possible options that is a presented as a dropdown menu.
1287
+ */
1288
+ Enum: '_Enum', // <enum>
1289
+
1290
+ /**
1291
+ * A setting kind representing a list of possible options that is presented as a segmented control.
1292
+ */
1293
+ Segment: '_Segment', // <enum>
1294
+
1295
+ /**
1296
+ * A setting kind that represents a non-editable string.
1297
+ */
1298
+ Info: '_Info', // <enum>
1299
+
1300
+ /**
1301
+ * A setting kind that represents a non-editable section title.
1302
+ */
1303
+ Title: '_Title', // <enum>
1304
+
1305
+ /**
1306
+ * A setting kind that represents an insets object.
1307
+ */
1308
+ Insets: '_Insets', // <enum>
1309
+
1310
+ /**
1311
+ * A setting kind that represents the deactivate constraint button.
1312
+ */
1313
+ DeactivateConstraintsButton: '_DeactivateConstraintsButton', // <enum>
1314
+
1315
+ /**
1316
+ * A setting kind that represents the delete constraint button.
1317
+ */
1318
+ DeleteConstraintButton: '_DeleteConstraintButton', // <enum>
1319
+
1320
+ });
1321
+
1322
+ // @endtype
1323
+
1324
+ // @type BMLayoutEditorSetting
1325
+
1326
+ /**
1327
+ * Represents a setting that can be displayed and modified in the layout editor.
1328
+ */
1329
+ export function BMLayoutEditorSetting() {} // <constructor>
1330
+
1331
+ BMLayoutEditorSetting.prototype = {
1332
+
1333
+ /**
1334
+ * The name of this setting.
1335
+ */
1336
+ name: undefined, // <String>
1337
+
1338
+ /**
1339
+ * The data kind.
1340
+ */
1341
+ kind: BMLayoutEditorSettingKind.String, // <BMLayoutEditorSettingKind or String>
1342
+
1343
+ /**
1344
+ * Should be set to `YES` for settings that support null values.
1345
+ * Note that some data kinds such as `String` cannot be set to null through the editor.
1346
+ */
1347
+ nullable: NO, // <Boolean>
1348
+
1349
+ /**
1350
+ * Should be set to `YES` for settings that support variations.
1351
+ */
1352
+ variations: NO, // <Boolean>
1353
+
1354
+ /**
1355
+ * When set to `YES`, selecting a size class within the layout editor will cause a variation setting to be
1356
+ * automatically generated for this setting.
1357
+ */
1358
+ automaticallyExpandsVariations: NO, // <Boolean>
1359
+
1360
+ /**
1361
+ * When the base setting supports variations and this setting represents a variation of it,
1362
+ * this property indicates the size class to which the variation applies.
1363
+ */
1364
+ _sizeClass: undefined, // <BMLayoutSizeClass, nullable>
1365
+
1366
+ get sizeClass() {
1367
+ return this._sizeClass;
1368
+ },
1369
+
1370
+ /**
1371
+ * When the base setting supports variations and this setting represents a variation of it,
1372
+ * this property indicates the base setting from which this setting was generated.
1373
+ */
1374
+ _associatedProperty: undefined, // <BMLayoutEditorSetting, nullable>
1375
+
1376
+ get associatedProperty() {
1377
+ return this._associatedProperty;
1378
+ },
1379
+
1380
+ /**
1381
+ * The object that represents the target of this setting.
1382
+ */
1383
+ target: undefined, // <AnyObject>
1384
+
1385
+ /**
1386
+ * The property that this setting manages. The value of this property
1387
+ * will be passed to the target object whenever the value of this setting changes.
1388
+ */
1389
+ property: undefined, // <String, nullable>
1390
+
1391
+ /**
1392
+ * Initializes this setting with the given name, data kind and target object.
1393
+ * @param name <String> The name of the setting.
1394
+ * {
1395
+ * @param kind <BMLayoutEditorSettingKind or String> The data kind.
1396
+ * @param target <AnyObject> The target object of the setting.
1397
+ * @param variations <Boolean, nullable> Defaults to `NO`. Controls whether this setting supports variations.
1398
+ * @param nullable <Boolean, nullable> Defaults to `NO`. Controls whether this setting supports null value.
1399
+ * @param property <String, nullable> If specified, this represents the property on the target object which is controlled by this
1400
+ * setting. This value is not directly by the layout editor but can be used by the target object.
1401
+ * }
1402
+ * @return <BMLayoutEditorSetting> This setting.
1403
+ */
1404
+ initWithName(name, {kind, target, variations = NO, nullable = NO, property = undefined}) {
1405
+ this.name = name;
1406
+ this.kind = kind;
1407
+ this.target = target;
1408
+
1409
+ this.variations = variations;
1410
+ this.nullable = nullable;
1411
+ if (property !== undefined) this.property = property;
1412
+
1413
+ return this;
1414
+ },
1415
+
1416
+ /**
1417
+ * Tests whether this setting and the given setting are equivalent.
1418
+ * Two settings are equivalent if they have the same name, target object, target property and size class.
1419
+ * @param setting <BMLayoutEditorSetting> The setting to test against.
1420
+ * @return <Boolean> `YES` if the settings are equivalent, `NO` otherwise.
1421
+ */
1422
+ isEqualToSetting(setting) {
1423
+ const result = setting.name == this.name && setting.target == this.target && setting._sizeClass == this._sizeClass && setting.property == this.property;
1424
+ return result;
1425
+ }
1426
+ }
1427
+
1428
+ /**
1429
+ * Constructs and returns a setting with the given name, data kind and target object.
1430
+ * @param name <String> The name of the setting.
1431
+ * {
1432
+ * @param kind <BMLayoutEditorSettingKind> The data kind.
1433
+ * @param target <AnyObject> The target object of the setting.
1434
+ * @param variations <Boolean, nullable> Defaults to `NO`. Controls whether this setting supports variations.
1435
+ * @param nullable <Boolean, nullable> Defaults to `NO`. Controls whether this setting supports null value.
1436
+ * @param property <String, nullable> If specified, this represents the property on the target object which is controlled by this
1437
+ * setting. This value is not directly by the layout editor but can be used by the target object.
1438
+ * }
1439
+ * @return <BMLayoutEditorSetting> A setting.
1440
+ */
1441
+ BMLayoutEditorSetting.settingWithName = function (name, {kind, target, variations = NO, nullable = NO, property = undefined}) {
1442
+ return (new this).initWithName(name, {kind, target, variations, nullable, property});
1443
+ }
1444
+
1445
+ // @endtype
1446
+
1447
+ // @type BMLayoutEditorEnumSetting extends BMLayoutEditorSetting
1448
+
1449
+ /**
1450
+ * Represents a setting that can be displayed and modified in the layout editor.
1451
+ * This setting also specifies a number of possible options via the `options` property.
1452
+ *
1453
+ * Settings created with this class must have their `kind` property set to `.Enum` or `.Segment`.
1454
+ */
1455
+ export function BMLayoutEditorEnumSetting() {} // <constructor>
1456
+
1457
+ BMLayoutEditorEnumSetting.prototype = BMExtend(Object.create(BMLayoutEditorSetting.prototype), {
1458
+
1459
+ /**
1460
+ * An array of menu items that define which options are available for this setting.
1461
+ *
1462
+ * The `userInfo` property of these menu items must be set to the value of the property that each item
1463
+ * represents.
1464
+ *
1465
+ * The `action` property of these objects will be ignored.
1466
+ */
1467
+ options: undefined, // <[BMMenuItem]>
1468
+
1469
+ initWithName(name, args) {
1470
+ BMLayoutEditorSetting.prototype.initWithName.apply(this, arguments);
1471
+
1472
+ this.options = [];
1473
+
1474
+ return this;
1475
+ }
1476
+ });
1477
+
1478
+ BMLayoutEditorEnumSetting.settingWithName = BMLayoutEditorSetting.settingWithName;
1479
+
1480
+ // @endtype BMLayoutEditorEnumSetting