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,1653 @@
1
+ //@ts-check
2
+
3
+ import { BMExtend, BMStringByCapitalizingString, YES, NO } from "../Core/BMCoreUI";
4
+ import { BMCollectionViewCell } from "../BMCollectionView/BMCollectionViewCell";
5
+ import { BMLayoutAttribute } from "../BMView/BMLayoutConstraint_v2.5";
6
+ import { _BMURLOfImageAtPath } from "./BMLayoutEditorSettings";
7
+ import { BMView } from "../BMView/BMView_v2.5";
8
+ import { BMLayoutSizeClass, BMLayoutOrientation } from "../BMView/BMLayoutSizeClass";
9
+ import { BMPointMake } from "../Core/BMPoint";
10
+ import { BMMenuKind } from "../BMView/BMMenu";
11
+ import { BMRectMakeWithNodeFrame } from "../Core/BMRect";
12
+
13
+
14
+ const BMLayoutEdittorSettingCellRowSpacing = 2;
15
+
16
+ // @type BMLayoutEditorSettingsTitleView extends BMView
17
+
18
+ /**
19
+ * A view subclass used by layout editor setting cells as the setting title.
20
+ * In addition to displaying the cell's title, it also manages adding and removing variations
21
+ * for the setting.
22
+ */
23
+ export function BMLayoutEditorSettingsTitleView() {} // <constructor>
24
+
25
+ BMLayoutEditorSettingsTitleView.prototype = BMExtend(Object.create(BMView.prototype), {
26
+ constructor: BMLayoutEditorSettingsTitleView,
27
+
28
+ /**
29
+ * The view displaying the title text.
30
+ */
31
+ _titleLabel: undefined, // <BMView>
32
+
33
+ /**
34
+ * The view displaying the size class badge, if there is one.
35
+ */
36
+ _badge: undefined, // <BMView>
37
+
38
+ /**
39
+ * The view displaying the variations button, if there is one.
40
+ */
41
+ _button: undefined, // <BMView>
42
+
43
+ /**
44
+ * The string to display as a title.
45
+ */
46
+ _title: undefined, // <String>
47
+ get title() {
48
+ return this._title;
49
+ },
50
+ set title(title) {
51
+ this._title = title;
52
+ this._titleLabel.node.innerText = title;
53
+ this._titleLabel.invalidateIntrinsicSize();
54
+ },
55
+
56
+ /**
57
+ * The setting displayed by this title view.
58
+ */
59
+ _setting: undefined, // <BMLayoutEditorSetting, nullable>
60
+ get setting() {
61
+ return this._setting;
62
+ },
63
+ set setting(setting) {
64
+ this._setting = setting;
65
+
66
+ if (setting.sizeClass) {
67
+ switch (setting.sizeClass) {
68
+ case BMLayoutSizeClass.phoneSizeClass():
69
+ this._badge.node.className = 'BMLayoutEditorDetailsItemBadgeIcon BMLayoutEditorImagePhoneMini';
70
+ break;
71
+ case BMLayoutSizeClass.phoneSizeClassWithOrientation(BMLayoutOrientation.Portrait):
72
+ this._badge.node.className = 'BMLayoutEditorDetailsItemBadgeIcon BMLayoutEditorImagePhonePortraitMini';
73
+ break;
74
+ case BMLayoutSizeClass.phoneSizeClassWithOrientation(BMLayoutOrientation.Landscape):
75
+ this._badge.node.className = 'BMLayoutEditorDetailsItemBadgeIcon BMLayoutEditorImagePhoneLandscapeMini';
76
+ break;
77
+ case BMLayoutSizeClass.tabletSizeClass():
78
+ this._badge.node.className = 'BMLayoutEditorDetailsItemBadgeIcon BMLayoutEditorImageTabletMini';
79
+ break;
80
+ case BMLayoutSizeClass.tabletSizeClassWithOrientation(BMLayoutOrientation.Portrait):
81
+ this._badge.node.className = 'BMLayoutEditorDetailsItemBadgeIcon BMLayoutEditorImageTabletPortraitMini';
82
+ break;
83
+ case BMLayoutSizeClass.tabletSizeClassWithOrientation(BMLayoutOrientation.Landscape):
84
+ this._badge.node.className = 'BMLayoutEditorDetailsItemBadgeIcon BMLayoutEditorImageTabletLandscapeMini';
85
+ break;
86
+ default:
87
+ this._badge.node.className = 'BMLayoutEditorDetailsItemBadge';
88
+ this._badge.node.innerText = setting.sizeClass._hashString;
89
+ }
90
+ this._button.node.innerHTML = '<i class="material-icons">remove_circle</i>';
91
+ this._button.isVisible = YES;
92
+
93
+ this._badgeWidthConstraint.constant = 16;
94
+ this._buttonWidthConstraint.constant = 24;
95
+ }
96
+ else {
97
+ this._badge.node.className = '';
98
+ if (setting.variations) {
99
+ this._button.node.innerHTML = '<i class="material-icons">add_circle</i>';
100
+ this._button.isVisible = YES;
101
+ this._buttonWidthConstraint.constant = 24;
102
+ }
103
+ else {
104
+ this._button.node.innerHTML = '';
105
+ this._button.isVisible = NO;
106
+ this._buttonWidthConstraint.constant = 0;
107
+ }
108
+
109
+ this._badgeWidthConstraint.constant = 0;
110
+ }
111
+
112
+ this._badge.invalidateIntrinsicSize();
113
+ this.title = setting.name + ':';
114
+ },
115
+
116
+ initWithDOMNode() {
117
+ BMView.prototype.initWithDOMNode.apply(this, arguments);
118
+ this.debuggingName = 'TitleView';
119
+
120
+ // Create and configure the label text.
121
+ const label = this._titleLabel = BMView.view();
122
+ label.debuggingName = 'TitleViewLabel';
123
+ label.supportsAutomaticIntrinsicSize = YES;
124
+ label.node.className = 'BMLayoutEditorDetailsTitleCell';
125
+ this.addSubview(label);
126
+ label.trailing.equalTo(this.trailing).isActive = YES;
127
+ label.top.equalTo(this.top).isActive = YES;
128
+ label.bottom.equalTo(this.bottom).isActive = YES;
129
+
130
+ label.compressionResistance = 700;
131
+
132
+ // Create and configure the size class badge
133
+ const badge = this._badge = BMView.view();
134
+ badge.debuggingName = 'TitleViewBadge';
135
+ badge.supportsAutomaticIntrinsicSize = YES;
136
+ this.addSubview(badge);
137
+ label.leading.equalTo(badge.trailing, {plus: 4}).isActive = YES;
138
+ badge.centerY.equalTo(label.centerY).isActive = YES;
139
+ this._badgeWidthConstraint = badge.width.greaterThanOrEqualTo(16);
140
+ this._badgeWidthConstraint.isActive = YES;
141
+ badge.height.greaterThanOrEqualTo(16).isActive = YES;
142
+
143
+ // Create and configure the variations button
144
+ const button = this._button = BMView.view();
145
+ button.debuggingName = 'TitleViewButton';
146
+ button.node.className = 'BMLayoutEditorDetailsItemVariationsButton';
147
+ this.addSubview(button);
148
+ button.leading.equalTo(this.leading).isActive = YES;
149
+ badge.leading.greaterThanOrEqualTo(button.trailing, {plus: 4}).isActive = YES;
150
+ button.centerY.equalTo(label.centerY).isActive = YES;
151
+ this._buttonWidthConstraint = button.width.equalTo(24);
152
+ this._buttonWidthConstraint.isActive = YES;
153
+ button.height.equalTo(24).isActive = YES;
154
+
155
+ button.node.addEventListener('click', e => this.variationsButtonClickedWithEvent(e));
156
+
157
+ return this;
158
+ },
159
+
160
+ /**
161
+ * Invoked when the user clicks on the variations button.
162
+ * @param event <Event> The event that triggered this action.
163
+ */
164
+ variationsButtonClickedWithEvent(event) {
165
+ if (this.setting.sizeClass) {
166
+ this.setting.target[`remove${BMStringByCapitalizingString(this.setting.property)}VariationForSizeClass`](this.setting.sizeClass);
167
+ // This is necessary in the case of removing variations for constant or isActive in the case of selected constraint for the currently selected size class
168
+ this.rootView.setting = this.rootView.setting;
169
+ this.rootView.tab.updateSettings();
170
+ }
171
+ else {
172
+ // TODO check if a variation already exists for this size class; ideally filter the menu
173
+ this.rootView.layoutEditor._showSizeClassMenuAtPoint(BMPointMake(event.clientX, event.clientY), {kind: BMMenuKind.Menu, action: sizeClass => {
174
+ // Skip adding a new variation for a size class when it has already been defined
175
+ if (this.setting.target._variations && this.setting.target._variations[sizeClass] && this.setting.property in this.setting.target._variations[sizeClass]) return;
176
+
177
+ // Create the variation, then reload the tab contents
178
+ this.setting.target[`set${BMStringByCapitalizingString(this.setting.property)}`](this.setting.target[this.setting.property], {forSizeClass: sizeClass});
179
+ this.rootView.tab.updateSettings();
180
+ }});
181
+ }
182
+ }
183
+ });
184
+
185
+ /**
186
+ * Constructs and returns a title view.
187
+ * @return <BMLayoutEditorSettingsTitleView> A title view.
188
+ */
189
+ BMLayoutEditorSettingsTitleView.titleView = function () {
190
+ return (new BMLayoutEditorSettingsTitleView).initWithDOMNode(document.createElement('div'));
191
+ };
192
+
193
+ // @endtype
194
+
195
+ // @type BMLayoutEditorSettingsFooter extends BMCollectionViewCell
196
+
197
+ /**
198
+ * The settings footer is a specialized subclass of collection view cell that is used by the layout editor
199
+ * to display section separators.
200
+ */
201
+ export function BMLayoutEditorSettingsFooter() {} // <constructor>
202
+
203
+ BMLayoutEditorSettingsFooter.prototype = BMExtend(Object.create(BMCollectionViewCell.prototype), {
204
+ constructor: BMLayoutEditorSettingsFooter,
205
+
206
+ initWithCollectionView() {
207
+ BMCollectionViewCell.prototype.initWithCollectionView.apply(this, arguments);
208
+
209
+ const contentView = BMView.view();
210
+ this.addSubview(contentView);
211
+
212
+ contentView.leading.equalTo(this.leading).isActive = YES;
213
+ contentView.trailing.equalTo(this.trailing).isActive = YES;
214
+ contentView.top.equalTo(this.top).isActive = YES;
215
+ contentView.bottom.equalTo(this.bottom).isActive = YES;
216
+
217
+ const dividerView = BMView.view();
218
+ contentView.addSubview(dividerView);
219
+ dividerView.node.className = 'BMLayoutEditorDetailsDividerCell';
220
+
221
+ dividerView.leading.equalTo(contentView.leading).isActive = YES;
222
+ dividerView.trailing.equalTo(contentView.trailing).isActive = YES;
223
+ dividerView.height.equalTo(1).isActive = YES;
224
+ dividerView.centerY.equalTo(contentView.centerY).isActive = YES;
225
+
226
+ return this;
227
+ }
228
+ });
229
+
230
+ // @endtype
231
+
232
+ // @type BMLayoutEditorSettingsCell extends BMCollectionViewCell
233
+
234
+ /**
235
+ * The settings cell is a specialized subclass of collection view cell that is used by the layout editor
236
+ * to display view and constraint settings.
237
+ *
238
+ * The settings cell class itself is never used directly; instead, dependending on the type of the setting
239
+ * an appropriate subclass is used.
240
+ *
241
+ * For settings where the built in cell types are not sufficient, this class serves as the base setting class
242
+ * that can be subclassed to provide custom settings interfaces.
243
+ */
244
+ export function BMLayoutEditorSettingsCell() {} // <constructor>
245
+
246
+ BMLayoutEditorSettingsCell.prototype = BMExtend(Object.create(BMCollectionViewCell.prototype), {
247
+ constructor: BMLayoutEditorSettingsCell,
248
+
249
+ /**
250
+ * The view to which this cell's contents should be added.
251
+ */
252
+ _contentView: undefined, // <BMView>
253
+
254
+ get contentView() {
255
+ return this._contentView;
256
+ },
257
+
258
+ initWithCollectionView() {
259
+ BMCollectionViewCell.prototype.initWithCollectionView.apply(this, arguments);
260
+
261
+ const contentView = BMView.view();
262
+ contentView.debuggingName = 'ContentView';
263
+ this._contentView = contentView;
264
+ this.addSubview(contentView);
265
+
266
+ if (this.constructor === BMLayoutEditorSettingsCell) {
267
+ contentView.node.innerText = 'This is a test!';
268
+
269
+ contentView.node.style.color = 'white';
270
+ }
271
+
272
+ contentView.leading.equalTo(this.leading).isActive = YES;
273
+ contentView.trailing.equalTo(this.trailing).isActive = YES;
274
+ contentView.top.equalTo(this.top).isActive = YES;
275
+ contentView.bottom.equalTo(this.bottom).isActive = YES;
276
+
277
+ contentView.height.greaterThanOrEqualTo(32, {priority: 750}).isActive = YES;
278
+
279
+ return this;
280
+ },
281
+
282
+ /**
283
+ * The setting displayed by this cell.
284
+ */
285
+ _setting: undefined, // <BMLayoutEditorSetting>
286
+
287
+ get setting() {
288
+ return this._setting;
289
+ },
290
+ set setting(setting) {
291
+ this._setting = setting;
292
+ this.cellDidBindToSetting(setting);
293
+ },
294
+
295
+ /**
296
+ * The settings tab displaying this cell.
297
+ * This property is typically not available during construction, but will
298
+ * become available after this cell has been bound to a setting.
299
+ */
300
+ _tab: undefined, // <BMLayoutEditorSettingsTab, nullable>
301
+
302
+ get tab() {
303
+ return this._tab;
304
+ },
305
+
306
+ /**
307
+ * The settings view displaying this cell.
308
+ * This property is typically not available during construction, but will
309
+ * become available after this cell has been bound to a setting.
310
+ */
311
+ get settingsView() { // <_BMLayoutEditorSettingsView, nullable>
312
+ const tab = this.tab;
313
+ if (tab) {
314
+ return tab._settingsPanel._settingsView;
315
+ }
316
+ },
317
+
318
+ /**
319
+ * The layout editor displaying this cell.
320
+ * This property is typically not available during construction, but will
321
+ * become available after this cell has been bound to a setting.
322
+ */
323
+ get layoutEditor() { // <BMLayoutEditor, nullable>
324
+ const tab = this.tab;
325
+ if (tab) {
326
+ return tab.layoutEditor;
327
+ }
328
+ },
329
+
330
+ /**
331
+ * Invoked when the setting displayed by this cell changes.
332
+ * Subclasses can use this method to update their display to match the new setting.
333
+ * The default implementation does nothing.
334
+ * @param setting <BMLayoutEditorSetting> The new setting.
335
+ */
336
+ cellDidBindToSetting(setting) {
337
+
338
+ }
339
+ });
340
+
341
+ // @endtype
342
+
343
+ // @type BMLayoutEditorSettingsConstraintCell extends BMLayoutEditorSettingsCell
344
+
345
+ /**
346
+ * A cell type specialized for displaying constraints.
347
+ */
348
+ export function BMLayoutEditorSettingsConstraintCell() {} // <constructor>
349
+
350
+ BMLayoutEditorSettingsConstraintCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsCell.prototype), {
351
+ constructor: BMLayoutEditorSettingsConstraintCell,
352
+
353
+ _constraintView: undefined,
354
+
355
+ /**
356
+ * The constraint view leading layout constraint.
357
+ */
358
+ _constraintViewLeading: undefined, // <BMLayoutConstraint>
359
+
360
+ initWithCollectionView() {
361
+ BMLayoutEditorSettingsCell.prototype.initWithCollectionView.apply(this, arguments);
362
+
363
+ const constraintView = BMView.view();
364
+ constraintView.debuggingName = 'ConstraintView';
365
+ this._constraintView = constraintView;
366
+ this.contentView.addSubview(constraintView);
367
+ constraintView.supportsAutomaticIntrinsicSize = YES;
368
+
369
+ this._constraintViewLeading = constraintView.leading.equalTo(this.leading, {plus: 8});
370
+ this._constraintViewLeading.isActive = YES;
371
+ constraintView.trailing.equalTo(this.trailing, {plus: -8}).isActive = YES;
372
+ constraintView.top.equalTo(this.top, {plus: 8}).isActive = YES;
373
+ constraintView.bottom.equalTo(this.bottom).isActive = YES;
374
+
375
+ constraintView.node.className = 'BMLayoutEditorDetailsConstraintItem BMLayoutEditorDetailsConstraintItemCell BMCollectionViewCellEventHandler';
376
+
377
+ constraintView.node.addEventListener('click', event => {
378
+ const constraint = this.setting.target;
379
+ if (!constraint._internal) this.settingsView.selectConstraint(constraint, {withReferenceView: this.tab._displayedView})
380
+ });
381
+
382
+ return this;
383
+ },
384
+
385
+ // @override - BMCollectionViewCell
386
+ cellDidBindToSetting(setting) {
387
+ const constraintNode = this._constraintView.node;
388
+ const constraint = setting.target;
389
+
390
+ constraintNode.innerText = constraint.descriptionRelativeToView(this.tab._displayedView);
391
+
392
+ const icon = document.createElement('img');
393
+ icon.className = 'BMLayoutEditorDetailsConstraintIcon';
394
+
395
+ if (constraint.isConstraintCollection) {
396
+ icon.src = _BMURLOfImageAtPath('images/OwnConstraints.png');
397
+ }
398
+ else {
399
+ const attribute = constraint._sourceView == this.tab._displayedView ? '_sourceViewAttribute' : '_targetViewAttribute';
400
+ switch (constraint[attribute]) {
401
+ case BMLayoutAttribute.Left:
402
+ case BMLayoutAttribute.Leading:
403
+ icon.src = _BMURLOfImageAtPath('images/LeftConstraint.png');
404
+ break;
405
+ case BMLayoutAttribute.Right:
406
+ case BMLayoutAttribute.Trailing:
407
+ icon.src = _BMURLOfImageAtPath('images/RightConstraint.png');
408
+ break;
409
+ case BMLayoutAttribute.CenterX:
410
+ icon.src = _BMURLOfImageAtPath('images/CenterXConstraint.png');
411
+ break;
412
+ case BMLayoutAttribute.CenterY:
413
+ icon.src = _BMURLOfImageAtPath('images/CenterYConstraint.png');
414
+ break;
415
+ case BMLayoutAttribute.Top:
416
+ icon.src = _BMURLOfImageAtPath('images/TopConstraint.png');
417
+ break;
418
+ case BMLayoutAttribute.Bottom:
419
+ icon.src = _BMURLOfImageAtPath('images/BottomConstraint.png');
420
+ break;
421
+ case BMLayoutAttribute.Width:
422
+ icon.src = _BMURLOfImageAtPath('images/WidthConstraint.png');
423
+ break;
424
+ case BMLayoutAttribute.Height:
425
+ icon.src = _BMURLOfImageAtPath('images/HeightConstraint.png');
426
+ break;
427
+ }
428
+ }
429
+
430
+ constraintNode.insertBefore(icon, constraintNode.firstChild);
431
+
432
+ if (constraint._priority > 500 && constraint._priority < 1000) {
433
+ constraintNode.classList.add('BMLayoutEditorDetailsConstraintItemStrong');
434
+ constraintNode.classList.remove('BMLayoutEditorDetailsConstraintItemWeak');
435
+ }
436
+ else if (constraint._priority <= 500) {
437
+ constraintNode.classList.remove('BMLayoutEditorDetailsConstraintItemStrong');
438
+ constraintNode.classList.add('BMLayoutEditorDetailsConstraintItemWeak');
439
+ }
440
+ else {
441
+ constraintNode.classList.remove('BMLayoutEditorDetailsConstraintItemStrong');
442
+ constraintNode.classList.remove('BMLayoutEditorDetailsConstraintItemWeak');
443
+ }
444
+
445
+ if (constraint._internal) {
446
+ constraintNode.classList.add('BMLayoutEditorDetailsConstraintItemReadonly');
447
+ }
448
+ else {
449
+ constraintNode.classList.remove('BMLayoutEditorDetailsConstraintItemReadonly');
450
+ }
451
+
452
+ if (!constraint._configuration.isActive) {
453
+ constraintNode.classList.add('BMLayoutEditorDetailsConstraintItemInactive');
454
+ }
455
+ else {
456
+ constraintNode.classList.remove('BMLayoutEditorDetailsConstraintItemInactive');
457
+ }
458
+
459
+ this._constraintView.invalidateIntrinsicSize();
460
+
461
+ return constraintNode;
462
+ }
463
+ })
464
+
465
+ // @endtype
466
+
467
+ // @type BMLayoutEditorSettingsDeactivateConstraintsCell extends BMLayoutEditorSettingsCell
468
+
469
+ /**
470
+ * A cell type specialized for displaying constraints.
471
+ */
472
+ export function BMLayoutEditorSettingsDeactivateConstraintsCell() {} // <constructor>
473
+
474
+ BMLayoutEditorSettingsDeactivateConstraintsCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsCell.prototype), {
475
+ constructor: BMLayoutEditorSettingsDeactivateConstraintsCell,
476
+
477
+ _buttonView: undefined,
478
+
479
+ prepareForDisplay() {
480
+ window.addEventListener('keydown', this._keyboardModifier, YES);
481
+ window.addEventListener('keyup', this._keyboardModifier, YES);
482
+ },
483
+
484
+ prepareForReuse() {
485
+ window.removeEventListener('keydown', this._keyboardModifier, YES);
486
+ window.removeEventListener('keyup', this._keyboardModifier, YES);
487
+ },
488
+
489
+ invalidate() {
490
+ window.removeEventListener('keydown', this._keyboardModifier, YES);
491
+ window.removeEventListener('keyup', this._keyboardModifier, YES);
492
+ },
493
+
494
+ initWithCollectionView() {
495
+ BMLayoutEditorSettingsCell.prototype.initWithCollectionView.apply(this, arguments);
496
+
497
+ const buttonView = BMView.view();
498
+ buttonView.debuggingName = 'DeactivateButton';
499
+ this._buttonView = buttonView;
500
+ this.contentView.addSubview(buttonView);
501
+ buttonView.supportsAutomaticIntrinsicSize = YES;
502
+
503
+ buttonView.leading.equalTo(this.contentView.leading, {plus: 32}).isActive = YES;
504
+ buttonView.trailing.equalTo(this.contentView.trailing, {plus: -32}).isActive = YES;
505
+ buttonView.top.equalTo(this.contentView.top, {plus: 8}).isActive = YES;
506
+ buttonView.bottom.equalTo(this.contentView.bottom, {plus: -8}).isActive = YES;
507
+
508
+ buttonView.node.className = 'BMWindowButton BMCollectionViewCellEventHandler';
509
+ buttonView.node.style.boxSizing = 'border-box';
510
+ buttonView.node.style.textAlign = 'center';
511
+ buttonView.node.style.pointer = 'default';
512
+ buttonView.node.style.margin = 0;
513
+ buttonView.node.innerText = 'Deactivate Constraints';
514
+
515
+ let hasOption = NO;
516
+ let hasShift = NO;
517
+ let hasCtrl = NO;
518
+
519
+ buttonView.node.addEventListener('click', event => {
520
+ if (hasOption && hasShift && hasCtrl) {
521
+ new Set(this.setting.target.rootView.allConstraints).forEach(constraint => constraint.remove());
522
+ }
523
+ else if (hasOption) {
524
+ for (let constraint of this.setting.target.localConstraints) {
525
+ if (hasShift) {
526
+ constraint.remove();
527
+ }
528
+ else {
529
+ if (this._activeSizeClass) {
530
+ constraint.setIsActive(NO, {forSizeClass: this._activeSizeClass});
531
+ }
532
+ else {
533
+ constraint.isActive = NO;
534
+ }
535
+ }
536
+ }
537
+ }
538
+ else {
539
+ const constraintCategoryMap = this.tab._constraintCategories;
540
+ Object.keys(constraintCategoryMap).forEach(key => {
541
+ for (let constraint of constraintCategoryMap[key]) {
542
+ if (hasShift) {
543
+ constraint.remove();
544
+ }
545
+ else {
546
+ if (this.layoutEditor._activeSizeClass) {
547
+ constraint.setIsActive(NO, {forSizeClass: this.layoutEditor._activeSizeClass});
548
+ }
549
+ else {
550
+ constraint.isActive = NO;
551
+ }
552
+ }
553
+ }
554
+ });
555
+ }
556
+
557
+ this.tab._updateSettings();
558
+ });
559
+
560
+ buttonView.node.addEventListener('contextmenu', event => event.preventDefault());
561
+
562
+ this._keyboardModifier = event => {
563
+ switch (event.key) {
564
+ case 'Alt':
565
+ hasOption = (event.type == 'keydown');
566
+ break;
567
+ case 'Shift':
568
+ hasShift = (event.type == 'keydown');
569
+ break;
570
+ case 'Control':
571
+ hasCtrl = (event.type == 'keydown');
572
+ break;
573
+ }
574
+
575
+ if (hasOption) {
576
+ if (hasShift) {
577
+ if (hasCtrl) {
578
+ buttonView.node.innerText = 'Reset Layout';
579
+ }
580
+ else {
581
+ buttonView.node.innerText = 'Remove All Constraints';
582
+ }
583
+ }
584
+ else {
585
+ buttonView.node.innerText = 'Deactivate All Constraints';
586
+ }
587
+ }
588
+ else if (hasShift) {
589
+ buttonView.node.innerText = 'Remove Constraints';
590
+ }
591
+ else {
592
+ buttonView.node.innerText = 'Deactivate Constraints';
593
+ }
594
+ };
595
+
596
+ return this;
597
+ },
598
+
599
+ // @override - BMCollectionViewCell
600
+ cellDidBindToSetting(setting) {
601
+ }
602
+ })
603
+
604
+ // @endtype
605
+
606
+ // @type BMLayoutEditorSettingsDeleteConstraintCell extends BMLayoutEditorSettingsCell
607
+
608
+ /**
609
+ * A cell type specialized for displaying constraints.
610
+ */
611
+ export function BMLayoutEditorSettingsDeleteConstraintCell() {} // <constructor>
612
+
613
+ BMLayoutEditorSettingsDeleteConstraintCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsCell.prototype), {
614
+ constructor: BMLayoutEditorSettingsDeleteConstraintCell,
615
+
616
+ _buttonView: undefined,
617
+
618
+ initWithCollectionView() {
619
+ BMLayoutEditorSettingsCell.prototype.initWithCollectionView.apply(this, arguments);
620
+
621
+ const buttonView = BMView.view();
622
+ this._buttonView = buttonView;
623
+ this.contentView.addSubview(buttonView);
624
+ buttonView.supportsAutomaticIntrinsicSize = YES;
625
+ buttonView.debuggingName = 'DeleteConstraintsButton';
626
+
627
+ buttonView.leading.equalTo(this.contentView.leading, {plus: 32}).isActive = YES;
628
+ buttonView.trailing.equalTo(this.contentView.trailing, {plus: -32}).isActive = YES;
629
+ buttonView.top.equalTo(this.contentView.top, {plus: 8}).isActive = YES;
630
+ buttonView.bottom.equalTo(this.contentView.bottom, {plus: -8}).isActive = YES;
631
+
632
+ buttonView.node.className = 'BMWindowButton BMCollectionViewCellEventHandler';
633
+ buttonView.node.style.boxSizing = 'border-box';
634
+ buttonView.node.style.textAlign = 'center';
635
+ buttonView.node.style.pointer = 'default';
636
+ buttonView.node.style.margin = 0;
637
+ buttonView.node.innerText = 'Delete Constraint';
638
+
639
+ buttonView.node.addEventListener('click', event => {
640
+ this.setting.target.remove();
641
+ this.layoutEditor.selectView(this.tab._settingsPanel._referenceView);
642
+ });
643
+
644
+ buttonView.node.addEventListener('contextmenu', event => event.preventDefault());
645
+
646
+ return this;
647
+ },
648
+
649
+ // @override - BMCollectionViewCell
650
+ cellDidBindToSetting(setting) {
651
+ }
652
+ })
653
+
654
+ // @endtype
655
+
656
+ // @type BMLayoutEditorSettingsConstraintCell extends BMLayoutEditorSettingsCell
657
+
658
+ /**
659
+ * A cell type specialized for displaying section titles.
660
+ */
661
+ export function BMLayoutEditorSettingsTitleCell() {} // <constructor>
662
+
663
+ BMLayoutEditorSettingsTitleCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsCell.prototype), {
664
+ constructor: BMLayoutEditorSettingsTitleCell,
665
+
666
+ _titleView: undefined,
667
+
668
+ initWithCollectionView() {
669
+ BMLayoutEditorSettingsCell.prototype.initWithCollectionView.apply(this, arguments);
670
+
671
+ const titleView = BMView.view();
672
+ this._titleView = titleView;
673
+ this.contentView.addSubview(titleView);
674
+ titleView.supportsAutomaticIntrinsicSize = YES;
675
+ titleView.debuggingName = 'TitleView';
676
+
677
+ titleView.leading.equalTo(this.leading, {plus: 8}).isActive = YES;
678
+ titleView.trailing.equalTo(this.trailing, {plus: -8}).isActive = YES;
679
+ titleView.top.equalTo(this.top).isActive = YES;
680
+ titleView.bottom.equalTo(this.bottom).isActive = YES;
681
+
682
+ titleView.node.className = 'BMLayoutEditorDetailsTitleCell';
683
+
684
+ return this;
685
+ },
686
+
687
+ // @override - BMCollectionViewCell
688
+ cellDidBindToSetting(setting) {
689
+ const titleNode = this._titleView.node;
690
+ titleNode.innerText = setting.name;
691
+ this._titleView.invalidateIntrinsicSize();
692
+ }
693
+ })
694
+
695
+ // @endtype
696
+
697
+
698
+ // @type BMLayoutEditorSettingsReadonlyCell extends BMLayoutEditorSettingsCell
699
+
700
+ /**
701
+ * A cell type specialized for displaying readonly settings.
702
+ */
703
+ export function BMLayoutEditorSettingsReadonlyCell() {} // <constructor>
704
+
705
+ BMLayoutEditorSettingsReadonlyCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsCell.prototype), {
706
+ constructor: BMLayoutEditorSettingsReadonlyCell,
707
+
708
+ /**
709
+ * The view that displays this setting's title.
710
+ */
711
+ _titleView: undefined,
712
+ get titleView() {
713
+ return this._titleView;
714
+ },
715
+
716
+ /**
717
+ * The view that displays this setting's input node.
718
+ */
719
+ _inputView: undefined,
720
+ get inputView() {
721
+ return this._inputView;
722
+ },
723
+
724
+ initWithCollectionView(collectionView, args) {
725
+ BMLayoutEditorSettingsCell.prototype.initWithCollectionView.apply(this, arguments);
726
+
727
+ const titleView = BMView.view();
728
+ this._titleView = titleView;
729
+ this.contentView.addSubview(titleView);
730
+ titleView.supportsAutomaticIntrinsicSize = YES;
731
+ titleView.debuggingName = 'TitleView';
732
+
733
+ titleView.leading.equalTo(this.leading, {plus: 8}).isActive = YES;
734
+ titleView.top.equalTo(this.top, {plus: BMLayoutEdittorSettingCellRowSpacing}).isActive = YES;
735
+ titleView.node.className = 'BMLayoutEditorDetailsTitleCell';
736
+ titleView.node.style.textAlign = 'right';
737
+ titleView.height.greaterThanOrEqualTo(24).isActive = YES;
738
+ titleView.node.style.cssText = 'vertical-align: middle; line-height: 24px; text-align: right;';
739
+
740
+ const inputNode = document.createElement('div');
741
+ inputNode.className = 'BMLayoutEditorDetailsItemReadonlyValue BMLayoutEditorDetailsItemReadonlyValueCell';
742
+ inputNode.style.cssText = 'padding-left: 8px !important';
743
+ const inputView = BMView.viewForNode(inputNode);
744
+ this._inputView = inputView;
745
+ inputView.supportsAutomaticIntrinsicSize = YES;
746
+ inputView.debuggingName = 'LabelView';
747
+
748
+ this.contentView.addSubview(inputView);
749
+ inputView.leading.equalTo(titleView.trailing, {plus: 8}).isActive = YES;
750
+ inputView.width.greaterThanOrEqualTo(200).isActive = YES;
751
+ inputView.width.equalTo(titleView.width, {times: 1.5, priority: 600}).isActive = YES;
752
+ inputView.trailing.equalTo(this.trailing, {plus: -8}).isActive = YES;
753
+ inputView.top.equalTo(this.top, {plus: BMLayoutEdittorSettingCellRowSpacing}).isActive = YES;
754
+ inputView.height.greaterThanOrEqualTo(24).isActive = YES;
755
+ inputView.bottom.equalTo(this.bottom, {plus: -BMLayoutEdittorSettingCellRowSpacing}).isActive = YES;
756
+
757
+ return this;
758
+ },
759
+
760
+ // @override - BMCollectionViewCell
761
+ cellDidBindToSetting(setting) {
762
+ this._titleView.node.innerText = setting.name + ':';
763
+ this._titleView.invalidateIntrinsicSize();
764
+
765
+ this._inputView.node.innerText = setting.target[setting.property];
766
+ this._inputView.invalidateIntrinsicSize();
767
+ }
768
+ })
769
+
770
+ // @endtype
771
+
772
+
773
+ // @type BMLayoutEditorSettingsViewCell extends BMLayoutEditorSettingsReadonlyCell
774
+
775
+ /**
776
+ * A cell type specialized for displaying readonly settings.
777
+ */
778
+ export function BMLayoutEditorSettingsViewCell() {} // <constructor>
779
+
780
+ BMLayoutEditorSettingsViewCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsReadonlyCell.prototype), {
781
+ constructor: BMLayoutEditorSettingsViewCell,
782
+
783
+ initWithCollectionView(collectionView, args) {
784
+ BMLayoutEditorSettingsReadonlyCell.prototype.initWithCollectionView.apply(this, arguments);
785
+
786
+ this._inputView.node.classList.add('BMLayoutEditorDetailsItemReadonlyValueClickable');
787
+ this._inputView.node.addEventListener('click', e => {
788
+ this.settingsView.selectView(this.setting.target[this.setting.property]);
789
+ });
790
+ this._inputView.node.style.cssText += 'margin: 0px !important;';
791
+
792
+ return this;
793
+ },
794
+
795
+ // @override - BMCollectionViewCell
796
+ cellDidBindToSetting(setting) {
797
+ this._titleView.node.innerText = setting.name + ':';
798
+ this._titleView.invalidateIntrinsicSize();
799
+
800
+ const view = setting.target[setting.property];
801
+ const arrowSpan = document.createElement('span');
802
+ arrowSpan.innerText = '›';
803
+ arrowSpan.className = 'BMLayoutEditorDetailsItemViewLink BMCollectionViewCellEventHandler';
804
+ const viewSpan = document.createElement('span');
805
+ viewSpan.innerText = ' ' + (view.debuggingName || view.node.id);
806
+ this._inputView.node.innerHTML = '';
807
+ this._inputView.node.appendChild(arrowSpan);
808
+ this._inputView.node.appendChild(viewSpan);
809
+ }
810
+ });
811
+
812
+ // @endtype
813
+
814
+ // @type BMLayoutEditorSettingsSegmentCell extends BMLayoutEditorSettingsCell
815
+
816
+ /**
817
+ * A cell type specialized for displaying segment settings.
818
+ */
819
+ export function BMLayoutEditorSettingsSegmentCell() {} // <constructor>
820
+
821
+ BMLayoutEditorSettingsSegmentCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsCell.prototype), {
822
+ constructor: BMLayoutEditorSettingsSegmentCell,
823
+
824
+ /**
825
+ * The selected menu item, if there is one.
826
+ */
827
+ _currentMenuItem: undefined, // <BMMenuItem, nullable>
828
+
829
+ initWithCollectionView(collectionView, args) {
830
+ BMLayoutEditorSettingsCell.prototype.initWithCollectionView.apply(this, arguments);
831
+
832
+ this.contentView.CSSClass = 'BMLayoutEditorDetailsTabView BMLayoutEditorDetailsSegmentControl';
833
+
834
+ // Set up the constraints for the tab view
835
+ this.contentView.height.equalTo(48).isActive = YES;
836
+ this.contentView.width.equalTo(1024, {priority: 750}).isActive = YES;
837
+
838
+
839
+ return this;
840
+ },
841
+
842
+ // @override - BMCollectionViewCell
843
+ cellDidBindToSetting(setting) {
844
+ this.contentView.contentNode.innerHTML = '';
845
+ for (const menuItem of setting.options) {
846
+ // Create the clickable tabs
847
+ const tabNode = document.createElement('img');
848
+ tabNode.className = 'BMLayoutEditorDetailsTab BMCollectionViewCellEventHandler';
849
+ tabNode.src = menuItem.icon;
850
+ this.contentView.contentNode.appendChild(tabNode);
851
+
852
+ tabNode.title = menuItem.name;
853
+
854
+ // Default to showing the current tab
855
+ if (menuItem.userInfo == setting.target[setting.property]) {
856
+ this._currentMenuItem = menuItem;
857
+ tabNode.classList.add('BMLayoutEditorDetailsTabSelected');
858
+ }
859
+
860
+ menuItem._tabNode = tabNode;
861
+
862
+ // Switch between the tabs on click
863
+ tabNode.addEventListener('click', event => {
864
+ if (menuItem == this._currentMenuItem) return;
865
+ // Deselect the current tab
866
+ this._currentMenuItem._tabNode.classList.remove('BMLayoutEditorDetailsTabSelected');
867
+ // Then select the new one
868
+ this._currentMenuItem = menuItem;
869
+ tabNode.classList.add('BMLayoutEditorDetailsTabSelected');
870
+
871
+ setting.target[setting.property] = menuItem.userInfo;
872
+ });
873
+ }
874
+ }
875
+ });
876
+
877
+ // @endtype
878
+
879
+ // @type BMLayoutEditorSettingsBooleanCell extends BMLayoutEditorSettingsCell
880
+
881
+ /**
882
+ * A cell type specialized for displaying input settings.
883
+ */
884
+ export function BMLayoutEditorSettingsBooleanCell() {} // <constructor>
885
+
886
+ BMLayoutEditorSettingsBooleanCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsCell.prototype), {
887
+ constructor: BMLayoutEditorSettingsBooleanCell,
888
+
889
+ /**
890
+ * The view that displays this setting's title.
891
+ */
892
+ _titleView: undefined, // <BMView>
893
+ get titleView() {
894
+ return this._titleView;
895
+ },
896
+
897
+ /**
898
+ * The view that displays this setting's label node.
899
+ */
900
+ _labelView: undefined, // <BMView>
901
+ get labelView() {
902
+ return this._labelView;
903
+ },
904
+
905
+ _inputNode: undefined, // <DOMNode>
906
+
907
+ initWithCollectionView(collectionView, args) {
908
+ BMLayoutEditorSettingsCell.prototype.initWithCollectionView.apply(this, arguments);
909
+
910
+ const titleView = BMLayoutEditorSettingsTitleView.titleView();
911
+ this._titleView = titleView;
912
+ this.contentView.addSubview(titleView);
913
+ titleView.debuggingName = 'TitleView';
914
+ /*titleView.supportsAutomaticIntrinsicSize = YES;*/
915
+
916
+ titleView.leading.equalTo(this.leading, {plus: 8}).isActive = YES;
917
+ titleView.centerY.equalTo(this.centerY).isActive = YES;
918
+ /*titleView.node.className = 'BMLayoutEditorDetailsTitleCell';
919
+ titleView.node.style.textAlign = 'right';*/
920
+
921
+ const toggleContainer = BMView.view();
922
+ toggleContainer.debuggingName = 'BooleanContainer';
923
+ this.contentView.addSubview(toggleContainer);
924
+ toggleContainer.leading.equalTo(titleView.trailing, {plus: 8}).isActive = YES;
925
+ toggleContainer.trailing.equalTo(this.contentView.trailing, {plus: -8}).isActive = YES;
926
+ toggleContainer.top.equalTo(this.contentView.top, {plus: BMLayoutEdittorSettingCellRowSpacing}).isActive = YES;
927
+ toggleContainer.bottom.equalTo(this.contentView.bottom, {plus: -BMLayoutEdittorSettingCellRowSpacing}).isActive = YES;
928
+ toggleContainer.width.greaterThanOrEqualTo(200).isActive = YES;
929
+ toggleContainer.width.equalTo(titleView.width, {times: 1.5, priority: 600}).isActive = YES;
930
+
931
+ // Create the toggle
932
+ let value = document.createElement('label');
933
+ value.className = 'BMWindowSwitch';
934
+ value.style.margin = '0';
935
+
936
+ value.innerHTML = `<input type="checkbox" data-toggle="YES"> OFF
937
+ <div class="BMWindowSwitchGutter">
938
+ <div class="BMWindowSwitchKnob"></div>
939
+ </div>
940
+ ON`;
941
+
942
+ this._labelView = BMView.viewForNode(value);
943
+ toggleContainer.addSubview(this._labelView);
944
+ this._labelView.supportsAutomaticIntrinsicSize = YES;
945
+ this._labelView.debuggingName = 'BooleanSwitch';
946
+
947
+ this._labelView.centerY.equalTo(toggleContainer.centerY).isActive = YES;
948
+ this._labelView.leading.equalTo(toggleContainer.leading).isActive = YES;
949
+ this._labelView.width.equalTo(104).isActive = YES;
950
+ this._labelView.height.equalTo(20).isActive = YES;
951
+
952
+ // Set the value
953
+ let input = value.getElementsByTagName('input')[0];
954
+ this._inputNode = input;
955
+
956
+ // If the setting supports an ideterminate state, set it up
957
+ value.addEventListener('click', event => {
958
+ if (!this.setting.nullable) return;
959
+ if (event.target === input) return;
960
+ if (input.checked) {
961
+ event.preventDefault();
962
+ event.stopPropagation();
963
+ if (input.indeterminate) {
964
+ input.indeterminate = NO;
965
+ input.checked = NO;
966
+ this.inputValueDidChangeWithEvent(event, {toValue: NO});
967
+ }
968
+ else {
969
+ input.indeterminate = YES;
970
+ this.inputValueDidChangeWithEvent(event, {toValue: undefined});
971
+ }
972
+ }
973
+ });
974
+
975
+ input.addEventListener('change', event => {
976
+ let value = Boolean(event.target.checked);
977
+
978
+ // Read-only indicates an indeterminate state, if supported
979
+ if (this.setting.nullable && input.indeterminate) {
980
+ value = undefined;
981
+ }
982
+
983
+ this.inputValueDidChangeWithEvent(event, {toValue: value});
984
+ });
985
+
986
+ return this;
987
+ },
988
+
989
+ /**
990
+ * Invoked whenever the value of this cell's input element changes as a result of user action.
991
+ * This can either be due to the user typing content into the input element or pasting.
992
+ *
993
+ * The default implementation does nothing.
994
+ * @param event <Event> The event that triggered this action.
995
+ * @param toValue <Boolean, nullable> The new value.
996
+ */
997
+ inputValueDidChangeWithEvent(event, {toValue: value}) {
998
+ const setting = this.setting;
999
+ if (setting.sizeClass) {
1000
+ if (value === undefined) {
1001
+ setting.target[`remove${BMStringByCapitalizingString(setting.property)}VariationForSizeClass`](setting.sizeClass);
1002
+ }
1003
+ else {
1004
+ setting.target[`set${BMStringByCapitalizingString(setting.property)}`](value, {forSizeClass: setting.sizeClass});
1005
+ }
1006
+ }
1007
+ else {
1008
+ setting.target[setting.property] = value;
1009
+ }
1010
+ },
1011
+
1012
+ // @override - BMCollectionViewCell
1013
+ cellDidBindToSetting(setting) {
1014
+ let value;
1015
+ if (setting.sizeClass) {
1016
+ if (setting.target._variations[setting.sizeClass]) {
1017
+ value = setting.target._variations[setting.sizeClass][setting.property];
1018
+ }
1019
+ else {
1020
+ value = undefined;
1021
+ }
1022
+ }
1023
+ else {
1024
+ value = setting.target[setting.property];
1025
+ }
1026
+
1027
+ if (value === undefined && setting.nullable) {
1028
+ this._inputNode.indeterminate = YES;
1029
+ this._inputNode.checked = YES;
1030
+ }
1031
+ else {
1032
+ this._inputNode.indeterminate = NO;
1033
+ this._inputNode.checked = value;
1034
+ }
1035
+
1036
+ this._titleView.setting = setting;
1037
+ }
1038
+ })
1039
+
1040
+ // @endtype
1041
+
1042
+ // @type BMLayoutEditorSettingsDropdownCell extends BMLayoutEditorSettingsCell
1043
+
1044
+ /**
1045
+ * A cell type specialized for displaying enum settings using drop down boxes.
1046
+ */
1047
+ export function BMLayoutEditorSettingsDropdownCell() {} // <constructor>
1048
+
1049
+ BMLayoutEditorSettingsDropdownCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsCell.prototype), {
1050
+ constructor: BMLayoutEditorSettingsDropdownCell,
1051
+
1052
+ /**
1053
+ * The view that displays this setting's title.
1054
+ */
1055
+ _titleView: undefined,
1056
+ get titleView() {
1057
+ return this._titleView;
1058
+ },
1059
+
1060
+ /**
1061
+ * The view that displays this setting's input node.
1062
+ */
1063
+ _select: undefined,
1064
+ get inputView() {
1065
+ return this._select;
1066
+ },
1067
+
1068
+ /**
1069
+ * Set to `YES` while this cell is bound.
1070
+ */
1071
+ _bound: NO,
1072
+
1073
+ // @override - BMCollectionViewCell
1074
+ prepareForReuse() {
1075
+ this._bound = NO;
1076
+ },
1077
+
1078
+ // @override - BMCollectionViewCell
1079
+ prepareForDisplay() {
1080
+ this._bound = YES;
1081
+ },
1082
+
1083
+ initWithCollectionView(collectionView, args) {
1084
+ BMLayoutEditorSettingsCell.prototype.initWithCollectionView.apply(this, arguments);
1085
+
1086
+ const titleView = BMLayoutEditorSettingsTitleView.titleView();
1087
+ this._titleView = titleView;
1088
+ titleView.debuggingName = 'TitleView';
1089
+ this.contentView.addSubview(titleView);
1090
+
1091
+ titleView.leading.equalTo(this.leading, {plus: 8}).isActive = YES;
1092
+ titleView.centerY.equalTo(this.centerY).isActive = YES;
1093
+
1094
+ const inputNode = document.createElement('select');
1095
+ inputNode.className = 'BMWindowInput BMLayoutEditorDetailsCellInput BMLayoutEditorDetailsItemNumericValue BMLayoutEditorDetailsDropdown';
1096
+ const inputView = BMView.viewForNode(inputNode);
1097
+ inputView.debuggingName = 'DropdownView';
1098
+ this._inputView = inputView;
1099
+
1100
+ this.contentView.addSubview(inputView);
1101
+ inputView.leading.equalTo(titleView.trailing, {plus: 8}).isActive = YES;
1102
+ inputView.width.greaterThanOrEqualTo(200).isActive = YES;
1103
+ inputView.trailing.equalTo(this.trailing, {plus: -8}).isActive = YES;
1104
+ inputView.height.equalTo(24).isActive = YES;
1105
+ inputView.centerY.equalTo(this.centerY).isActive = YES;
1106
+ inputView.width.equalTo(titleView.width, {times: 1.5, priority: 600}).isActive = YES;
1107
+
1108
+ inputNode.addEventListener('change', event => {
1109
+ if (!this._bound) return;
1110
+
1111
+ this.inputValueDidChangeWithEvent(event);
1112
+ });
1113
+
1114
+ inputNode.addEventListener('focus', e => this.retain());
1115
+ inputNode.addEventListener('blur', e => this.release());
1116
+
1117
+ return this;
1118
+ },
1119
+
1120
+ /**
1121
+ * Invoked whenever the value of this cell's input element changes as a result of user action.
1122
+ * This can either be due to the user typing content into the input element or pasting.
1123
+ *
1124
+ * The default implementation does nothing.
1125
+ * @param event <Event> The event that triggered this action.
1126
+ */
1127
+ inputValueDidChangeWithEvent(event) {
1128
+ const setting = this.setting;
1129
+ const value = JSON.parse(this._inputView.node.value);
1130
+ if (setting.sizeClass) {
1131
+ setting.target[`set${BMStringByCapitalizingString(setting.property)}`](value, {forSizeClass: setting.sizeClass});
1132
+ }
1133
+ else {
1134
+ setting.target[setting.property] = value;
1135
+ }
1136
+ },
1137
+
1138
+ // @override - BMCollectionViewCell
1139
+ cellDidBindToSetting(setting) {
1140
+ this._inputView.node.innerHTML = '';
1141
+ for (const item of setting.options) {
1142
+ const option = document.createElement('option');
1143
+
1144
+ option.value = JSON.stringify(item.userInfo);
1145
+ option.innerText = item.name;
1146
+
1147
+ this._inputView.node.appendChild(option);
1148
+ }
1149
+ this._inputView.node.value = setting.sizeClass ? JSON.stringify(setting.target._variations[setting.sizeClass][setting.property]) : JSON.stringify(setting.target[setting.property]);
1150
+ this._titleView.setting = setting;
1151
+ }
1152
+ })
1153
+
1154
+ // @endtype
1155
+
1156
+ // @type BMLayoutEditorSettingsInputCell extends BMLayoutEditorSettingsCell
1157
+
1158
+ /**
1159
+ * A cell type specialized for displaying input settings.
1160
+ */
1161
+ export function BMLayoutEditorSettingsInputCell() {} // <constructor>
1162
+
1163
+ BMLayoutEditorSettingsInputCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsCell.prototype), {
1164
+ constructor: BMLayoutEditorSettingsInputCell,
1165
+
1166
+ /**
1167
+ * The view that displays this setting's title.
1168
+ */
1169
+ _titleView: undefined,
1170
+ get titleView() {
1171
+ return this._titleView;
1172
+ },
1173
+
1174
+ /**
1175
+ * The view that displays this setting's input node.
1176
+ */
1177
+ _inputView: undefined,
1178
+ get inputView() {
1179
+ return this._inputView;
1180
+ },
1181
+
1182
+ /**
1183
+ * Set to `YES` while this cell is bound.
1184
+ */
1185
+ _bound: NO,
1186
+
1187
+ // @override - BMCollectionViewCell
1188
+ prepareForReuse() {
1189
+ this._bound = NO;
1190
+ },
1191
+
1192
+ // @override - BMCollectionViewCell
1193
+ prepareForDisplay() {
1194
+ this._bound = YES;
1195
+ },
1196
+
1197
+ /**
1198
+ * Returns the value that indicates that the property should be "unset".
1199
+ *
1200
+ * The default implementation returns an empty string.
1201
+ */
1202
+ get nullValue() {
1203
+ return '';
1204
+ },
1205
+
1206
+ initWithCollectionView(collectionView, args) {
1207
+ BMLayoutEditorSettingsCell.prototype.initWithCollectionView.apply(this, arguments);
1208
+
1209
+ const titleView = BMLayoutEditorSettingsTitleView.titleView();
1210
+ this._titleView = titleView;
1211
+ this.contentView.addSubview(titleView);
1212
+ titleView.debuggingName = 'TitleView';
1213
+ // titleView.supportsAutomaticIntrinsicSize = YES;
1214
+
1215
+ titleView.leading.equalTo(this.leading, {plus: 8}).isActive = YES;
1216
+ titleView.centerY.equalTo(this.centerY).isActive = YES;
1217
+ // titleView.node.className = 'BMLayoutEditorDetailsTitleCell';
1218
+ // titleView.node.style.textAlign = 'right';
1219
+
1220
+ const inputNode = document.createElement('input');
1221
+ inputNode.className = 'BMWindowInput BMLayoutEditorDetailsCellInput ' + args.inputClasses;
1222
+ inputNode.type = args.inputKind;
1223
+ const inputView = BMView.viewForNode(inputNode);
1224
+ this._inputView = inputView;
1225
+ inputView.debuggingName = 'InputView';
1226
+
1227
+ this.contentView.addSubview(inputView);
1228
+ inputView.leading.equalTo(titleView.trailing, {plus: 8}).isActive = YES;
1229
+ inputView.width.greaterThanOrEqualTo(200).isActive = YES;
1230
+ inputView.trailing.equalTo(this.trailing, {plus: -8}).isActive = YES;
1231
+ inputView.centerY.equalTo(this.centerY).isActive = YES;
1232
+ inputView.height.equalTo(24).isActive = YES;
1233
+ inputView.width.equalTo(titleView.width, {times: 1.5, priority: 600}).isActive = YES;
1234
+
1235
+ inputNode.addEventListener('input', event => {
1236
+ if (!this._bound) return;
1237
+
1238
+ this.inputValueDidChangeWithEvent(event);
1239
+ });
1240
+
1241
+ inputNode.addEventListener('paste', event => {
1242
+ if (!this._bound) return;
1243
+
1244
+ this.inputValueDidChangeWithEvent(event);
1245
+ });
1246
+
1247
+ inputNode.addEventListener('focus', e => this.retain());
1248
+ inputNode.addEventListener('blur', e => this.release());
1249
+
1250
+ return this;
1251
+ },
1252
+
1253
+ /**
1254
+ * Returns the value that should be applied to the setting.
1255
+ * Subclasses can override this getter to perform any necessary transforms
1256
+ * on the filter value if needed.
1257
+ *
1258
+ * The default implementation returns the `value` property of this cell's input node.
1259
+ */
1260
+ get value() {
1261
+ return this._inputView.node.value;
1262
+ },
1263
+
1264
+ /**
1265
+ * Invoked whenever the value of this cell's input element changes as a result of user action.
1266
+ * This can either be due to the user typing content into the input element or pasting.
1267
+ *
1268
+ * The default implementation does nothing.
1269
+ * @param event <Event> The event that triggered this action.
1270
+ */
1271
+ inputValueDidChangeWithEvent(event) {
1272
+ const setting = this.setting;
1273
+ if (setting.sizeClass) {
1274
+ const value = this.value;
1275
+ if (value === undefined || value === '' || value === this.nullValue) {
1276
+ setting.target[`remove${BMStringByCapitalizingString(setting.property)}VariationForSizeClass`](setting.sizeClass);
1277
+ }
1278
+ else {
1279
+ setting.target[`set${BMStringByCapitalizingString(setting.property)}`](value, {forSizeClass: setting.sizeClass});
1280
+ }
1281
+ }
1282
+ else {
1283
+ setting.target[setting.property] = this.value;
1284
+ }
1285
+ },
1286
+
1287
+ // @override - BMCollectionViewCell
1288
+ cellDidBindToSetting(setting) {
1289
+ if (setting.sizeClass) {
1290
+ if (setting.target._variations[setting.sizeClass]) {
1291
+ if (!(setting.property in setting.target._variations[setting.sizeClass])) {
1292
+ this._inputView.node.value = this.nullValue;
1293
+ }
1294
+ else {
1295
+ this._inputView.node.value = setting.target._variations[setting.sizeClass][setting.property];
1296
+ }
1297
+ }
1298
+ else {
1299
+ this._inputView.node.value = this.nullValue;
1300
+ }
1301
+ }
1302
+ else {
1303
+ const value = setting.target[setting.property];
1304
+ if (value === undefined) {
1305
+ this._inputView.node.value = this.nullValue;
1306
+ }
1307
+ else {
1308
+ this._inputView.node.value = setting.target[setting.property];
1309
+ }
1310
+ }
1311
+
1312
+ this._titleView.setting = setting;
1313
+ }
1314
+ })
1315
+
1316
+ // @endtype
1317
+
1318
+ // @type BMLayoutEditorSettingsIntegerCell extends BMLayoutEditorSettingsInputCell
1319
+
1320
+ /**
1321
+ * A cell type specialized for displaying integer settings.
1322
+ */
1323
+ export function BMLayoutEditorSettingsIntegerCell() {} // <constructor>
1324
+
1325
+ BMLayoutEditorSettingsIntegerCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsInputCell.prototype), {
1326
+ constructor: BMLayoutEditorSettingsIntegerCell,
1327
+
1328
+ initWithCollectionView(collectionView, args) {
1329
+ args.inputKind = 'number';
1330
+ args.inputClasses = 'BMLayoutEditorDetailsItemNumericValue';
1331
+ BMLayoutEditorSettingsInputCell.prototype.initWithCollectionView.apply(this, arguments);
1332
+ this._inputView.node.step = 1;
1333
+ return this;
1334
+ },
1335
+
1336
+
1337
+ get value() {
1338
+ const value = parseInt(this._inputView.node.value, 10);
1339
+
1340
+ if (isNaN(value)) {
1341
+ return 0;
1342
+ }
1343
+
1344
+ return value;
1345
+ },
1346
+
1347
+ // @override - BMCollectionViewCell
1348
+ cellDidBindToSetting(setting) {
1349
+ BMLayoutEditorSettingsInputCell.prototype.cellDidBindToSetting.apply(this, arguments);
1350
+ }
1351
+ });
1352
+
1353
+ // @endtype
1354
+
1355
+ // @type BMLayoutEditorSettingsNumberCell extends BMLayoutEditorSettingsInputCell
1356
+
1357
+ /**
1358
+ * A cell type specialized for displaying number settings.
1359
+ */
1360
+ export function BMLayoutEditorSettingsNumberCell() {} // <constructor>
1361
+
1362
+ BMLayoutEditorSettingsNumberCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsInputCell.prototype), {
1363
+ constructor: BMLayoutEditorSettingsNumberCell,
1364
+
1365
+ initWithCollectionView(collectionView, args) {
1366
+ args.inputKind = 'number';
1367
+ args.inputClasses = 'BMLayoutEditorDetailsItemNumericValue';
1368
+ return BMLayoutEditorSettingsInputCell.prototype.initWithCollectionView.apply(this, arguments);
1369
+ },
1370
+
1371
+ get value() {
1372
+ const value = parseFloat(this._inputView.node.value);
1373
+
1374
+ if (isNaN(value)) {
1375
+ return 0;
1376
+ }
1377
+
1378
+ return value;
1379
+ },
1380
+
1381
+ // @override - BMCollectionViewCell
1382
+ cellDidBindToSetting(setting) {
1383
+ BMLayoutEditorSettingsInputCell.prototype.cellDidBindToSetting.apply(this, arguments);
1384
+ }
1385
+ });
1386
+
1387
+ // @endtype
1388
+
1389
+ // @type BMLayoutEditorSettingsStringCell extends BMLayoutEditorSettingsInputCell
1390
+
1391
+ /**
1392
+ * A cell type specialized for displaying string settings.
1393
+ */
1394
+ export function BMLayoutEditorSettingsStringCell() {} // <constructor>
1395
+
1396
+ BMLayoutEditorSettingsStringCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsInputCell.prototype), {
1397
+ constructor: BMLayoutEditorSettingsStringCell,
1398
+
1399
+ initWithCollectionView(collectionView, args) {
1400
+ args.inputKind = 'text';
1401
+ args.inputClasses = 'BMLayoutEditorDetailsItemTextValue';
1402
+ return BMLayoutEditorSettingsInputCell.prototype.initWithCollectionView.apply(this, arguments);
1403
+ },
1404
+
1405
+ // @override - BMCollectionViewCell
1406
+ cellDidBindToSetting(setting) {
1407
+ BMLayoutEditorSettingsInputCell.prototype.cellDidBindToSetting.apply(this, arguments);
1408
+ }
1409
+ })
1410
+
1411
+ // @endtype
1412
+
1413
+ // @type BMLayoutEditorSettingsConstantCell extends BMLayoutEditorSettingsInputCell
1414
+
1415
+ /**
1416
+ * A cell type specialized for displaying constraint constant settings.
1417
+ */
1418
+ export function BMLayoutEditorSettingsConstantCell() {} // <constructor>
1419
+
1420
+ BMLayoutEditorSettingsConstantCell.prototype = BMExtend(Object.create(BMLayoutEditorSettingsInputCell.prototype), {
1421
+ constructor: BMLayoutEditorSettingsConstantCell,
1422
+
1423
+ get nullValue() {
1424
+ return '';
1425
+ },
1426
+
1427
+ initWithCollectionView(collectionView, args) {
1428
+ args.inputKind = 'text';
1429
+ args.inputClasses = 'BMLayoutEditorDetailsItemTextValue';
1430
+ BMLayoutEditorSettingsInputCell.prototype.initWithCollectionView.apply(this, arguments);
1431
+
1432
+ /** @type {HTMLInputElement} */
1433
+ const inputNode = this._inputView.node;
1434
+ // This array contains all the available layout variables and is initialized upon the input
1435
+ // element acquiring keyboard focus
1436
+ let suggestions = [];
1437
+ // This array contains the layout variables that match the text contained within the input element
1438
+ let filteredSuggestions = [];
1439
+ // The popup menu displaying the filtered suggestions
1440
+ let menu;
1441
+ // The index of the top hit suggestion. And index of -1 represents no top hit.
1442
+ let highlightedItemIndex;
1443
+
1444
+ // TODO: Consider replacing this with a BMTextField
1445
+ inputNode.addEventListener('focus', async event => {
1446
+ // Select all content upon receiving focus
1447
+ inputNode.select();
1448
+
1449
+ suggestions = [];
1450
+ // Load up the variable names
1451
+ const variables = Object.keys(this.layoutEditor.layoutVariableProvider.layoutVariables);
1452
+ const uniqueNames = new Set;
1453
+ variables.forEach(variable => uniqueNames.add(variable));
1454
+
1455
+ let options = [];
1456
+
1457
+ // Create a pair of regular and minus-prefixed variable names
1458
+ for (const variable of uniqueNames) {
1459
+ suggestions.unshift(variable);
1460
+ suggestions.push('-' + variable);
1461
+ }
1462
+
1463
+ filteredSuggestions = suggestions;
1464
+ highlightedItemIndex = -1;
1465
+
1466
+ for (const option of suggestions) {
1467
+ options.push(this.layoutEditor.constraintOptionWithLabel(option, {action: () => {
1468
+ menu = undefined;
1469
+ inputNode.value = option;
1470
+ inputNode.dispatchEvent(new Event('input'));
1471
+
1472
+ // Remove focus from the text box upon selecting a value
1473
+ inputNode.blur();
1474
+ }}));
1475
+ }
1476
+
1477
+ // Create and show a pulldown menu below the constant textbox, if there are any suggestions
1478
+ if (suggestions.length) {
1479
+ if (this._delaysSuggestionsDropdown) {
1480
+ const menuContainer = this.layoutEditor._createMenuWithOptions(options);
1481
+ menu = menuContainer.children[0];
1482
+ setTimeout(() => {
1483
+ if (document.activeElement == inputNode) {
1484
+ let frame = BMRectMakeWithNodeFrame(inputNode);
1485
+ let point = BMPointMake(frame.origin.x, frame.bottom);
1486
+ this.layoutEditor.showMenuAtPoint(point, {menu: menuContainer, kind: BMMenuKind.PullDownMenu});
1487
+ }
1488
+ }, 200);
1489
+ }
1490
+ else {
1491
+ let frame = BMRectMakeWithNodeFrame(inputNode);
1492
+ let point = BMPointMake(frame.origin.x, frame.bottom);
1493
+ menu = this.layoutEditor.showMenuAtPoint(point, {withOptions: options, kind: BMMenuKind.PullDownMenu});
1494
+ }
1495
+ menu.classList.add('BMLayoutEditorConstantSuggestions');
1496
+
1497
+ menu.parentNode.classList.add('BMLayoutEditorConstantSuggestionsOverlay');
1498
+
1499
+ // Prevent the menu from stealing focus, which would dismiss it before an option is selected
1500
+ menu.addEventListener('mousedown', e => e.preventDefault(), YES);
1501
+ }
1502
+
1503
+ const fn = event => event.preventDefault();
1504
+ inputNode.addEventListener('mouseup', fn);
1505
+ // This timeout is necessary because otherwise regular clicks would instantly dismiss the menu
1506
+ await new Promise(resolve => setTimeout(resolve, 200));
1507
+ inputNode.removeEventListener('mouseup', fn);
1508
+
1509
+ });
1510
+
1511
+ inputNode.addEventListener('keydown', event => {
1512
+ if (event.code == 'ArrowDown' || event.code == 'ArrowUp') {
1513
+ event.preventDefault();
1514
+ if (filteredSuggestions.length == 0) return;
1515
+
1516
+ // Move the selection accordingly
1517
+ if (event.code == 'ArrowUp') {
1518
+ highlightedItemIndex = Math.max((highlightedItemIndex || 0) - 1, 0);
1519
+ }
1520
+ else {
1521
+ highlightedItemIndex = Math.min((isNaN(highlightedItemIndex) ? -1 : highlightedItemIndex) + 1, filteredSuggestions.length - 1);
1522
+ }
1523
+
1524
+ // Remove the highlighted class from the previously selected item
1525
+ for (const element of menu.querySelectorAll('.BMLayoutEditorConstraintPopupOptionHighlighted')) {
1526
+ element.classList.remove('BMLayoutEditorConstraintPopupOptionHighlighted');
1527
+ }
1528
+
1529
+ // Highlight the current item
1530
+ menu.childNodes[highlightedItemIndex].classList.add('BMLayoutEditorConstraintPopupOptionHighlighted');
1531
+ }
1532
+
1533
+ if (event.code == 'Enter') {
1534
+ // Upon pressing the enter key, select the currently highlighted item, if available
1535
+ // then remove focus from the constant box
1536
+ if (!isNaN(highlightedItemIndex) && filteredSuggestions.length) {
1537
+ inputNode.value = filteredSuggestions[highlightedItemIndex];
1538
+ inputNode.dispatchEvent(new Event('input'));
1539
+ }
1540
+
1541
+ inputNode.blur();
1542
+ }
1543
+ });
1544
+
1545
+ inputNode.addEventListener('input', event => {
1546
+ const view = this.tab._settingsPanel._referenceView;
1547
+ const constraint = this._setting.target;
1548
+
1549
+ // Note that these are currently static, but this may be used in the future for custom constraint or view properties as well at which point they may become
1550
+ // editable
1551
+ const property = 'constant';
1552
+ const capitalizedPropertyName = 'Constant';
1553
+
1554
+ if (!view || !constraint) return;
1555
+
1556
+ const number = parseFloat(inputNode.value);
1557
+ const constantValue = isNaN(number) ? inputNode.value : number;
1558
+
1559
+ if (menu) {
1560
+ // If the menu is visible, filter it accordingly
1561
+ filteredSuggestions = suggestions.filter(e => e.toLowerCase().startsWith(inputNode.value.toLowerCase()));
1562
+
1563
+ // Empty the menu
1564
+ menu.innerHTML = '';
1565
+
1566
+ // Reset the highlighted item index
1567
+ highlightedItemIndex = 0;
1568
+
1569
+ // Then add the filtered options
1570
+ for (const option of filteredSuggestions) {
1571
+ menu.appendChild(this.layoutEditor.constraintOptionWithLabel(option, {action: () => {
1572
+ menu = undefined;
1573
+ inputNode.value = option;
1574
+ inputNode.dispatchEvent(new Event('input'));
1575
+ }}));
1576
+ }
1577
+
1578
+ // Highlight the first item
1579
+ if (filteredSuggestions.length) {
1580
+ menu.childNodes[0].classList.add('BMLayoutEditorConstraintPopupOptionHighlighted');
1581
+ menu.style.display = 'block';
1582
+ }
1583
+ else {
1584
+ menu.style.display = 'none';
1585
+ }
1586
+ }
1587
+
1588
+ if (!inputNode.value) {
1589
+ if (args.sizeClass) {
1590
+ if (constraint) {
1591
+ constraint[`remove${capitalizedPropertyName}VariationForSizeClass`](args.sizeClass);
1592
+ constraint._updateConfiguration();
1593
+ }
1594
+ else {
1595
+ view[`remove${capitalizedPropertyName}VariationForSizeClass`](args.sizeClass);
1596
+ view._updateConfiguration();
1597
+ }
1598
+ }
1599
+ return;
1600
+ }
1601
+
1602
+ if (constraint) {
1603
+
1604
+ if (this.setting.sizeClass) {
1605
+ constraint[`set${capitalizedPropertyName}`](constantValue, {forSizeClass: this.setting.sizeClass});
1606
+ }
1607
+ else {
1608
+ constraint[property] = constantValue;
1609
+ }
1610
+ constraint._updateConfiguration();
1611
+ constraint._constraint = undefined;
1612
+ constraint._constituentConstraints = undefined;
1613
+ if (constraint._sourceView) {
1614
+ constraint._sourceView.needsLayout = YES;
1615
+ }
1616
+ else {
1617
+ constraint._views[0].needsLayout = YES;
1618
+ }
1619
+ }
1620
+ else {
1621
+ if (this.setting.sizeClass) {
1622
+ view[`set${capitalizedPropertyName}`](number, {forSizeClass: this.setting.sizeClass});
1623
+ }
1624
+ else {
1625
+ view[property] = number;
1626
+ }
1627
+ view.needsLayout = YES;
1628
+ }
1629
+ });
1630
+
1631
+ // Upon the value box losing focus, dismiss the menu, if it was open
1632
+ // unless the newly focused item is a descendant of the menu
1633
+ inputNode.addEventListener('blur', event => {
1634
+ if (menu) {
1635
+ menu.parentNode.dispatchEvent(new Event('click'));
1636
+ }
1637
+ });
1638
+
1639
+ return this;
1640
+ },
1641
+
1642
+ // @override - BMCollectionViewCell
1643
+ cellDidBindToSetting(setting) {
1644
+ BMLayoutEditorSettingsInputCell.prototype.cellDidBindToSetting.apply(this, arguments);
1645
+ },
1646
+
1647
+ // @override - BMLayoutEditorSettingCell
1648
+ inputValueDidChangeWithEvent(event) {
1649
+ // This cell has a custom implementation, therefore this method is a no op
1650
+ }
1651
+ })
1652
+
1653
+ // @endtype