neo.mjs 8.14.3 → 8.16.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='8.14.3'
23
+ * @member {String} version='8.16.0'
24
24
  */
25
- version: '8.14.3'
25
+ version: '8.16.0'
26
26
  }
27
27
 
28
28
  /**
@@ -16,7 +16,7 @@
16
16
  "@type": "Organization",
17
17
  "name": "Neo.mjs"
18
18
  },
19
- "datePublished": "2025-02-04",
19
+ "datePublished": "2025-02-06",
20
20
  "publisher": {
21
21
  "@type": "Organization",
22
22
  "name": "Neo.mjs"
@@ -107,7 +107,7 @@ class FooterContainer extends Container {
107
107
  }, {
108
108
  module: Component,
109
109
  cls : ['neo-version'],
110
- html : 'v8.14.3'
110
+ html : 'v8.16.0'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -1,4 +1,3 @@
1
- import Component from '../../../src/component/Base.mjs';
2
1
  import ComponentController from '../../../src/controller/Component.mjs';
3
2
  import ComponentManager from '../../../src/manager/Component.mjs';
4
3
  import DemoDialog from './DemoDialog.mjs';
@@ -127,14 +126,15 @@ class MainContainerController extends ComponentController {
127
126
  * @param {Object} proxyRect
128
127
  */
