neo.mjs 6.4.2 → 6.5.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.
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.4.2'
23
+ * @member {String} version='6.5.0'
24
24
  */
25
- version: '6.4.2'
25
+ version: '6.5.0'
26
26
  }
27
27
 
28
28
  /**
@@ -20,7 +20,7 @@ class Page4 extends FormPageContainer {
20
20
  groupRequired : true,
21
21
  labelText : null,
22
22
  labelWidth : 70,
23
- name : 'fruits',
23
+ name : 'my.fruits[0].basket',
24
24
  showErrorTexts: false
25
25
  },
26
26
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.4.2'
23
+ * @member {String} version='6.5.0'
24
24
  */
25
- version: '6.4.2'
25
+ version: '6.5.0'
26
26
  }
27
27
 
28
28
  /**
@@ -83,6 +83,27 @@ class MainContainer extends ConfigurationViewport {
83
83
  labelText: 'sortable',
84
84
  listeners: {change: me.onConfigChange.bind(me, 'sortable')},
85
85
  style : {marginTop: '10px'}
86
+ }, {
87
+ module : Checkbox,
88
+ checked : false,
89
+ labelText: 'Fit width',
90
+ listeners: {
91
+ change({ value }) {
92
+ const { style } = me.exampleComponent;
93
+
94
+ if (value) {
95
+ style.width = '100%';
96
+ style.tableLayout = 'fixed';
97
+ } else {
98
+ style.width = '';
99
+ style.tableLayout = '';
100
+ }
101
+
102
+ me.exampleComponent.style = style;
103
+ me.exampleComponent.update();
104
+ }
105
+ },
106
+ style : {marginTop: '10px'}
86
107
  }];
87
108
  }
88
109
 
@@ -102,16 +123,43 @@ class MainContainer extends ConfigurationViewport {
102
123
  {dataField: 'country', text: 'Country'},
103
124
  {
104
125
  text: 'Edit Action',
105
- renderer: data => {
106
- let button = Neo.create({
107
- module : Button,
108
- appName : this.appName,
109
- handler : this.editButtonHandler,
110
- parentId: 'myTableStoreContainer',
111
- text : 'Edit'
112
- });
126
+ renderer: ({ column, index }) => {
127
+ const
128
+ widgetId = `${column.id}-widget-${index}`,
129
+ button = (column.widgetMap || (column.widgetMap = {}))[widgetId] || (column.widgetMap[widgetId] = Neo.create({
130
+ module : Button,
131
+ appName : this.appName,
132
+ handler : this.editButtonHandler,
133
+ parentId: 'myTableStoreContainer',
134
+ text : 'Edit'
135
+ }));
136
+
137
+ return button.vdom;
138
+ }
139
+ },
140
+ {
141
+ text : 'Menu',
142
+ renderer({ column, record, index }) {
143
+ const
144
+ widgetId = `${column.id}-widget-${index}`,
145
+ button = (column.widgetMap || (column.widgetMap = {}))[widgetId] || (column.widgetMap[widgetId] = Neo.create('Neo.button.Base', {
146
+ ntype : 'button',
147
+ appName : this.appName,
148
+ text : '\u22ee',
149
+ menu : {
150
+ items : [{
151
+ text : 'Menu option 1'
152
+ }, {
153
+ text : 'Menu option 2'
154
+ }, {
155
+ text : 'Menu option 3'
156
+ }, {
157
+ text : 'Menu option 4'
158
+ }]
159
+ }
160
+ }));
113
161
 
114
- return button.vdom
162
+ return button.vdom;
115
163
  }
116
164
  }
117
165
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "6.4.2",
3
+ "version": "6.5.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,8 +1,9 @@
1
1
  .neo-floating {
2
- top : -10000px;
3
- left : -10000px;
4
- position : fixed;
5
- z-index : 1000;
2
+ top : -10000px;
3
+ left : -10000px;
4
+ position : fixed;
5
+ z-index : 1000;
6
+ background-color : var(--neo-background-color);
6
7
  }
7
8
 
8
9
  // Shadow at the top
@@ -236,12 +236,12 @@ const DefaultConfig = {
236
236
  useVdomWorker: true,
237
237
  /**
238
238
  * buildScripts/injectPackageVersion.mjs will update this value
239
- * @default '6.4.2'
239
+ * @default '6.5.0'
240
240
  * @memberOf! module:Neo
241
241
  * @name config.version
242
242
  * @type String
243
243
  */
