neo.mjs 8.15.0 → 8.17.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.15.0'
23
+ * @member {String} version='8.17.0'
24
24
  */
25
- version: '8.15.0'
25
+ version: '8.17.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-06",
19
+ "datePublished": "2025-02-07",
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.15.0'
110
+ html : 'v8.17.0'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.15.0'
23
+ * @member {String} version='8.17.0'
24
24
  */
25
- version: '8.15.0'
25
+ version: '8.17.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.15.0",
3
+ "version": "8.17.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",
@@ -4,74 +4,18 @@
4
4
  overflow-y : hidden;
5
5
  overscroll-behavior: none;
6
6
  position : relative;
7
- }
8
-
9
- .neo-grid-container {
10
- border : 1px solid var(--grid-container-border-color);
11
- border-spacing : 0;
12
- color : var(--grid-container-color);
13
- font-size : 13px;
14
- font-weight : 400;
15
- height : 100%;
16
- line-height : 19px;
17
- overscroll-behavior: none;
18
- overflow-y : hidden;
19
- position : absolute;
20
- width : 100%;
21
-
22
- .neo-grid-row {
23
- display: flex;
24
- width : 100%;
25
- }
26
-
27
- .neo-grid-header-toolbar .neo-grid-row:nth-child(1) .neo-grid-header-cell {
28
- z-index : 10;
29
- }
30
-
31
- .neo-grid-body {
32
- display : block;
33
- max-height: 300px; // an initial dummy value to reduce the flickering until the real height is available
34
- overflow-y: scroll;
35
- }
36
-
37
- .neo-grid-row:last-child {
38
- .neo-grid-cell {
39
- border-bottom-width: 0 !important;
40
- }
41
- }
42
-
43
- .neo-grid-row.neo-even {
44
- .neo-grid-cell {
45
- background-color: var(--grid-container-cell-background-color-even);
46
- }
47
- }
48
-
49
- .neo-grid-header-cell {
50
- position: sticky;
51
- top : 0;
52
- z-index : 5;
53
- }
54
-
55
- .neo-grid-cell, .neo-grid-header-cell {
56
- border-bottom: 1px solid var(--grid-container-border-color);
57
- border-right : 1px solid var(--grid-container-border-color);
58
- height : inherit;
59
- min-width : 300px;
60
-
61
- &:last-child {
62
- border-right-width : 0;
63
- }
64
-
65
- &.neo-locked {
66
- left: 0;
67
- }
68
- }
69
7
 
70
- .neo-grid-cell {
71
- align-items : center;
72
- background-color: var(--grid-container-cell-background-color);
73
- display : flex;
74
- padding : 2px 10px 2px;
75
- width : fit-content;
8
+ .neo-grid-container {
9
+ border : 1px solid var(--grid-container-border-color);
10
+ border-spacing : 0;
11
+ color : var(--grid-container-color);
12
+ font-size : 13px;
13
+ font-weight : 400;
14
+ height : 100%;
15
+ line-height : 19px;
16
+ overscroll-behavior: none;
17
+ overflow-y : hidden;
18
+ position : absolute;
19
+ width : 100%;
76
20
  }
77
21
  }
@@ -0,0 +1,21 @@
1
+ .neo-grid-scrollbar {
2
+ bottom : 1px;
3
+ opacity : 0;
4
+ overflow-y : scroll;
5
+ position : absolute;
6
+ right : 0;
7
+ top : 31px; // header-toolbar height
8
+ transition : opacity 1s ease-out;
9
+ width : 16px;
10
+ z-index : 2;
11
+
12
+ &:hover {
13
+ opacity: 1;
14
+ }
15
+ }
16
+
17
+ .neo-grid-wrapper:has(.neo-grid-view.neo-is-scrolling) {
18
+ .neo-grid-scrollbar {
19
+ opacity: 1;
20
+ }
21
+ }
@@ -4,17 +4,19 @@
4
4
  overflow-x : hidden;
5
5
  overflow-y : auto;
6
6
  position : relative;
7
+ scrollbar-width: none;
7
8
 
8
9
  &:focus {
9
10
  outline: none;
10
11
  }
11
12
 
12
- .neo-grid-scrollbar {
13
+ .neo-grid-stretcher {
13
14
  height : 1px;
14
15
  position : absolute;
15
16
  top : 0;
16
17
  visibility : hidden;
17
18
  width : 1px;
19
+ z-index : 1001; // Above the column dragProxy element
18
20
  }
19
21
  }
20
22
 
@@ -52,6 +54,13 @@
52
54
 
53
55
  .neo-grid-row {
54
56
  position: absolute;
57
+ width : 100%;
58
+
59
+ &:last-child {
60
+ .neo-grid-cell {
61
+ border-bottom-width: 0 !important;
62
+ }
63
+ }
55
64
 
56
65
  &:hover {
57
66
  .neo-grid-cell {
@@ -59,6 +68,12 @@
59
68
  }
60
69
  }
61
70
 
71
+ &.neo-even {
72
+ .neo-grid-cell {
73
+ background-color: var(--grid-container-cell-background-color-even);
74
+ }
75
+ }
76
+
62
77
  // selection.RowModel
63
78
  &.neo-selected {
64
79
  .neo-grid-cell {
@@ -68,7 +83,19 @@
68
83
  }
69
84
 
70
85
  .neo-grid-cell {
71
- position: absolute;
86
+ align-items : center;
87
+ background-color: var(--grid-container-cell-background-color);
88
+ border-bottom : 1px solid var(--grid-container-border-color);
89
+ border-right : 1px solid var(--grid-container-border-color);
90
+ display : flex;
91
+ height : inherit;
92
+ padding : 2px 10px 2px;
93
+ position : absolute;
94
+ width : fit-content;
95
+
96
+ &:last-child {
97
+ border-right-width : 0;
98
+ }
72
99
 
73
100
  // selection.CellModel
74
101
  &.neo-selected {
@@ -262,12 +262,12 @@ const DefaultConfig = {
262
262
  useVdomWorker: true,
263
263
  /**
264
264
  * buildScripts/injectPackageVersion.mjs will update this value
265
- * @default '8.15.0'
265
+ * @default '8.17.0'
266
266
  * @memberOf! module:Neo
267
267
  * @name config.version
268
268
  * @type String
269
269
  */
270
- version: '8.15.0'
270
+ version: '8.17.0'
271
271
  };
272
272
 
273
273
  Object.assign(DefaultConfig, {
@@ -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
@@ -150,52 +156,70 @@ class SortZone extends DragZone {
150
156
  * @param {Object} data
151
157
  */
152
158
  async onDragMove(data) {
153
- // the method can trigger before we got the client rects from the main thread
159
+ // The method can trigger before we got the client rects from the main thread
154
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'
172
181
  }
173
182
 
174
- if (index > 0 && (!reversed && delta < 0 || reversed && delta > 0)) {
175
- if (Math.abs(delta) > itemRects[index - 1][itemWidth] * moveFactor) {
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) {
176
188
  me.currentIndex--;
189
+ await me.scrollToIndex();
190
+ me.switchItems(index, me.currentIndex)
191
+ }
192
+ }
177
193
 
178
- if (data.clientX < me.boundaryContainerRect.left) {
179
- me.isScrolling = true;
180
- await me.owner.scrollToIndex?.(me.currentIndex, itemRects[me.currentIndex]);
181
- me.isScrolling = false
182
- }
194
+ else if (isOverDraggingEnd) {
195
+ if (index < maxItems) {
196
+ me.currentIndex++;
197
+ await me.scrollToIndex();
198
+ me.switchItems(index, me.currentIndex)
199
+ }
200
+ }
183
201
 
202
+ else if (index > 0 && (!reversed && delta < 0 || reversed && delta > 0)) {
203
+ if (Math.abs(delta) > itemRects[index - 1][itemHeightOrWidth] * moveFactor) {
204
+ me.currentIndex--;
184
205
  me.switchItems(index, me.currentIndex)
185
206
  }
186
207
  }
187
208
 
188
209
  else if (index < maxItems && (!reversed && delta > 0 || reversed && delta < 0)) {
189
- if (Math.abs(delta) > itemRects[index + 1][itemWidth] * moveFactor) {
210
+ if (Math.abs(delta) > itemRects[index + 1][itemHeightOrWidth] * moveFactor) {
190
211
  me.currentIndex++;
212
+ me.switchItems(index, me.currentIndex)
213
+ }
214
+ }
191
215
 
192
- if (data.clientX > me.boundaryContainerRect.right) {
193
- me.isScrolling = true;
194
- await me.owner.scrollToIndex?.(me.currentIndex, itemRects[me.currentIndex]);
195
- me.isScrolling = false
196
- }
216
+ me.isOverDragging = isOverDragging && me.currentIndex !== 0 && me.currentIndex !== maxItems;
197
217
 
198
- me.switchItems(index, me.currentIndex)
218
+ if (me.isOverDragging) {
219
+ await me.timeout(30); // wait for 1 frame
220
+
221
+ if (me.isOverDragging) {
222
+ await me.onDragMove(data)
199
223
  }
200
224
  }
201
225
  }
@@ -227,7 +251,7 @@ class SortZone extends DragZone {
227
251
  startIndex : index
228
252
  });
229
253
 
230
- 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
231
255
 
232
256
  owner.items.forEach((item, index) => {
233
257
  indexMap[index] = index;
@@ -244,7 +268,7 @@ class SortZone extends DragZone {
244
268
  ownerStyle.height = `${itemRects[0].height}px`;
245
269
  ownerStyle.width = `${itemRects[0].width}px`;
246
270
 
247
- // 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.
248
272
  // removing items from the layout could trigger a change in size.
249
273
  owner.style = ownerStyle;
250
274
 
@@ -277,6 +301,17 @@ class SortZone extends DragZone {
277
301
  }
278
302
  }
279
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
+
280
315
  /**
281
316
  * @param {Number} index1
282
317
  * @param {Number} index2
@@ -1,5 +1,6 @@
1
1
  import BaseContainer from '../container/Base.mjs';
2
2
  import ClassSystemUtil from '../util/ClassSystem.mjs';
3
+ import GridScrollbar from './Scrollbar.mjs';
3
4
  import GridView from './View.mjs';
4
5
  import Store from '../data/Store.mjs';
5
6
  import * as header from './header/_export.mjs';
@@ -70,6 +71,11 @@ class GridContainer extends BaseContainer {
70
71
  * @member {Number} rowHeight_=32
71
72
  */
72
73
  rowHeight_: 32,
74
+ /**
75
+ * @member {Neo.grid.Scrollbar|null} scrollbar=null
76
+ * @protected
77
+ */
78
+ scrollbar: null,
73
79
  /**
74
80
  * @member {Boolean} showHeaderFilters_=false
75
81
  */
@@ -98,7 +104,7 @@ class GridContainer extends BaseContainer {
98
104
  */
99
105
  items: null,
100
106
  /**
101
- * @member {Object} _vdom={cls:['neo-grid-wrapper'],cn:[{cn:[]}]}
107
+ * @member {Object} _vdom
102
108
  */
103
109
  _vdom:
104
110
  {cls: ['neo-grid-wrapper'], cn: [
@@ -134,8 +140,8 @@ class GridContainer extends BaseContainer {
134
140
  construct(config) {
135
141
  super.construct(config);
136
142
 
137
- let me = this,
138
- {rowHeight, store} = me;
143
+ let me = this,
144
+ {appName, rowHeight, store, windowId} = me;
139
145
 
140
146
  me.headerToolbarId = Neo.getId('grid-header-toolbar');
141
147
  me.viewId = Neo.getId('grid-view');
@@ -147,13 +153,26 @@ class GridContainer extends BaseContainer {
147
153
  sortable : me.sortable,
148
154
  ...me.headerToolbarConfig
149
155
  }, {
150
- module: GridView,
151
- id : me.viewId,
156
+ module : GridView,
157
+ flex : 1,
158
+ gridContainer: me,
159
+ id : me.viewId,
152
160
  rowHeight,
153
161
  store,
154
162
  ...me.viewConfig
155
163
  }];
156
164
 
165
+ me.scrollbar = Neo.create({
166
+ module : GridScrollbar,
167
+ appName,
168
+ parentId: me.id,
169
+ rowHeight,
170
+ store,
171
+ windowId
172
+ });
173
+
174
+ me.vdom.cn.push(me.scrollbar.createVdomReference())
175
+
157
176
  me.vdom.id = me.getWrapperId();
158
177
 
159
178
  me.createColumns(me.columns);
@@ -219,15 +238,10 @@ class GridContainer extends BaseContainer {
219
238
  */
220
239
  async addResizeObserver(mounted) {
221
240
  let me = this,
222
- ResizeObserver = Neo.main?.addon?.ResizeObserver,
223
241
  {windowId} = me,
242
+ ResizeObserver = await Neo.currentWorker.getAddon('ResizeObserver', windowId),
224
243
  resizeParams = {id: me.id, windowId};
225
244
 
226
- if (!ResizeObserver) {
227
- await Neo.Main.importAddon({name: 'ResizeObserver', windowId});
228
- ResizeObserver = Neo.main.addon.ResizeObserver
229
- }
230
-
231
245
  if (mounted) {
232
246
  ResizeObserver.register(resizeParams);
233
247
  await me.passSizeToView()
@@ -505,12 +519,17 @@ class GridContainer extends BaseContainer {
505
519
 
506
520
  /**
507
521
  * @param {Object} data
522
+ * @param {Number} data.scrollLeft
523
+ * @param {Object} data.target
508
524
  */
509
- onScroll({scrollLeft}) {
525
+ onScroll({scrollLeft, target}) {
510
526
  let me = this;
511
527
 
512
- me.headerToolbar.scrollLeft = scrollLeft;
513
- me.view.scrollPosition = {x: scrollLeft, y: me.view.scrollPosition.y}
528
+ // We must ignore events for grid-scrollbar
529
+ if (target.id.includes('grid-container')) {
530
+ me.headerToolbar.scrollLeft = scrollLeft;
531
+ me.view.scrollPosition = {x: scrollLeft, y: me.view.scrollPosition.y}
532
+ }
514
533
  }
515
534
 
516
535
  /**
@@ -0,0 +1,118 @@
1
+ import Component from '../component/Base.mjs';
2
+
3
+ /**
4
+ * @class Neo.grid.Scrollbar
5
+ * @extends Neo.component.Base
6
+ */
7
+ class GridScrollbar extends Component {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='Neo.grid.Scrollbar'
11
+ * @protected
12
+ */
13
+ className: 'Neo.grid.Scrollbar',
14
+ /**
15
+ * @member {String} ntype='grid-scrollbar'
16
+ * @protected
17
+ */
18
+ ntype: 'grid-scrollbar',
19
+ /**
20
+ * @member {String[]} baseCls=['neo-grid-scrollbar']
21
+ * @protected
22
+ */
23
+ baseCls: ['neo-grid-scrollbar'],
24
+ /**
25
+ * Number in px
26
+ * @member {Number} rowHeight_=0
27
+ */
28
+ rowHeight_: 0,
29
+ /**
30
+ * @member {Neo.data.Store|null} store_=null
31
+ */
32
+ store_: null,
33
+ /**
34
+ * @member {Object} _vdom
35
+ */
36
+ _vdom:
37
+ {cn: [
38
+ {cls: ['neo-grid-scrollbar-content']}
39
+ ]}
40
+ }
41
+
42
+ /**
43
+ * @param {Boolean} mounted
44
+ * @protected
45
+ */
46
+ async addScrollSync(mounted) {
47
+ let me = this,
48
+ {windowId} = me,
49
+ ScrollSync = await Neo.currentWorker.getAddon('ScrollSync', windowId),
50
+ params = {id: me.id, windowId};
51
+
52
+ if (mounted) {
53
+ ScrollSync.register({
54
+ fromId: me.parent.view.vdom.id,
55
+ toId : me.id,
56
+ ...params
57
+ })
58
+ } else {
59
+ ScrollSync.unregister(params)
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Triggered after the mounted config got changed
65
+ * @param {Boolean} value
66
+ * @param {Boolean} oldValue
67
+ * @protected
68
+ */
69
+ afterSetMounted(value, oldValue) {
70
+ super.afterSetMounted(value, oldValue);
71
+ oldValue !== undefined && this.addScrollSync(value)
72
+ }
73
+
74
+ /**
75
+ * Triggered after the rowHeight config got changed
76
+ * @param {Number} value
77
+ * @param {Number} oldValue
78
+ * @protected
79
+ */
80
+ afterSetRowHeight(value, oldValue) {
81
+ value > 0 && this.updateScrollHeight()
82
+ }
83
+
84
+ /**
85
+ * Triggered after the store config got changed
86
+ * @param {Neo.data.Store|null} value
87
+ * @param {Neo.data.Store|null} oldValue
88
+ * @protected
89
+ */
90
+ afterSetStore(value, oldValue) {
91
+ if (value) {
92
+ let me = this;
93
+
94
+ value.on({
95
+ load : me.updateScrollHeight,
96
+ scope: me
97
+ });
98
+
99
+ value.getCount() > 0 && me.updateScrollHeight()
100
+ }
101
+ }
102
+
103
+ /**
104
+ *
105
+ */
106
+ updateScrollHeight() {
107
+ let me = this,
108
+ countRecords = me.store.getCount(),
109
+ {rowHeight} = me;
110
+
111
+ if (countRecords > 0 && rowHeight > 0) {
112
+ me.vdom.cn[0].height = `${(countRecords + 1) * rowHeight}px`;
113
+ me.update()
114
+ }
115
+ }
116
+ }
117
+
118
+ export default Neo.setupClass(GridScrollbar);
package/src/grid/View.mjs CHANGED
@@ -126,7 +126,7 @@ class GridView extends Component {
126
126
  _vdom:
127
127
  {tabIndex: '-1', cn: [
128
128
  {cn: []},
129
- {cls: 'neo-grid-scrollbar'}
129
+ {cls: 'neo-grid-stretcher'}
130
130
  ]}
131
131
  }
132
132
 
@@ -0,0 +1,90 @@
1
+ import Base from './Base.mjs';
2
+ import DomAccess from '../DomAccess.mjs';
3
+
4
+ /**
5
+ * Syncs the scroll state of 2 DOM nodes
6
+ * @class Neo.main.addon.ScrollSync
7
+ * @extends Neo.main.addon.Base
8
+ */
9
+ class ScrollSync extends Base {
10
+ static config = {
11
+ /**
12
+ * @member {String} className='Neo.main.addon.ScrollSync'
13
+ * @protected
14
+ */
15
+ className: 'Neo.main.addon.ScrollSync',
16
+ /**
17
+ * Remote method access for other workers
18
+ * @member {Object} remote
19
+ * @protected
20
+ */
21
+ remote: {
22
+ app: [
23
+ 'register',
24
+ 'unregister'
25
+ ]
26
+ }
27
+ }
28
+
29
+ /**
30
+ * @param {String} fromId
31
+ * @param {String} toId
32
+ * @param {String} direction
33
+ */
34
+ addScrollListener(fromId, toId, direction) {
35
+ DomAccess.getElement(fromId)?.addEventListener('scroll', this.onScroll.bind(this, toId, direction))
36
+ }
37
+
38
+ /**
39
+ * @param {String} toId
40
+ * @param {String} direction
41
+ * @param {Event} event
42
+ */
43
+ onScroll(toId, direction, event) {
44
+ let node = DomAccess.getElement(toId),
45
+ {scrollLeft, scrollTop} = event.target;
46
+
47
+ if (node) {
48
+ if (direction === 'both') {
49
+ node.scrollTo({
50
+ behavior: 'instant',
51
+ left : scrollLeft,
52
+ top : scrollTop
53
+ })
54
+ } else if (direction === 'horizontal') {
55
+ node.scrollLeft = scrollLeft
56
+ } else if (direction === 'vertical') {
57
+ node.scrollTop = scrollTop
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ * @param {Object} data
64
+ * @param {String} direction='vertical' 'horizontal', 'vertical' or 'both'
65
+ * @param {String} fromId
66
+ * @param {String} id The owner id (e.g. component id)
67
+ * @param {String} toId
68
+ * @param {Boolean} twoWay=true Sync the target's scroll state back to the source node
69
+ */
70
+ register({direction='vertical', fromId, id, toId, twoWay=true}) {
71
+ console.log('register', direction, fromId, id, toId, twoWay);
72
+
73
+ let me = this;
74
+
75
+ me.addScrollListener(fromId, toId, direction);
76
+
77
+ if (twoWay) {
78
+ me.addScrollListener(toId, fromId, direction)
79
+ }
80
+ }
81
+
82
+ /**
83
+ * @param {Object} data
84
+ */
85
+ unregister(data) {
86
+ console.log('unregister', data)
87
+ }
88
+ }
89
+
90
+ export default Neo.setupClass(ScrollSync);
@@ -242,6 +242,23 @@ class App extends Base {
242
242
  })
243
243
  }
244
244
 
245
+ /**
246
+ * Convenience shortcut to lazy-load main thread addons, in case they are not imported yet
247
+ * @param {String} name
248
+ * @param {Number} windowId
249
+ * @returns {Promise<Neo.main.addon.Base>} The namespace of the addon to use via remote method access
250
+ */
251
+ async getAddon(name, windowId) {
252
+ let addon = Neo.main?.addon?.[name];
253
+
254
+ if (!addon) {
255
+ await Neo.Main.importAddon({name, windowId});
256
+ addon = Neo.main.addon[name]
257
+ }
258
+
259
+ return addon
260
+ }
261
+
245
262
  /**
246
263
  * Get configs of any app realm based Neo instance from main
247
264
  * @param {Object} data