129
128
  dropDialogBetweenWindows(proxyRect) {
130
- let me = this,
131
- dialog = me.dialog,
132
- intersection = Rectangle.getIntersectionDetails(me.dragStartWindowRect, proxyRect),
133
- side = me.dockedWindowSide,
134
- size = proxyRect.height * proxyRect.width,
129
+ let me = this,
130
+ dialog = me.dialog,
131
+ intersection = Rectangle.getIntersection(me.dragStartWindowRect, proxyRect),
132
+ intersectionSize = intersection?.height * intersection?.width,
133
+ side = me.dockedWindowSide,
134
+ size = proxyRect.height * proxyRect.width,
135
135
  wrapperStyle;
136
136
 
137
- if (intersection.area > size / 2) { // drop the dialog fully into the dragStart window
137
+ if (intersectionSize > size / 2) { // drop the dialog fully into the dragStart window
138
138
  me.destroyDockedWindowProxy();
139
139
 
140
140
  wrapperStyle = dialog.wrapperStyle;
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.14.3'
23
+ * @member {String} version='8.16.0'
24
24
  */
25
- version: '8.14.3'
25
+ version: '8.16.0'
26
26
  }
27
27
 
28
28
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.14.3",
3
+ "version": "8.16.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -60,7 +60,7 @@
60
60
  "neo-jsdoc": "1.0.1",
61
61
  "neo-jsdoc-x": "1.0.5",
62
62
  "postcss": "^8.5.1",
63
- "sass": "^1.83.4",
63
+ "sass": "^1.84.0",
64
64
  "siesta-lite": "5.5.2",
65
65
  "url": "^0.11.4",
66
66
  "webpack": "^5.97.1",
@@ -262,12 +262,12 @@ const DefaultConfig = {
262
262
  useVdomWorker: true,
263
263
  /**
264
264
  * buildScripts/injectPackageVersion.mjs will update this value
265
- * @default '8.14.3'
265
+ * @default '8.16.0'
266
266
  * @memberOf! module:Neo
267
267
  * @name config.version
268
268
  * @type String
269
269
  */
270
- version: '8.14.3'
270
+ version: '8.16.0'
271
271
  };
272
272
 
273
273
  Object.assign(DefaultConfig, {
@@ -1,6 +1,7 @@
1
1
  import Base from '../core/Base.mjs';
2
2
  import Filter from './Filter.mjs';
3
3
  import Logger from '../util/Logger.mjs';
4
+ import NeoArray from '../util/Array.mjs';
4
5
  import Observable from '../core/Observable.mjs';
5
6
  import Sorter from './Sorter.mjs';
6
7
 
@@ -1015,6 +1016,25 @@ class Collection extends Base {
1015
1016
  return this._items[this.getCount() -1]
1016
1017
  }
1017
1018
 
1019
+ /**
1020
+ * Moves an item from fromIndex to toIndex
1021
+ * @param {Number} fromIndex
1022
+ * @param {Number} toIndex
1023
+ */
1024
+ move(fromIndex, toIndex) {
1025
+ if (fromIndex === toIndex) {
1026
+ return
1027
+ }
1028
+
1029
+ let {items} = this;
1030
+
1031
+ if (fromIndex >= items.length) {
1032
+ fromIndex = items.length - 1
1033
+ }
1034
+
1035
+ items.splice(toIndex, 0, items.splice(fromIndex, 1)[0])
1036
+ }
1037
+
1018
1038
  /**
1019
1039
  * @param {Object} opts
1020
1040
  * @protected
@@ -50,9 +50,18 @@ class DragZone extends Base {
50
50
  */
51
51
  bodyCursorStyle: null,
52
52
  /**
53
- * @member {String|null} boundaryContainerId=null
53
+ * Limit the zone in which you can drag an element.
54
+ * You can pass a node id, or an array of 2 node ids, in case you need an intersection.
55
+ * Example for 2 ids: grid.header.Toolbar => boundaryContainerId: [id, me.parent.id]
56
+ * @member {String|String[]|null} boundaryContainerId=null
54
57
  */
55
58
  boundaryContainerId: null,
59
+ /**
60
+ * Stores the DOMRect matching this.boundaryContainerId
61
+ * @member {DOMRect|null} data=null
62
+ * @protected
63
+ */
64
+ boundaryContainerRect: null,
56
65
  /**
57
66
  * Store data which you want to pass to drop related events here
58
67
  * @member {Object|null} data=null
@@ -315,16 +324,18 @@ class DragZone extends Base {
315
324
  {appName, owner, windowId} = me,
316
325
  {cls} = owner,
317
326
  rect = me.getDragElementRect(data),
318
- offsetX, offsetY;
327
+ mainData, offsetX, offsetY;
319
328
 
320
329
  me.setData();
321
330
 
322
- Neo.main.addon.DragDrop.setConfigs({
331
+ mainData = await Neo.main.addon.DragDrop.setConfigs({
323
332
  appName,
324
333
  windowId,
325
334
  ...me.getMainThreadConfigs()
326
335
  });
327
336
 
337
+ me.boundaryContainerRect = mainData.boundaryContainerRect
338
+
328
339
  NeoArray.add(cls, 'neo-is-dragging');
329
340
  owner.cls = cls;
330
341
 
@@ -57,7 +57,7 @@ class SortZone extends BaseSortZone {
57
57
  {view} = grid,
58
58
  gridRows = view.getVdomRoot().cn,
59
59
  columnIndex = me.dragElement['aria-colindex'] - 1,
60
- {dataField} = view.columnPositions[columnIndex],
60
+ {dataField} = view.columnPositions.getAt(columnIndex),
61
61
  cells = view.getColumnCells(dataField),
62
62
  rows = [],
63
63
  config = await super.createDragProxy(data, false),
@@ -95,10 +95,9 @@ class SortZone extends BaseSortZone {
95
95
  config.listeners = {
96
96
  mounted() {
97
97
  Neo.main.DomAccess.scrollTo({
98
- direction: 'top',
99
- id : viewWrapperId,
100
- value : view.scrollPosition.y,
101
- windowId : this.windowId
98
+ id : viewWrapperId,
99
+ value : view.scrollPosition.y,
100
+ windowId: this.windowId
102
101
  })
103
102
  }
104
103
  };
@@ -150,14 +149,17 @@ class SortZone extends BaseSortZone {
150
149
  await super.onDragStart(data);
151
150
 
152
151
  if (this.moveColumnContent) {
153
- let me = this,
154
- {view} = me.owner.parent,
155
- columnIndex = me.dragElement['aria-colindex'] - 1,
156
- {dataField} = view.columnPositions[columnIndex],
157
- cells = view.getColumnCells(dataField);
152
+ let me = this,
153
+ {view} = me.owner.parent,
154
+ columnIndex = me.dragElement['aria-colindex'] - 1,
155
+ columnPosition = view.columnPositions.getAt(columnIndex),
156
+ {dataField} = columnPosition,
157
+ cells = view.getColumnCells(dataField);
158
+
159
+ columnPosition.hidden = true;
158
160
 
159
161
  cells.forEach(cell => {
160
- cell.style.display = 'none'
162
+ cell.style.visibility = 'hidden'
161
163
  });
162
164
 
163
165
  view.update()
@@ -172,33 +174,35 @@ class SortZone extends BaseSortZone {
172
174
  super.switchItems(index1, index2);
173
175
 
174
176
  if (this.moveColumnContent) {
175
- let me = this,
176
- {itemRects} = me,
177
- {view} = me.owner.parent,
178
- columnPositions = view._columnPositions, // no clone
179
- column1Cells = view.getColumnCells(columnPositions[index1].dataField),
180
- column2Cells = view.getColumnCells(columnPositions[index2].dataField);
181
-
182
- Object.assign(columnPositions[index1], {
177
+ let me = this,
178
+ {itemRects} = me,
179
+ {view} = me.owner.parent,
180
+ {columnPositions} = view,
181
+ column1Position = columnPositions.getAt(index1),
182
+ column2Position = columnPositions.getAt(index2),
183
+ column1Cells = view.getColumnCells(column1Position.dataField),
184
+ column2Cells = view.getColumnCells(column2Position.dataField);
185
+
186
+ Object.assign(column1Position, {
183
187
  width: itemRects[index2].width,
184
188
  x : itemRects[index2].x + 1
185
189
  });
186
190
 
187
- Object.assign(columnPositions[index2], {
191
+ Object.assign(column2Position, {
188
192
  width: itemRects[index1].width,
189
193
  x : itemRects[index1].x + 1
190
194
  });
191
195
 
192
- NeoArray.move(columnPositions, index1, index2);
196
+ columnPositions.move(index1, index2);
193
197
 
194
198
  column1Cells.forEach(node => {
195
- node.style.left = columnPositions[index2].x + 'px';
196
- node.style.width = columnPositions[index2].width + 'px'
199
+ node.style.left = column1Position.x + 'px';
200
+ node.style.width = column1Position.width + 'px'
197
201
  });
198
202
 
199
203
  column2Cells.forEach(node => {
200
- node.style.left = columnPositions[index1].x + 'px';
201
- node.style.width = columnPositions[index1].width + 'px'
204
+ node.style.left = column2Position.x + 'px';
205
+ node.style.width = column2Position.width + 'px'
202
206
  });
203
207
 
204
208
  view.update()
@@ -82,6 +82,12 @@ class SortZone extends DragZone {
82
82
  startIndex: -1
83
83
  }
84
84
 
85
+ /**
86
+ * @member {Boolean} isOverDragging=false
87
+ * @protected
88
+ */
89
+ isOverDragging = false
90
+
85
91
  /**
86
92
  * Override this method for class extensions (e.g. tab.header.Toolbar)
87
93
  * @param {Number} fromIndex
@@ -149,41 +155,73 @@ class SortZone extends DragZone {
149
155
  /**
150
156
  * @param {Object} data
151
157
  */
152
- onDragMove(data) {
153
- // the method can trigger before we got the client rects from the main thread
154
- if (!this.itemRects) {
158
+ async onDragMove(data) {
159
+ // The method can trigger before we got the client rects from the main thread
160
+ if (!this.itemRects || this.isScrolling) {
155
161
  return
156
162
  }
157
163
 
158
164
  let me = this,
159
- moveFactor = 0.55, // we can not use 0.5, since items would jump back & forth
160
165
  index = me.currentIndex,
161
166
  {itemRects} = me,
162
167
  maxItems = itemRects.length - 1,
163
168
  reversed = me.reversedLayoutDirection,
164
- delta, itemWidth;
169
+ delta, isOverDragging, isOverDraggingEnd, isOverDraggingStart, itemHeightOrWidth, moveFactor;
165
170
 
166
171
  if (me.sortDirection === 'horizontal') {
167
- delta = data.clientX + me.scrollLeft - me.offsetX - itemRects[index].left;
168
- itemWidth = 'width'
172
+ delta = data.clientX + me.scrollLeft - me.offsetX - itemRects[index].left;
173
+ isOverDraggingEnd = data.clientX > me.boundaryContainerRect.right;
174
+ isOverDraggingStart = data.clientX < me.boundaryContainerRect.left;
175
+ itemHeightOrWidth = 'width'
169
176
  } else {
170
- delta = data.clientY + me.scrollTop - me.offsetY - itemRects[index].top;
171
- itemWidth = 'height'
177
+ delta = data.clientY + me.scrollTop - me.offsetY - itemRects[index].top;
178
+ isOverDraggingEnd = data.clientY > me.boundaryContainerRect.bottom;
179
+ isOverDraggingStart = data.clientY < me.boundaryContainerRect.top;
180
+ itemHeightOrWidth = 'height'
181
+ }
182
+
183
+ isOverDragging = isOverDraggingEnd || isOverDraggingStart;
184
+ moveFactor = isOverDragging ? 0.02 : 0.55; // We can not use 0.5, since items would jump back & forth
185
+
186
+ if (isOverDraggingStart) {
187
+ if (index > 0) {
188
+ me.currentIndex--;
189
+ await me.scrollToIndex();
190
+ me.switchItems(index, me.currentIndex)
191
+ }
192
+ }
193
+
194
+ else if (isOverDraggingEnd) {
195
+ if (index < maxItems) {
196
+ me.currentIndex++;
197
+ await me.scrollToIndex();
198
+ me.switchItems(index, me.currentIndex)
199
+ }
172
200
  }
173
201
 
174
- if (index > 0 && (!reversed && delta < 0 || reversed && delta > 0)) {
175
- if (Math.abs(delta) > itemRects[index - 1][itemWidth] * moveFactor) {
202
+ else if (index > 0 && (!reversed && delta < 0 || reversed && delta > 0)) {
203
+ if (Math.abs(delta) > itemRects[index - 1][itemHeightOrWidth] * moveFactor) {
176
204
  me.currentIndex--;
177
205
  me.switchItems(index, me.currentIndex)
178
206
  }
179
207
  }
180
208
 
181
209
  else if (index < maxItems && (!reversed && delta > 0 || reversed && delta < 0)) {
182
- if (Math.abs(delta) > itemRects[index + 1][itemWidth] * moveFactor) {
210
+ if (Math.abs(delta) > itemRects[index + 1][itemHeightOrWidth] * moveFactor) {
183
211
  me.currentIndex++;
184
212
  me.switchItems(index, me.currentIndex)
185
213
  }
186
214
  }
215
+
216
+ me.isOverDragging = isOverDragging && me.currentIndex !== 0 && me.currentIndex !== maxItems;
217
+
218
+ if (me.isOverDragging) {
219
+ await me.timeout(30); // wait for 1 frame
220
+
221
+ if (me.isOverDragging) {
222
+ await me.onDragMove(data)
223
+ }
224
+ }
187
225
  }
188
226
 
189
227
  /**
@@ -213,7 +251,7 @@ class SortZone extends DragZone {
213
251
  startIndex : index
214
252
  });
215
253
 
216
- await me.dragStart(data); // we do not want to trigger the super class call here
254
+ await me.dragStart(data); // We do not want to trigger the super class call here
217
255
 
218
256
  owner.items.forEach((item, index) => {
219
257
  indexMap[index] = index;
@@ -230,7 +268,7 @@ class SortZone extends DragZone {
230
268
  ownerStyle.height = `${itemRects[0].height}px`;
231
269
  ownerStyle.width = `${itemRects[0].width}px`;
232
270
 
233
- // the only reason we are adjusting the toolbar style is that there is no min height or width present.
271
+ // The only reason we are adjusting the toolbar style is that there is no min height or width present.
234
272
  // removing items from the layout could trigger a change in size.
235
273
  owner.style = ownerStyle;
236
274
 
@@ -263,6 +301,17 @@ class SortZone extends DragZone {
263
301
  }
264
302
  }
265
303
 
304
+ /**
305
+ * @returns {Promise<void>}
306
+ */
307
+ async scrollToIndex() {
308
+ let me = this;
309
+
310
+ me.isScrolling = true;
311
+ await me.owner.scrollToIndex?.(me.currentIndex, me.itemRects[me.currentIndex]);
312
+ me.isScrolling = false
313
+ }
314
+
266
315
  /**
267
316
  * @param {Number} index1
268
317
  * @param {Number} index2
package/src/grid/View.mjs CHANGED
@@ -1,4 +1,5 @@
1
1
  import ClassSystemUtil from '../util/ClassSystem.mjs';
2
+ import Collection from '../collection/Base.mjs';
2
3
  import Component from '../component/Base.mjs';
3
4
  import NeoArray from '../util/Array.mjs';
4
5
  import RowModel from '../selection/grid/RowModel.mjs';
@@ -63,10 +64,10 @@ class GridView extends Component {
63
64
  */
64
65
  containerWidth_: 0,
65
66
  /**
66
- * @member {Object[]} columnPositions_=[]
67
+ * @member {Neo.collection.Base|null} columnPositions_=null
67
68
  * @protected
68
69
  */
69
- columnPositions_: [],
70
+ columnPositions_: null,
70
71
  /**
71
72
  * @member {Boolean} highlightModifiedCells_=false
72
73
  */
@@ -238,19 +239,7 @@ class GridView extends Component {
238
239
  * @protected
239
240
  */
240
241
  afterSetContainerWidth(value, oldValue) {
241
- if (value > 0 && this.columnPositions.length > 0) {
242
- this.updateVisibleColumns()
243
- }
244
- }
245
-
246
- /**
247
- * Triggered after the columnPositions config got changed
248
- * @param {Object[]} value
249
- * @param {Object[]} oldValue
250
- * @protected
251
- */
252
- afterSetColumnPositions(value, oldValue) {
253
- if (value.length > 0 && this.containerWidth > 0) {
242
+ if (value > 0) {
254
243
  this.updateVisibleColumns()
255
244
  }
256
245
  }
@@ -299,7 +288,7 @@ class GridView extends Component {
299
288
  {bufferRowRange} = me,
300
289
  newStartIndex;
301
290
 
302
- if (value.x !== oldValue?.x && me.columnPositions.length > 0) {
291
+ if (value.x !== oldValue?.x) {
303
292
  me.updateVisibleColumns()
304
293
  }
305
294
 
@@ -461,6 +450,22 @@ class GridView extends Component {
461
450
  return cellConfig
462
451
  }
463
452
 
453
+ /**
454
+ * Triggered when accessing the columnPositions config
455
+ * @param {Object} value
456
+ * @protected
457
+ */
458
+ beforeGetColumnPositions(value) {
459
+ if (!value) {
460
+ this._columnPositions = value = Neo.create({
461
+ module : Collection,
462
+ keyProperty: 'dataField'
463
+ })
464
+ }
465
+
466
+ return value
467
+ }
468
+
464
469
  /**
465
470
  * Triggered before the selectionModel config gets changed.
466
471
  * @param {Neo.selection.Model} value
@@ -490,7 +495,7 @@ class GridView extends Component {
490
495
  columns = gridContainer.headerToolbar.items,
491
496
  id = me.getRowId(record, rowIndex),
492
497
  trCls = me.getTrClass(record, rowIndex),
493
- config, column, endIndex, gridRow, i, startIndex;
498
+ config, column, columnPosition, endIndex, gridRow, i, startIndex;
494
499
 
495
500
  if (rowIndex % 2 !== 0) {
496
501
  trCls.push('neo-even')
@@ -529,10 +534,17 @@ class GridView extends Component {
529
534
  config.cls = ['neo-locked', ...config.cls || []]
530
535
  }
531
536
 
537
+ columnPosition = me.columnPositions.get(column.dataField);
538
+
532
539
  config.style = {
533
540
  ...config.style,
534
- left : me.columnPositions[i].x + 'px',
535
- width: me.columnPositions[i].width + 'px'
541
+ left : columnPosition.x + 'px',
542
+ width: columnPosition.width + 'px'
543
+ }
544
+
545
+ // Happens during a column header drag OP, when leaving the painted range
546
+ if (columnPosition.hidden) {
547
+ config.style.visibility = 'hidden'
536
548
  }
537
549
 
538
550
  gridRow.cn.push(config)
@@ -552,11 +564,11 @@ class GridView extends Component {
552
564
  endIndex, i;
553
565
 
554
566
  if (
555
- countRecords < 1 ||
556
- me.availableRows < 1 ||
557
- me._containerWidth < 1 || // we are not checking me.containerWidth, since we want to ignore the config symbol
558
- me.columnPositions.length < 1 ||
559
- me.visibleColumns[1] < 1
567
+ countRecords < 1 ||
568
+ me.availableRows < 1 ||
569
+ me._containerWidth < 1 || // we are not checking me.containerWidth, since we want to ignore the config symbol
570
+ me.columnPositions.getCount() < 1 ||
571
+ me.visibleColumns[1] < 1
560
572
  ) {
561
573
  return
562
574
  }
@@ -652,13 +664,14 @@ class GridView extends Component {
652
664
  * @returns {Object[]}
653
665
  */
654
666
  getColumnCells(dataField) {
655
- let me = this,
656
- cells = [],
657
- vdomRoot = me.getVdomRoot(),
658
- firstRow = vdomRoot.cn[0],
659
- i = 0,
660
- len = firstRow.cn.length,
661
- columnIndex;
667
+ let me = this,
668
+ cells = [],
669
+ columnIndex = -1,
670
+ vdomRoot = me.getVdomRoot(),
671
+ firstRow = vdomRoot.cn[0],
672
+ i = 0,
673
+ len = firstRow.cn.length,
674
+ cell;
662
675
 
663
676
  // Columns might get moved via drag&drop, so let's check for the current match
664
677
  for (; i < len; i++) {
@@ -668,9 +681,12 @@ class GridView extends Component {
668
681
  }
669
682
  }
670
683
 
671
- vdomRoot.cn.forEach(row => {
672
- cells.push(row.cn[columnIndex])
673
- });
684
+ if (columnIndex > -1) {
685
+ vdomRoot.cn.forEach(row => {
686
+ cell = row.cn[columnIndex];
687
+ cell && cells.push(cell)
688
+ })
689
+ }
674
690
 
675
691
  return cells
676
692
  }
@@ -900,12 +916,16 @@ class GridView extends Component {
900
916
  {columnPositions} = me,
901
917
  {x} = me.scrollPosition,
902
918
  i = 0,
903
- len = columnPositions.length,
919
+ len = columnPositions.getCount(),
904
920
  endIndex = len - 1,
905
921
  column, startIndex;
906
922
 
923
+ if (len < 1) {
924
+ return
925
+ }
926
+
907
927
  for (; i < len; i++) {
908
- column = columnPositions[i];
928
+ column = columnPositions.getAt(i);
909
929
 
910
930
  if (x >= column.x && x <= column.x + column.width) {
911
931
  startIndex = i
@@ -71,7 +71,7 @@ class Toolbar extends BaseToolbar {
71
71
  me.sortZone = Neo.create({
72
72
  module : module.default,
73
73
  appName,
74
- boundaryContainerId: id,
74
+ boundaryContainerId: [id, me.parent.id],
75
75
  owner : me,
76
76
  scrollLeft,
77
77
  windowId,
@@ -201,6 +201,7 @@ class Toolbar extends BaseToolbar {
201
201
  async passSizeToView(silent=false) {
202
202
  let me = this,
203
203
  {items} = me,
204
+ {view} = me.parent,
204
205
  rects = await me.getDomRect(items.map(item => item.id)),
205
206
  lastItem = rects[rects.length - 1],
206
207
  columnPositions = rects.map((item, index) => ({dataField: items[index].dataField, width: item.width, x: item.x - rects[0].x})),
@@ -221,12 +222,29 @@ class Toolbar extends BaseToolbar {
221
222
  await me.timeout(100);
222
223
  await me.passSizeToView(silent)
223
224
  } else {
224
- me.parent.view[silent ? 'setSilent' : 'set']({
225
- availableWidth: lastItem.x + lastItem.width - rects[0].x,
226
- columnPositions
227
- })
225
+ view.columnPositions.clear();
226
+ view.columnPositions.add(columnPositions);
227
+
228
+ view[silent ? 'setSilent' : 'set']({
229
+ availableWidth: lastItem.x + lastItem.width - rects[0].x
230
+ });
231
+
232
+ !silent && view.updateVisibleColumns()
228
233
  }
229
234
  }
235
+
236
+ /**
237
+ * @param {Number} index
238
+ * @param {DOMRect} itemRect
239
+ * @returns {Promise<void>}
240
+ */
241
+ async scrollToIndex(index, itemRect) {
242
+ await Neo.main.DomAccess.scrollIntoView({
243
+ delay : 125,
244
+ id : this.items[index].id,
245
+ windowId: this.windowId
246
+ })
247
+ }
230
248
  }
231
249
 
232
250
  export default Neo.setupClass(Toolbar);
@@ -464,7 +464,7 @@ class DomAccess extends Base {
464
464
  returnData;
465
465
 
466
466
  if (Array.isArray(data.id)) {
467
- return data.id.map(id => me.getBoundingClientRect({ id }));
467
+ return data.id.map(id => me.getBoundingClientRect({id}))
468
468
  } else {
469
469
  let node = me.getElementOrBody(data.nodeType ? data : data.id),
470
470
  rect = {},
@@ -897,19 +897,16 @@ class DomAccess extends Base {
897
897
  * @param {String} [data.id]
898
898
  * @param {String} data.behavior='smooth'
899
899
  * @param {String} data.block='start'
900
+ * @param {Number} data.delay=500
900
901
  * @param {String} data.inline='nearest'
901
902
  * @param {String} [data.querySelector]
902
903
  * @returns {Promise<any>}
903
904
  */
904
- scrollIntoView(data) {
905
- let node = data.id ? this.getElement(data.id) : document.querySelector(data.querySelector),
906
- opts = {
907
- behavior: data.behavior || 'smooth',
908
- block : data.block || 'start',
909
- inline : data.inline || 'nearest'
910
- };
905
+ scrollIntoView({id, behavior='smooth', block='start', delay=500, inline='nearest', querySelector}) {
906
+ let node = id ? this.getElement(id) : document.querySelector(querySelector),
907
+ opts = {behavior, block, inline};
911
908
 
912
- if (opts.behavior !== 'smooth') {
909
+ if (behavior !== 'smooth') {
913
910
  node.scrollIntoView(opts)
914
911
  } else {
915
912
  // scrollIntoView() does not provide a callback yet.
@@ -918,11 +915,11 @@ class DomAccess extends Base {
918
915
  if (node) {
919
916
  let hasListener = 'scrollend' in window;
920
917
 
921
- hasListener && document.addEventListener('scrollend', () =>resolve(), {capture : true, once: true});
918
+ hasListener && document.addEventListener('scrollend', () =>resolve(), {capture: true, once: true});
922
919
 
923
920
  node.scrollIntoView(opts);
924
921
 
925
- !hasListener && this.timeout(500).then(() => {resolve()})
922
+ !hasListener && this.timeout(delay).then(() => {resolve()})
926
923
  } else {
927
924
  resolve()
928
925
  }
@@ -1,6 +1,7 @@
1
1
  import Base from './Base.mjs';
2
2
  import DomAccess from '../DomAccess.mjs';
3
3
  import DomEvents from '../DomEvents.mjs';
4
+ import Rectangle from '../../util/Rectangle.mjs';
4
5
 
5
6
  /**
6
7
  * @class Neo.main.addon.DragDrop
@@ -438,23 +439,30 @@ class DragDrop extends Base {
438
439
  /**
439
440
  * DragZones will set these configs inside their dragStart() method.
440
441
  * They only persist until the end of a drag OP.
441
- * @param {Object} data
442
- * @param {Boolean} data.alwaysFireDragMove
443
- * @param {String} data.boundaryContainerId
444
- * @param {String} data.scrollContainerId
445
- * @param {Number} data.scrollFactorLeft
446
- * @param {Number} data.scrollFactorTop
442
+ * @param {Object} data
443
+ * @param {Boolean} data.alwaysFireDragMove
444
+ * @param {String|String[]|null} data.boundaryContainerId
445
+ * @param {String|null} data.scrollContainerId
446
+ * @param {Number} data.scrollFactorLeft
447
+ * @param {Number} data.scrollFactorTop
448
+ * @returns {Object} return the boundaryContainerRect
447
449
  */
448
450
  setConfigs(data) {
449
- let me = this,
450
- node;
451
+ let me = this,
452
+ {boundaryContainerId} = data,
453
+ node, rects;
451
454
 
452
455
  delete data.appName;
453
456
  delete data.windowId;
454
457
 
455
- if (data.boundaryContainerId) {
456
- node = DomAccess.getElementOrBody(data.boundaryContainerId);
457
- me.boundaryContainerRect = node.getBoundingClientRect()
458
+ if (boundaryContainerId) {
459
+ rects = DomAccess.getBoundingClientRect({id: boundaryContainerId});
460
+
461
+ if (Array.isArray(boundaryContainerId)) {
462
+ me.boundaryContainerRect = Rectangle.getIntersection(...rects)
463
+ } else {
464
+ me.boundaryContainerRect = rects
465
+ }
458
466
  }
459
467
 
460
468
  delete data.boundaryContainerId;
@@ -489,6 +497,10 @@ class DragDrop extends Base {
489
497
  }
490
498
  })
491
499
  }
500
+
501
+ return {
502
+ boundaryContainerRect: me.boundaryContainerRect || null
503
+ }
492
504
  }
493
505
 
494
506
  /**
@@ -111,30 +111,24 @@ export default class Rectangle extends DOMRect {
111
111
  }
112
112
 
113
113
  /**
114
- * Returns the overlapping area of rect1 & rect2
115
- * @param {Object} rect1
116
- * @param {Object} rect2
117
- * @returns {Number} The area (x * y)
114
+ * Returns the overlapping area of rect1 & rect2 as a new Rectangle
115
+ * @param {DOMRect|Neo.util.Rectangle} rect1
116
+ * @param {DOMRect|Neo.util.Rectangle} rect2
117
+ * @returns {Neo.util.Rectangle|null} The intersecting rect
118
118
  */
119
119
  static getIntersection(rect1, rect2) {
120
- return Rectangle.getIntersectionDetails(rect1, rect2).area;
121
- }
122
-
123
- /**
124
- * Returns the overlapping area of rect1 & rect2
125
- * @param {Object} rect1
126
- * @param {Object} rect2
127
- * @returns {Object} x, y & area
128
- */
129
- static getIntersectionDetails(rect1, rect2) {
130
- let width = Math.max(0, Math.min(rect1.right, rect2.right) - Math.max(rect1.left, rect2.left)),
131
- height = Math.max(0, Math.min(rect1.bottom, rect2.bottom) - Math.max(rect1.top, rect2.top));
120
+ let x = Math.max(rect1.x, rect2.x),
121
+ y = Math.max(rect1.y, rect2.y),
122
+ right = Math.min(rect1.right, rect2.right),
123
+ bottom = Math.min(rect1.bottom, rect2.bottom),
124
+ width = Math.max(0, right - x),
125
+ height = Math.max(0, bottom - y);
126
+
127
+ if (height < 1 || width < 1) {
128
+ return null
129
+ }
132
130
 
133
- return {
134
- area: height * width,
135
- height,
136
- width
137
- };
131
+ return new Rectangle(x, y, width, height)
138
132
  }
139
133
 
140
134
  /**