244
- version: '6.4.2'
244
+ version: '6.5.0'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
@@ -81,7 +81,7 @@ class EditEventContainer extends FormContainer {
81
81
  super.construct(config);
82
82
 
83
83
  // focus trap, see: https://github.com/neomjs/neo/issues/2306
84
- this.vdom.tabIndex = -1;
84
+ this.vdom.tabIndex = -1
85
85
  }
86
86
 
87
87
  /**
@@ -92,7 +92,7 @@ class EditEventContainer extends FormContainer {
92
92
  */
93
93
  afterSetMounted(value, oldValue) {
94
94
  super.afterSetMounted(value, oldValue);
95
- value && this.getField('title').focus();
95
+ value && this.getField('title').then(field => field.focus())
96
96
  }
97
97
 
98
98
  /**
@@ -106,17 +106,17 @@ class EditEventContainer extends FormContainer {
106
106
  let me = this,
107
107
  timeFormat = me.intlFormat_time;
108
108
 
109
- me.getField('endTime') .minValue = me.getEndTimeMinValue(value);
110
- me.getField('startTime').maxValue = me.getStartTimeMaxValue(value);
109
+ me.getField('endTime') .then(field => field.minValue = me.getEndTimeMinValue(value));
110
+ me.getField('startTime').then(field => field.maxValue = me.getStartTimeMaxValue(value));
111
111
 
112
112
  me.reset({
113
113
  calendarId: value.calendarId,
114
114
  endTime : timeFormat.format(value.endDate),
115
115
  startTime : timeFormat.format(value.startDate),
116
116
  title : value.title
117
- });
117
+ })
118
118
  } else if (value) {
119
- this.createItems();
119
+ this.createItems()
120
120
  }
121
121
  }
122
122
 
@@ -205,7 +205,7 @@ class EditEventContainer extends FormContainer {
205
205
  text : 'Delete'
206
206
  }];
207
207
 
208
- super.createItems();
208
+ super.createItems()
209
209
  }
210
210
  }
211
211
 
@@ -256,13 +256,13 @@ class EditEventContainer extends FormContainer {
256
256
  /**
257
257
  * @param {Object} data
258
258
  */
259
- onFocusLeave(data) {
259
+ async onFocusLeave(data) {
260
260
  let me = this;
261
261
 
262
262
  // we need a short delay, since a TimeField picker could be open
263
- setTimeout(() => {
264
- me.mounted && me.unmount();
265
- }, 100);
263
+ await me.timeout(100);
264
+
265
+ me.mounted && me.unmount()
266
266
  }
267
267
 
268
268
  /**
@@ -282,9 +282,9 @@ class EditEventContainer extends FormContainer {
282
282
  record[field] = date;
283
283
 
284
284
  if (name === 'endTime') {
285
- me.getField('startTime').maxValue = me.getStartTimeMaxValue(record);
285
+ me.getField('startTime').then(field => field.maxValue = me.getStartTimeMaxValue(record))
286
286
  } else {
287
- me.getField('endTime') .minValue = me.getEndTimeMinValue(record);
287
+ me.getField('endTime') .then(field => field.minValue = me.getEndTimeMinValue(record))
288
288
  }
289
289
  }
290
290
 
@@ -349,23 +349,6 @@ class Base extends CoreBase {
349
349
  return Neo.apps[this.appName] || null
350
350
  }
351
351
 
352
- /**
353
- * Convenience method
354
- * @returns {Boolean}
355
- */
356
- get isVdomUpdating() {
357
- // The VDOM is being updated if we have the promise that executeVdomUpdate uses
358
- return Boolean(this.vdomUpdate);
359
- }
360
- // Allow the Component to be set to the isVdomUpdating state
361
- set isVdomUpdating(isVdomUpdating) {
362
- isVdomUpdating = Boolean(isVdomUpdating);
363
-
364
- if (isVdomUpdating !== this.isVdomUpdating) {
365
- this.vdomUpdate = isVdomUpdating;
366
- }
367
- }
368
-
369
352
  /**
370
353
  * Apply component based listeners
371
354
  * @member {Object} listeners={}
@@ -967,7 +950,7 @@ class Base extends CoreBase {
967
950
  // Just a simple 't-b'
968
951
  if (typeof align === 'string') {
969
952
  align = {
970
- edgeAlign : align
953
+ edgeAlign: align
971
954
  };
972
955
  }
973
956
  // merge the incoming alignment specification into the configured default
@@ -987,8 +970,8 @@ class Base extends CoreBase {
987
970
  /**
988
971
  * Triggered before the controller config gets changed.
989
972
  * Creates a controller.Component instance if needed.
990
- * @param {Object} value
991
- * @param {Object} oldValue
973
+ * @param {Neo.controller.Component|Object} value
974
+ * @param {Neo.controller.Component|null} oldValue
992
975
  * @returns {Neo.controller.Component}
993
976
  * @protected
994
977
  */
@@ -1105,7 +1088,14 @@ class Base extends CoreBase {
1105
1088
  return (Neo.isNumber(oldValue) && oldValue > 0) ? (oldValue - 1) : 0
1106
1089
  }
1107
1090
 
1108
- beforeSetStyle(value) {
1091
+ /**
1092
+ * Triggered before the style config gets changed.
1093
+ * @param {Object} value
1094
+ * @param {Object} oldValue
1095
+ * @returns {Object}
1096
+ * @protected
1097
+ */
1098
+ beforeSetStyle(value, oldValue) {
1109
1099
  let me = this;
1110
1100
 
1111
1101
  if (typeof value === 'object') {
@@ -1241,26 +1231,20 @@ class Base extends CoreBase {
1241
1231
  opts.appName = me.appName
1242
1232
  }
1243
1233
 
1244
- /**
1245
- * If a VDOM update is in flight, this is the Promise that will resolve when
1246
- * the update is completed.
1247
- * @member {Promise|null} vdomUpdate
1248
- * @protected
1249
- */
1250
- me.vdomUpdate = Neo.vdom.Helper.update(opts);
1234
+ me.isVdomUpdating = true;
1251
1235
 
1252
1236
  // we can not set the config directly => it could already be false,
1253
1237
  // and we still want to pass it further into subtrees
1254
1238
  me._needsVdomUpdate = false;
1255
1239
  me.afterSetNeedsVdomUpdate?.(false, true)
1256
1240
 
1257
- me.vdomUpdate.catch(err => {
1258
- me.vdomUpdate = null;
1241
+ Neo.vdom.Helper.update(opts).catch(err => {
1242
+ me.isVdomUpdating = false;
1259
1243
  console.log('Error attempting to update component dom', err, me);
1260
1244
 
1261
1245
  reject?.()
1262
1246
  }).then(data => {
1263
- me.vdomUpdate = null;
1247
+ me.isVdomUpdating = false;
1264
1248
  // checking if the component got destroyed before the update cycle is done
1265
1249
  if (me.id) {
1266
1250
  // console.log('Component vnode updated', data);
@@ -1276,9 +1260,7 @@ class Base extends CoreBase {
1276
1260
  me.resolveVdomUpdate(resolve)
1277
1261
  }
1278
1262
  }
1279
- });
1280
-
1281
- return me.vdomUpdate;
1263
+ })
1282
1264
  }
1283
1265
 
1284
1266
  /**
@@ -1351,26 +1333,31 @@ class Base extends CoreBase {
1351
1333
  * @returns {Promise<Neo.util.Rectangle>}
1352
1334
  */
1353
1335
  async getDomRect(id=this.id, appName=this.appName) {
1354
- const
1355
- {
1356
- x,
1357
- y,
1358
- width,
1359
- height,
1360
- minWidth,
1361
- minHeight
1362
- } = await Neo.main.DomAccess.getBoundingClientRect({appName, id}),
1363
- result = new Rectangle(x, y, width, height);
1364
-
1365
- if (minWidth) {
1366
- result.minWidth = minWidth;
1336
+ if (Array.isArray(id)) {
1337
+ return await Neo.main.DomAccess.getBoundingClientRect({appName, id});
1367
1338
  }
1339
+ else {
1340
+ const
1341
+ {
1342
+ x,
1343
+ y,
1344
+ width,
1345
+ height,
1346
+ minWidth,
1347
+ minHeight
1348
+ } = await Neo.main.DomAccess.getBoundingClientRect({appName, id}),
1349
+ result = new Rectangle(x, y, width, height);
1350
+
1351
+ if (minWidth) {
1352
+ result.minWidth = minWidth;
1353
+ }
1368
1354
 
1369
- if (minHeight) {
1370
- result.minHeight = minHeight;
1371
- }
1355
+ if (minHeight) {
1356
+ result.minHeight = minHeight;
1357
+ }
1372
1358
 
1373
- return result;
1359
+ return result;
1360
+ }
1374
1361
  }
1375
1362
 
1376
1363
  /**
@@ -2284,10 +2271,10 @@ class Base extends CoreBase {
2284
2271
  }
2285
2272
 
2286
2273
  if (
2287
- mounted
2288
- && vnode
2289
- && !me.needsParentUpdate(me.parentId, resolve)
2274
+ !me.needsParentUpdate(me.parentId, resolve)
2290
2275
  && !me.isParentVdomUpdating(me.parentId, resolve)
2276
+ && mounted
2277
+ && vnode
2291
2278
  ) {
2292
2279
  me.#executeVdomUpdate(vdom, vnode, resolve, reject)
2293
2280
  }
@@ -1,6 +1,7 @@
1
1
  import BaseContainer from '../container/Base.mjs';
2
2
  import BaseField from '../form/field/Base.mjs';
3
3
  import ComponentManager from '../manager/Component.mjs';
4
+ import NeoArray from '../util/Array.mjs';
4
5
 
5
6
  /**
6
7
  * @class Neo.form.Container
@@ -35,25 +36,29 @@ class Container extends BaseContainer {
35
36
  * The logic assumes that field config values must not be objects (separation between the key & value realm).
36
37
  * @param {Object} values
37
38
  * @param {String} configName
39
+ * @param {String[]} fieldPaths
40
+ * @param {String} currentPath=''
38
41
  */
39
- static adjustTreeLeaves(values={}, configName) {
40
- let assign,type;
42
+ static adjustTreeLeaves(values={}, configName, fieldPaths, currentPath='') {
43
+ let assign, newPath, type;
41
44
 
42
45
  Object.entries(values).forEach(([key, value]) => {
43
- assign = true;
44
- type = Neo.typeOf(value);
46
+ assign = true;
47
+ newPath = currentPath === '' ? key : `${currentPath}.${key}`;
48
+ type = Neo.typeOf(value);
45
49
 
46
- if (type === 'Array') {
47
- assign = false;
50
+ if (type === 'Array' || type === 'Object') {
51
+ assign = fieldPaths.includes(newPath);
48
52
 
49
- value.forEach(item => {
50
- if (Neo.typeOf(item) === 'Object') {
51
- this.adjustTreeLeaves(item, configName)
52
- }
53
- })
54
- } else if (type === 'Object') {
55
- assign = false;
56
- this.adjustTreeLeaves(value, configName)
53
+ if (type === 'Array') {
54
+ value.forEach((item, index) => {
55
+ if (Neo.typeOf(item) === 'Object') {
56
+ this.adjustTreeLeaves(item, configName, fieldPaths, `${newPath}[${index}]`)
57
+ }
58
+ })
59
+ } else if (type === 'Object') {
60
+ this.adjustTreeLeaves(value, configName, fieldPaths, newPath)
61
+ }
57
62
  }
58
63
 
59
64
  if (assign) {
@@ -307,7 +312,16 @@ class Container extends BaseContainer {
307
312
  * @param {Boolean} suspendEvents=false
308
313
  */
309
314
  async setValues(values={}, suspendEvents=false) {
310
- Container.adjustTreeLeaves(values, 'value');
315
+ let fields = await this.getFields(),
316
+ fieldPaths = [];
317
+
318
+ // Grouped CheckBoxes & Radios can have the same path
319
+ // => using NeoArray to ensure they only get added once
320
+ fields.map(field => NeoArray.add(fieldPaths, field.getPath()));
321
+
322
+ values = Neo.clone(values, true);
323
+
324
+ Container.adjustTreeLeaves(values, 'value', fieldPaths);
311
325
 
312
326
  await this.setConfigs(values, suspendEvents)
313
327
  }
package/src/grid/View.mjs CHANGED
@@ -83,6 +83,7 @@ class View extends Component {
83
83
  }
84
84
 
85
85
  rendererOutput = column.renderer.call(column.rendererScope || container, {
86
+ column,
86
87
  field: column.field,
87
88
  index: i,
88
89
  record,
@@ -102,9 +102,7 @@ class DomAccess extends Base {
102
102
  construct(config) {
103
103
  super.construct(config);
104
104
 
105
- const
106
- me = this,
107
- syncAligns = me.syncAligns.bind(me);
105
+ const me = this;
108
106
 
109
107
  if (Neo.config.renderCountDeltas) {
110
108
  let node;
@@ -122,7 +120,7 @@ class DomAccess extends Base {
122
120
 
123
121
  // Set up our aligning callback which is called when things change which may
124
122
  // mean that alignments need to be updated.
125
- me.syncAligns = () => requestAnimationFrame(syncAligns);
123
+ me.syncAligns = me.syncAligns.bind(me);
126
124
  }
127
125
 
128
126
  /**
@@ -211,8 +209,14 @@ class DomAccess extends Base {
211
209
  me.resetDimensions(align);
212
210
 
213
211
  // The Rectangle's align spec target and constrainTo must be Rectangles
214
- align.target = me.getBoundingClientRect({ id : data.targetElement = me.getElementOrBody(data.target) });
215
- data.offsetParent = data.targetElement.offsetParent
212
+ align.target = me.getClippedRect({ id : data.targetElement = me.getElementOrBody(data.target) });
213
+
214
+ if (!align.target) {
215
+ // Set the Component with id data.id to hidden : true
216
+ return Neo.worker.App.setConfigs({ id : data.id, hidden : true });
217
+ }
218
+
219
+ data.offsetParent = data.targetElement.offsetParent;
216
220
  if (constrainTo) {
217
221
  align.constrainTo = me.getBoundingClientRect({ id : data.constrainToElement = me.getElementOrBody(constrainTo) });
218
222
  }
@@ -338,13 +342,9 @@ class DomAccess extends Base {
338
342
  let returnData;
339
343
 
340
344
  if (Array.isArray(data.id)) {
341
- returnData = [];
342
-
343
- data.id.forEach(id => {
344
- returnData.push(this.getBoundingClientRect({id: id}));
345
- });
345
+ return data.id.map(id => this.getBoundingClientRect({ id }));
346
346
  } else {
347
- let node = this.getElementOrBody(data.id),
347
+ let node = this.getElementOrBody(data.nodeType ? data : data.id),
348
348
  rect = {}, style, minWidth, minHeight;
349
349
 
350
350
  returnData = {};
@@ -352,7 +352,7 @@ class DomAccess extends Base {
352
352
  if (node) {
353
353
  rect = node.getBoundingClientRect();
354
354
  style = node.ownerDocument.defaultView.getComputedStyle(node);
355
- minWidth = style.getPropertyValue('min-width'),
355
+ minWidth = style.getPropertyValue('min-width');
356
356
  minHeight = style.getPropertyValue('min-height');
357
357
 
358
358
  // DomRect does not support spreading => {...DomRect} => {}
@@ -373,6 +373,20 @@ class DomAccess extends Base {
373
373
  return returnData;
374
374
  }
375
375
 
376
+ getClippedRect(data) {
377
+ let node = this.getElement(typeof data === 'object' ? data.id : data),
378
+ { defaultView } = node.ownerDocument,
379
+ rect = this.getBoundingClientRect(node);
380
+
381
+ for (let parentElement = node.offsetParent; rect && parentElement !== document.documentElement; parentElement = parentElement.parentElement) {
382
+ if (defaultView.getComputedStyle(parentElement).getPropertyValue('overflow') !== 'visible') {
383
+ rect = rect.intersects(this.getBoundingClientRect(parentElement));
384
+ }
385
+ }
386
+
387
+ return rect;
388
+ }
389
+
376
390
  onDocumentMutation(mutations) {
377
391
  const me = this;
378
392
 
@@ -67,6 +67,7 @@ class View extends Component {
67
67
  }
68
68
 
69
69
  rendererOutput = column.renderer.call(column.rendererScope || tableContainer, {
70
+ column,
70
71
  dataField,
71
72
  index,
72
73
  record,
@@ -273,11 +273,11 @@ export default class Rectangle extends DOMRect {
273
273
  top = Math.max(me.y, other.y),
274
274
  right = Math.min(me.x + me.width, other.x + other.width),
275
275
  bottom = Math.min(me.y + me.height, other.y + other.height);
276
-
276
+
277
277
  if (left >= right || top >= bottom) {
278
278
  return false;
279
279
  }
280
-
280
+
281
281
  return new Rectangle(left, top, right - left, bottom - top);
282
282
  }
283
283
  // We're dealing with a point here - zero dimensions
@@ -300,8 +300,8 @@ export default class Rectangle extends DOMRect {
300
300
 
301
301
  /**
302
302
  * Returns a clone of this Rectangle expanded according to the edges array.
303
- * @param {Number}Number[]} edges
304
- * @returns
303
+ * @param {Number}Number[]} edges
304
+ * @returns {Rectangle}
305
305
  */
306
306
  expand(edges) {
307
307
  edges = parseEdgeValue(edges);
@@ -323,7 +323,7 @@ export default class Rectangle extends DOMRect {
323
323
 
324
324
  /**
325
325
  * Returns `true` if this Rectangle completely contains the other Rectangle
326
- * @param {Rectangle} other
326
+ * @param {Rectangle} other
327
327
  */
328
328
  contains(other) {
329
329
  return this.constructor.includes(this, other);
@@ -362,10 +362,6 @@ export default class Rectangle extends DOMRect {
362
362
  alignTo(align) {
363
363
  const
364
364
  me = this,
365
- {
366
- minWidth,
367
- minHeight
368
- } = me,
369
365
  {
370
366
  constrainTo, // Element or Rectangle result must fit into
371
367
  target, // Element or Rectangle to align to
@@ -391,7 +387,7 @@ export default class Rectangle extends DOMRect {
391
387
  myPoint = result.getAnchorPoint(edges.ourEdgeZone, edges.ourEdgeOffset, edges.ourEdgeUnit),
392
388
  targetPoint = targetRect.getAnchorPoint(edges.theirEdgeZone, edges.theirEdgeOffset, edges.theirEdgeUnit, targetMargin),
393
389
  vector = [targetPoint[0] - myPoint[0], targetPoint[1] - myPoint[1]];
394
-
390
+
395
391
  result = result.moveBy(vector);
396
392
 
397
393
  // A useful property in the resulting rectangle which specifies which zone of the target
@@ -453,17 +449,15 @@ export default class Rectangle extends DOMRect {
453
449
  edgeAlign : createReversedEdgeAlign(edges)
454
450
  }
455
451
 
456
- // Fall back to the other two zones if we are allowed to
457
- if (axisLock === 'flexible') {
458
- zonesToTry.push({
459
- zone : zone = (alignSpec.startZone + 1) % 4,
460
- edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}`
461
- });
462
- zonesToTry.push({
463
- zone : zone = (zone + 2) % 4,
464
- edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}`
465
- });
466
- }
452
+ // Fall back to the other two zones.
453
+ zonesToTry.push({
454
+ zone : zone = (alignSpec.startZone + 1) % 4,
455
+ edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}`
456
+ });
457
+ zonesToTry.push({
458
+ zone : zone = (zone + 2) % 4,
459
+ edgeAlign : `${oppositeEdge[zoneEdges[zone]]}-${zoneEdges[zone]}`
460
+ });
467
461
  }
468
462
  else {
469
463
  // go through the other zones in order
@@ -536,6 +530,11 @@ export default class Rectangle extends DOMRect {
536
530
  }
537
531
  }
538
532
 
533
+ // Add the configurable finishing touch.
534
+ if (offset) {
535
+ result.moveBy(offset);
536
+ }
537
+
539
538
  return result;
540
539
  }
541
540
 
@@ -566,7 +565,7 @@ export default class Rectangle extends DOMRect {
566
565
  }
567
566
 
568
567
  equals(other) {
569
- return other instanceof DOMRect &&
568
+ return other instanceof DOMRect &&
570
569
  other.x === this.x &&
571
570
  other.y === this.y &&
572
571
  other.height === this.height &&
@@ -28,7 +28,8 @@ class App extends Base {
28
28
  remote: {
29
29
  main: [
30
30
  'createNeoInstance',
31
- 'destroyNeoInstance'
31
+ 'destroyNeoInstance',
32
+ 'setConfigs'
32
33
  ]
33
34
  },
34
35
  /**
@@ -386,6 +387,24 @@ class App extends Base {
386
387
  me.themeFilesCache = []
387
388
  }
388
389
 
390
+ /**
391
+ * Set configs of any app realm based Neo instance from main
392
+ * @param {Object} data
393
+ * @param {String} data.id
394
+ */
395
+ setConfigs(data) {
396
+ let instance = Neo.get(data.id);
397
+
398
+ if (instance) {
399
+ delete data.id;
400
+ instance.set(data);
401
+
402
+ return true
403
+ }
404
+
405
+ return false
406
+ }
407
+
389
408
  /**
390
409
  * @param {Object} data
391
410
  * @param {String} data.key