neo.mjs 8.23.0 → 8.24.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.23.0'
23
+ * @member {String} version='8.24.0'
24
24
  */
25
- version: '8.23.0'
25
+ version: '8.24.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-21",
19
+ "datePublished": "2025-02-24",
20
20
  "publisher": {
21
21
  "@type": "Organization",
22
22
  "name": "Neo.mjs"
@@ -22,10 +22,10 @@ class FeatureSection extends Container {
22
22
  */
23
23
  className: 'Portal.view.home.FeatureSection',
24
24
  /**
25
- * @member {String[]} baseCls=['portal-home-feature-section','neo-container']
25
+ * @member {String[]} baseCls=['portal-home-feature-section','portal-home-content-view','neo-container']
26
26
  * @protected
27
27
  */
28
- baseCls: ['portal-home-feature-section', 'neo-container'],
28
+ baseCls: ['portal-home-feature-section', 'portal-home-content-view', 'neo-container'],
29
29
  /**
30
30
  * If you want to use the LivePreview, use the config livePreviewCode.
31
31
  * For custom content, use this config instead.
@@ -91,7 +91,7 @@ class FeatureSection extends Container {
91
91
  }]
92
92
  }, {
93
93
  module : Container,
94
- cls : 'portal-content-wrapper',
94
+ cls : ['portal-content-wrapper'],
95
95
  layout : 'fit',
96
96
  reference: 'portal-content-wrapper'
97
97
  }]
@@ -125,7 +125,7 @@ class FeatureSection extends Container {
125
125
  cls : ['page-live-preview'],
126
126
  reference: 'live-preview',
127
127
  value : this.livePreviewCode
128
- }]
128
+ }];
129
129
 
130
130
  this.getItem('portal-content-wrapper').items = value
131
131
  }
@@ -107,7 +107,7 @@ class FooterContainer extends Container {
107
107
  }, {
108
108
  module: Component,
109
109
  cls : ['neo-version'],
110
- html : 'v8.23.0'
110
+ html : 'v8.24.0'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -6,8 +6,6 @@ import Helix from './parts/Helix.mjs';
6
6
  import How from './parts/How.mjs';
7
7
  import MainNeo from './parts/MainNeo.mjs';
8
8
 
9
- // import References from './parts/References.mjs';
10
-
11
9
  /**
12
10
  * @class Portal.view.home.MainContainer
13
11
  * @extends Neo.container.Base
@@ -28,9 +26,12 @@ class MainContainer extends Container {
28
26
  */
29
27
  domListeners: [{
30
28
  intersect(data) {
31
- let id = data.path[1].id;
29
+ let id = data.path[0].id;
32
30
  this.activePartsId = id;
33
- Neo.getComponent(id)?.activate?.()
31
+
32
+ this.items.forEach(item => {
33
+ item[item.id === id ? 'activate' : 'deactivate']?.()
34
+ })
34
35
  },
35
36
  scroll(event) {
36
37
  if (event.target.cls.includes('portal-home-maincontainer')) {
@@ -54,7 +55,6 @@ class MainContainer extends Container {
54
55
  Helix,
55
56
  Colors,
56
57
  How,
57
- // References,
58
58
  AfterMath
59
59
  ],
60
60
  /**
@@ -85,9 +85,9 @@ class MainContainer extends Container {
85
85
  Neo.main.addon.IntersectionObserver.register({
86
86
  callback : 'isVisible',
87
87
  id,
88
- observe : ['.portal-content-wrapper'],
88
+ observe : ['.portal-home-content-view'],
89
89
  root : `#${id}`,
90
- threshold: 1.0,
90
+ threshold: .6,
91
91
  windowId
92
92
  })
93
93
  })
@@ -1,5 +1,6 @@
1
1
  import BaseContainer from './BaseContainer.mjs';
2
2
  import Button from '../../../../../src/button/Base.mjs';
3
+ import MagicMoveText from '../../../../../src/component/MagicMoveText.mjs';
3
4
 
4
5
  /**
5
6
  * @class Portal.view.home.parts.MainNeo
@@ -33,10 +34,26 @@ class MainNeo extends BaseContainer {
33
34
  tag : 'h1'
34
35
  }]
35
36
  }, {
36
- cls : ['neo-h2'],
37
- flex: 'none',
38
- html: 'Modern Enterprise-Ready JavaScript Framework',
39
- tag : 'h2'
37
+ module : MagicMoveText,
38
+ autoCycle: false,
39
+ cls : ['neo-h2'],
40
+ colorMove: '#3E63DD',
41
+ flex : 'none',
42
+ reference: 'magic-move',
43
+
44
+ cycleTexts: [
45
+ 'Modern Enterprise JavaScript Framework',
46
+ 'An Application Worker being the Orchestrator',
47
+ 'Scalability',
48
+ 'Extensibility',
49
+ 'Performance',
50
+ 'Declarative & Reactive Component Trees',
51
+ 'Separated from Business Logic',
52
+ 'View Controllers',
53
+ 'State Providers',
54
+ 'Clean Architectures',
55
+ 'Multi Window Apps'
56
+ ]
40
57
  }, {
41
58
  cls : ['neo-h3'],
42
59
  flex: 'none',
@@ -66,6 +83,20 @@ class MainNeo extends BaseContainer {
66
83
  }]
67
84
  }]
68
85
  }
86
+
87
+ /**
88
+ *
89
+ */
90
+ activate() {
91
+ this.getItem('magic-move').autoCycle = true
92
+ }
93
+
94
+ /**
95
+ *
96
+ */
97
+ deactivate() {
98
+ this.getItem('magic-move').autoCycle = false
99
+ }
69
100
  }
70
101
 
71
102
  export default Neo.setupClass(MainNeo);
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.23.0'
23
+ * @member {String} version='8.24.0'
24
24
  */
25
- version: '8.23.0'
25
+ version: '8.24.0'
26
26
  }
27
27
 
28
28
  /**
@@ -74,6 +74,8 @@ class MainContainer extends ConfigurationViewport {
74
74
  createExampleComponent() {
75
75
  return {
76
76
  module: MagicMoveText,
77
+ height: 100,
78
+ width : 450,
77
79
 
78
80
  cycleTexts: [
79
81
  'Magic Move',
@@ -1,6 +1,8 @@
1
1
  import BaseGridContainer from '../../../src/grid/Container.mjs';
2
2
  import MainStore from './MainStore.mjs';
3
3
 
4
+ import * as selection from '../../../src/selection/grid/_export.mjs';
5
+
4
6
  /**
5
7
  * @class Neo.examples.grid.bigData.GridContainer
6
8
  * @extends Neo.grid.Container
@@ -33,6 +35,7 @@ class GridContainer extends BaseGridContainer {
33
35
  * @member {Object} viewConfig
34
36
  */
35
37
  viewConfig: {
38
+ selectionModel: selection.CellModel, // todo: remove after #6491 is resolved
36
39
  bufferColumnRange: 3,
37
40
  bufferRowRange : 5
38
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.23.0",
3
+ "version": "8.24.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -27,6 +27,24 @@
27
27
  min-width : 240px;
28
28
  }
29
29
 
30
+ .neo-magic-move-text {
31
+ background-color: transparent;
32
+ color : #000;
33
+ font-size : 2.5rem;
34
+ height : 2em;
35
+ letter-spacing : -0.02em;
36
+ line-height : 1.2em;
37
+ width : 90%;
38
+
39
+ .neo-content {
40
+ font-size: 2.5rem;
41
+ }
42
+
43
+ .neo-measure-element-wrapper {
44
+ letter-spacing: -0.02em;
45
+ }
46
+ }
47
+
30
48
  .neo-github {
31
49
  background-color: #fff;
32
50
  }
@@ -79,6 +97,10 @@
79
97
  margin: 1rem 2rem;
80
98
  }
81
99
 
100
+ .neo-magic-move-text {
101
+ height: 3.5em;
102
+ }
103
+
82
104
  .vector {
83
105
  height: 17vw;
84
106
  }
@@ -106,6 +128,14 @@
106
128
  font-size: 1.15rem;
107
129
  }
108
130
 
131
+ .neo-magic-move-text {
132
+ height: 3.5em;
133
+
134
+ .neo-content {
135
+ font-size: 2rem;
136
+ }
137
+ }
138
+
109
139
  .vector {
110
140
  height: 70px;
111
141
  width : 70px;
@@ -1,42 +1,36 @@
1
1
  .neo-magic-move-text {
2
- --neo-height : 100px;
3
2
  --neo-transition-time: 500ms;
4
- --neo-width : 450px;
5
3
 
6
- align-items : center;
7
4
  background-color: #000;
8
5
  color : #fff;
9
- display : flex;
10
6
  font-size : 30px;
11
7
  font-weight : bold;
12
- height : var(--neo-height);
13
- justify-content : center;
14
- width : var(--neo-width);
15
8
 
16
9
  .neo-content {
17
- height : 100%;
18
- position: relative;
19
- width : 100%;
10
+ align-items : center;
11
+ display : flex;
12
+ height : 100%;
13
+ justify-content: center;
14
+ position : relative;
15
+ width : 100%;
20
16
 
21
- > * {
17
+ .neo-char {
22
18
  position : absolute;
23
19
  transition:
24
20
  color 0.2s ease-out,
25
21
  left var(--neo-transition-time) ease-out,
26
- opacity var(--neo-transition-time) ease-out;
22
+ opacity var(--neo-transition-time) ease-out,
23
+ top var(--neo-transition-time) ease-out;
27
24
  }
28
25
  }
29
26
 
30
27
  .neo-measure-element-wrapper {
31
28
  align-items : center;
32
- background-color: #000;
33
29
  display : flex;
34
- height : var(--neo-height);
35
30
  justify-content: center;
36
31
  left : -5000px;
37
32
  position : absolute;
38
33
  top : -5000px;
39
- width : var(--neo-width);
40
34
  }
41
35
 
42
36
  .neo-measure-element {
@@ -263,12 +263,12 @@ const DefaultConfig = {
263
263
  useVdomWorker: true,
264
264
  /**
265
265
  * buildScripts/injectPackageVersion.mjs will update this value
266
- * @default '8.23.0'
266
+ * @default '8.24.0'
267
267
  * @memberOf! module:Neo
268
268
  * @name config.version
269
269
  * @type String
270
270
  */
271
- version: '8.23.0'
271
+ version: '8.24.0'
272
272
  };
273
273
 
274
274
  Object.assign(DefaultConfig, {
@@ -74,25 +74,98 @@ class MagicMoveText extends Component {
74
74
 
75
75
  /**
76
76
  * @member {Object[]} chars=[]
77
+ * @protected
77
78
  */
78
79
  chars = []
80
+ /**
81
+ * @member {Object[]} charsVdom=[]
82
+ * @protected
83
+ */
84
+ charsVdom = []
85
+ /**
86
+ * @member {Number} contentHeight=0
87
+ * @protected
88
+ */
89
+ contentHeight = 0
90
+ /**
91
+ * @member {Number} contentWidth=0
92
+ * @protected
93
+ */
94
+ contentWidth = 0
79
95
  /**
80
96
  * @member {Number} currentIndex=0
97
+ * @protected
81
98
  */
82
99
  currentIndex = 0
100
+ /**
101
+ * We do not need the first event to trigger logic, since afterSetMounted() handles this
102
+ * @member {Boolean} initialResizeEvent=true
103
+ * @protected
104
+ */
105
+ initialResizeEvent = true
83
106
  /**
84
107
  * @member {Number|null} intervalId=null
108
+ * @protected
85
109
  */
86
110
  intervalId = null
111
+ /**
112
+ * Internal flag which gets set to true while the animated char transitions are running
113
+ * @member {Boolean} isTransitioning=false
114
+ * @protected
115
+ */
116
+ isTransitioning = false
117
+ /**
118
+ * @member {Object} measureCache={}
119
+ * @protected
120
+ */
121
+ measureCache = {}
87
122
  /**
88
123
  * @member {Object[]} previousChars=[]
124
+ * @protected
89
125
  */
90
126
  previousChars = []
91
127
  /**
92
128
  * @member {Object} measureElement
129
+ * @protected
93
130
  */
94
131
  get measureElement() {
95
- return this.vdom.cn[1].cn[0]
132
+ return this.measureWrapper.cn[0]
133
+ }
134
+ /**
135
+ * @member {Object} measureElement
136
+ * @protected
137
+ */
138
+ get measureWrapper() {
139
+ return this.vdom.cn[1]
140
+ }
141
+
142
+ /**
143
+ * @param {Object} config
144
+ */
145
+ construct(config) {
146
+ super.construct(config);
147
+
148
+ let me = this;
149
+
150
+ me.addDomListeners({
151
+ resize: me.onResize,
152
+ scope : me
153
+ })
154
+ }
155
+
156
+ /**
157
+ * @param {Boolean} mounted
158
+ * @protected
159
+ */
160
+ async addResizeObserver(mounted) {
161
+ let {id, windowId} = this,
162
+ ResizeObserver = await Neo.currentWorker.getAddon('ResizeObserver', windowId);
163
+
164
+ ResizeObserver[mounted ? 'register' : 'unregister']({id, windowId});
165
+
166
+ if (mounted) {
167
+ this.initialResizeEvent = true
168
+ }
96
169
  }
97
170
 
98
171
  /**
@@ -127,8 +200,12 @@ class MagicMoveText extends Component {
127
200
  * @protected
128
201
  */
129
202
  afterSetFontFamily(value, oldValue) {
130
- this.vdom.style.fontFamily = value;
131
- this.update()
203
+ let me = this;
204
+
205
+ me.measureCache = {};
206
+
207
+ me.vdom.style.fontFamily = value;
208
+ me.update()
132
209
  }
133
210
 
134
211
  /**
@@ -139,7 +216,17 @@ class MagicMoveText extends Component {
139
216
  */
140
217
  afterSetMounted(value, oldValue) {
141
218
  super.afterSetMounted(value, oldValue);
142
- this.autoCycle && this.startAutoCycle(value)
219
+
220
+ let me = this;
221
+
222
+ value && me.getDomRect().then(rect => {
223
+ me.contentHeight = rect.height;
224
+ me.contentWidth = rect.width;
225
+
226
+ me.autoCycle && me.startAutoCycle(value)
227
+ });
228
+
229
+ oldValue !== undefined && me.addResizeObserver(value)
143
230
  }
144
231
 
145
232
  /**
@@ -165,7 +252,7 @@ class MagicMoveText extends Component {
165
252
  me.chars.push({name: char});
166
253
 
167
254
  if (char === ' ') {
168
- char = ' '
255
+ char = ' '
169
256
  }
170
257
 
171
258
  measureElement.cn.push({tag: 'span', html: char})
@@ -190,44 +277,132 @@ class MagicMoveText extends Component {
190
277
  this.update()
191
278
  }
192
279
 
280
+ /**
281
+ * @param {String[]} letters
282
+ * @returns {Object[]}
283
+ * @protected
284
+ */
285
+ createCharsVdom(letters) {
286
+ let me = this,
287
+ {chars} = me,
288
+ charsContainer = [],
289
+ char;
290
+
291
+ letters.forEach((letter, index) => {
292
+ if (letter !== null) {
293
+ char = chars[index];
294
+
295
+ charsContainer.push({
296
+ cls : ['neo-char'],
297
+ html : char.name,
298
+ style: {color: me.colorFadeIn, left: char.left, opacity: 0, top: char.top}
299
+ })
300
+ }
301
+ });
302
+
303
+ return charsContainer
304
+ }
305
+
306
+ /**
307
+ * @protected
308
+ */
309
+ cycleText() {
310
+ let me = this;
311
+
312
+ me.text = me.cycleTexts[me.currentIndex];
313
+ me.currentIndex = (me.currentIndex + 1) % me.cycleTexts.length
314
+ }
315
+
193
316
  /**
194
317
  * @returns {Promise<void>}
318
+ * @protected
195
319
  */
196
320
  async measureChars() {
197
321
  let me = this,
198
- {measureElement} = me,
322
+ {measureCache, measureElement, measureWrapper, text} = me,
199
323
  parentRect, rects;
200
324
 
201
- delete me.vdom.cn[1].removeDom;
325
+ if (measureCache[text]) {
326
+ rects = [...measureCache[text]];
327
+ parentRect = rects.shift()
328
+ } else {
329
+ measureWrapper.style = {
330
+ height: me.contentHeight + 'px',
331
+ width : me.contentWidth + 'px'
332
+ };
333
+
334
+ delete measureWrapper.removeDom;
202
335
 
203
- await me.promiseUpdate();
204
- await me.timeout(20);
336
+ await me.promiseUpdate();
337
+ await me.timeout(20);
205
338
 
206
- rects = await me.getDomRect([me.vdom.cn[1].id, ...measureElement.cn.map(node => node.id)]);
207
- parentRect = rects.shift();
339
+ rects = await me.getDomRect([measureWrapper.id, ...measureElement.cn.map(node => node.id)]);
340
+ parentRect = rects.shift();
341
+
342
+ measureCache[text] = [parentRect, ...rects]
343
+ }
208
344
 
209
345
  rects.forEach((rect, index) => {
210
346
  me.chars[index].left = `${rect.left - parentRect.left}px`;
211
347
  me.chars[index].top = `${rect.top - parentRect.top }px`;
212
348
  });
213
349
 
214
- me.vdom.cn[1].removeDom = true;
350
+ measureWrapper.removeDom = true;
215
351
  await me.promiseUpdate()
216
352
  }
217
353
 
354
+ /**
355
+ * @param {Object} data
356
+ * @returns {Promise<void>}
357
+ * @protected
358
+ */
359
+ async onResize({rect}) {
360
+ let me = this;
361
+
362
+ me.contentHeight = rect.height;
363
+ me.contentWidth = rect.width;
364
+
365
+ me.measureCache = {};
366
+
367
+
368
+ if (!me.initialResizeEvent) {
369
+ if (!me.isTransitioning) {
370
+ await me.measureChars();
371
+
372
+ me.charsVdom = me.createCharsVdom(me.chars.map(char => char.name))
373
+ }
374
+ } else {
375
+ me.initialResizeEvent = false
376
+ }
377
+ }
378
+
379
+ /**
380
+ * @param {Object} a
381
+ * @param {Object} b
382
+ * @returns {Number}
383
+ * @protected
384
+ */
385
+ sortCharacters(a, b) {
386
+ let deltaTop = parseFloat(a.style.top) - parseFloat(b.style.top);
387
+
388
+ if (deltaTop !== 0) {
389
+ return deltaTop
390
+ }
391
+
392
+ return parseFloat(a.style.left) - parseFloat(b.style.left)
393
+ }
394
+
218
395
  /**
219
396
  * @param {Boolean} start=true
397
+ * @protected
220
398
  */
221
399
  startAutoCycle(start=true) {
222
400
  let me = this;
223
401
 
224
402
  if (start) {
225
- me.intervalId = setInterval(() => {
226
- me.text = me.cycleTexts[me.currentIndex];
227
- me.currentIndex = (me.currentIndex + 1) % me.cycleTexts.length
228
- }, me.autoCycleInterval)
403
+ me.intervalId = setInterval(me.cycleText.bind(me), me.autoCycleInterval);
229
404
 
230
- me.text && me.measureChars()
405
+ me.timeout(20).then(() => {me.cycleText()});
231
406
  } else {
232
407
  clearInterval(me.intervalId)
233
408
  }
@@ -235,71 +410,84 @@ class MagicMoveText extends Component {
235
410
 
236
411
  /**
237
412
  * @returns {Promise<void>}
413
+ * @protected
238
414
  */
239
415
  async updateChars() {
240
416
  let me = this,
241
417
  {chars, previousChars} = me,
242
- charsContainer = me.vdom.cn[0].cn,
418
+ charsContainer = me.vdom.cn[0],
243
419
  letters = chars.map(char => char.name),
244
- char, charNode, index;
420
+ charNode, index;
421
+
422
+ me.isTransitioning = true;
423
+
424
+ if (me.charsVdom.length > 1) {
425
+ charsContainer.cn = me.charsVdom;
426
+ await me.promiseUpdate()
427
+ }
245
428
 
246
429
  previousChars.forEach((previousChar, previousIndex) => {
247
430
  index = letters.indexOf(previousChar.name);
248
431
 
249
432
  if (index > -1) {
250
- charNode = charsContainer[previousIndex];
433
+ charNode = charsContainer.cn[previousIndex];
434
+
435
+ Object.assign(charNode.style, {
436
+ color: me.colorMove,
437
+ left : chars[index].left,
438
+ top : chars[index].top
439
+ });
251
440
 
252
- charNode.style.color = me.colorMove;
253
- charNode.style.left = chars[index].left;
254
441
  letters[index] = null
255
442
  } else {
256
- charNode = charsContainer[previousIndex];
443
+ charNode = charsContainer.cn[previousIndex];
257
444
 
258
445
  charNode.flag = 'remove'
259
446
  }
260
447
  });
261
448
 
262
- letters.forEach((letter, index) => {
263
- if (letter !== null) {
264
- char = chars[index];
265
-
266
- charsContainer.push({
267
- html : char.name,
268
- style: {color: me.colorFadeIn, left: char.left, opacity: 0, top: char.top}
269
- })
270
- }
271
- });
449
+ charsContainer.cn.push(...me.createCharsVdom(letters));
272
450
 
273
451
  await me.promiseUpdate();
274
452
 
275
- charsContainer.forEach(charNode => {
453
+ charsContainer.cn.forEach(charNode => {
276
454
  if (charNode.flag === 'remove') {
277
455
  charNode.style.color = me.colorFadeOut;
278
456
  charNode.style.opacity = 0
279
457
  } else {
280
- charNode.style.opacity = 1
458
+ delete charNode.style.opacity
281
459
  }
282
460
  });
283
461
 
284
462
  await me.promiseUpdate();
285
463
  await me.timeout(me.transitionTime);
286
464
 
287
- charsContainer.sort((a, b) => parseFloat(a.style.left) - parseFloat(b.style.left));
465
+ charsContainer.cn.sort(me.sortCharacters);
288
466
 
289
- index = charsContainer.length - 1;
467
+ index = charsContainer.cn.length - 1;
290
468
 
291
469
  for (; index >= 0; index--) {
292
- charNode = charsContainer[index];
470
+ charNode = charsContainer.cn[index];
293
471
 
294
472
  delete charNode.flag;
295
473
  delete charNode.style.color;
296
474
 
297
475
  if (charNode.style.opacity === 0) {
298
- charsContainer.splice(index, 1)
476
+ charsContainer.cn.splice(index, 1)
299
477
  }
300
478
  }
301
479
 
302
- await me.promiseUpdate()
480
+ await me.promiseUpdate();
481
+ await me.timeout(200);
482
+
483
+ me.charsVdom = [...charsContainer.cn];
484
+
485
+ charsContainer.cn.length = 0;
486
+
487
+ charsContainer.cn.push({html: me.text});
488
+ await me.promiseUpdate();
489
+
490
+ me.isTransitioning = false
303
491
  }
304
492
  }
305
493
 
@@ -197,6 +197,25 @@ class GridContainer extends BaseContainer {
197
197
  })
198
198
  }
199
199
 
200
+ /**
201
+ * @param {Boolean} mounted
202
+ * @protected
203
+ */
204
+ async addResizeObserver(mounted) {
205
+ let me = this,
206
+ {windowId} = me,
207
+ ResizeObserver = await Neo.currentWorker.getAddon('ResizeObserver', windowId),
208
+ resizeParams = {id: me.id, windowId};
209
+
210
+ if (mounted) {
211
+ ResizeObserver.register(resizeParams);
212
+ await me.passSizeToView()
213
+ } else {
214
+ me.initialResizeEvent = true;
215
+ ResizeObserver.unregister(resizeParams)
216
+ }
217
+ }
218
+
200
219
  /**
201
220
  * Triggered after the cellEditing config got changed
202
221
  * @param {Boolean} value
@@ -245,25 +264,6 @@ class GridContainer extends BaseContainer {
245
264
  }
246
265
  }
247
266
 
248
- /**
249
- * @param {Boolean} mounted
250
- * @protected
251
- */
252
- async addResizeObserver(mounted) {
253
- let me = this,
254
- {windowId} = me,
255
- ResizeObserver = await Neo.currentWorker.getAddon('ResizeObserver', windowId),
256
- resizeParams = {id: me.id, windowId};
257
-
258
- if (mounted) {
259
- ResizeObserver.register(resizeParams);
260
- await me.passSizeToView()
261
- } else {
262
- me.initialResizeEvent = true;
263
- ResizeObserver.unregister(resizeParams)
264
- }
265
- }
266
-
267
267
  /**
268
268
  * Triggered after the mounted config got changed
269
269
  * @param {Boolean} value
@@ -537,16 +537,17 @@ class GridContainer extends BaseContainer {
537
537
  * @param {Object} data.touches
538
538
  */
539
539
  onScroll({scrollLeft, target, touches}) {
540
- let me = this,
540
+ let me = this,
541
+ {view} = me,
541
542
  deltaY, lastTouchY;
542
543
 
543
544
  // We must ignore events for grid-scrollbar
544
545
  if (target.id.includes('grid-container')) {
545
546
  me.headerToolbar.scrollLeft = scrollLeft;
546
- me.view.scrollPosition = {x: scrollLeft, y: me.view.scrollPosition.y};
547
+ view.scrollPosition = {x: scrollLeft, y: view.scrollPosition.y};
547
548
 
548
549
  if (touches) {
549
- if (!me.view.isTouchMoveOwner) {
550
+ if (!view.isTouchMoveOwner) {
550
551
  me.isTouchMoveOwner = true
551
552
  }
552
553
 
@@ -556,8 +557,8 @@ class GridContainer extends BaseContainer {
556
557
 
557
558
  deltaY !== 0 && Neo.main.DomAccess.scrollTo({
558
559
  direction: 'top',
559
- id : me.view.vdom.id,
560
- value : me.view.scrollPosition.y + deltaY
560
+ id : view.vdom.id,
561
+ value : view.scrollPosition.y + deltaY
561
562
  })
562
563
 
563
564
  me.lastTouchY = lastTouchY
@@ -657,6 +658,58 @@ class GridContainer extends BaseContainer {
657
658
  }
658
659
  })
659
660
  }
661
+
662
+ /**
663
+ * Used for keyboard navigation (selection models)
664
+ * @param {Number} index
665
+ * @param {Number} step
666
+ */
667
+ scrollByColumns(index, step) {
668
+ let me = this,
669
+ {view} = me,
670
+ {columnPositions, containerWidth, mountedColumns, visibleColumns} = view,
671
+ countColumns = columnPositions.getCount(),
672
+ newIndex = index + step,
673
+ column, mounted, scrollPosition, visible;
674
+
675
+ if (newIndex >= countColumns) {
676
+ newIndex %= countColumns;
677
+ step = newIndex - index
678
+ }
679
+
680
+ while (newIndex < 0) {
681
+ newIndex += countColumns;
682
+ step += countColumns
683
+ }
684
+
685
+ mounted = newIndex >= mountedColumns[0] && newIndex <= mountedColumns[1];
686
+
687
+ // Not using >= or <=, since the first / last column might not be fully visible
688
+ visible = newIndex > visibleColumns[0] && newIndex < visibleColumns[1];
689
+
690
+ if (!visible) {
691
+ // Leaving the mounted area will re-calculate the visibleColumns for us
692
+ if (mounted) {
693
+ visibleColumns[0] += step;
694
+ visibleColumns[1] += step
695
+ }
696
+
697
+ column = columnPositions.getAt(newIndex);
698
+
699
+ if (step < 0) {
700
+ scrollPosition = column.x
701
+ } else {
702
+ scrollPosition = column.x - containerWidth + column.width
703
+ }
704
+
705
+ Neo.main.DomAccess.scrollTo({
706
+ direction: 'left',
707
+ id : me.id,
708
+ value : scrollPosition,
709
+ windowId : me.windowId
710
+ })
711
+ }
712
+ }
660
713
  }
661
714
 
662
715
  export default Neo.setupClass(GridContainer);
package/src/grid/View.mjs CHANGED
@@ -142,9 +142,9 @@ class GridView extends Component {
142
142
  * @member {Object} _vdom
143
143
  */
144
144
  _vdom:
145
- {tabIndex: '-1', cn: [
146
- {cn: []}
147
- ]}
145
+ {tabIndex: '-1', cn: [
146
+ {cn: []}
147
+ ]}
148
148
  }
149
149
 
150
150
  /**
@@ -314,7 +314,7 @@ class GridView extends Component {
314
314
  * @protected
315
315
  */
316
316
  afterSetMountedColumns(value, oldValue) {
317
- oldValue !== undefined && this.createViewData()
317
+ oldValue && this.createViewData()
318
318
  }
319
319
 
320
320
  /**
@@ -1036,18 +1036,18 @@ class GridView extends Component {
1036
1036
  */
1037
1037
  updateMountedAndVisibleColumns() {
1038
1038
  let me = this,
1039
- {bufferColumnRange, columnPositions, visibleColumns} = me,
1040
- {x} = me.scrollPosition,
1041
- i = 0,
1042
- len = columnPositions.getCount(),
1043
- endIndex = len - 1,
1039
+ {bufferColumnRange, columnPositions, mountedColumns, visibleColumns} = me,
1040
+ {x} = me.scrollPosition,
1041
+ i = 0,
1042
+ countColumns = columnPositions.getCount(),
1043
+ endIndex = countColumns - 1,
1044
1044
  column, startIndex;
1045
1045
 
1046
- if (len < 1) {
1046
+ if (countColumns < 1) {
1047
1047
  return
1048
1048
  }
1049
1049
 
1050
- for (; i < len; i++) {
1050
+ for (; i < countColumns; i++) {
1051
1051
  column = columnPositions.getAt(i);
1052
1052
 
1053
1053
  if (x >= column.x && x <= column.x + column.width) {
@@ -1060,15 +1060,12 @@ class GridView extends Component {
1060
1060
  }
1061
1061
  }
1062
1062
 
1063
- if (
1064
- Math.abs(startIndex - me.visibleColumns[0]) >= me.bufferColumnRange ||
1065
- me.visibleColumns[1] < 1 // initial call
1066
- ) {
1067
- visibleColumns[0] = startIndex;
1068
- visibleColumns[1] = endIndex;
1063
+ visibleColumns[0] = startIndex; // update the array inline
1064
+ visibleColumns[1] = endIndex;
1069
1065
 
1070
- endIndex = Math.min(len - 1, visibleColumns[1] + bufferColumnRange);
1066
+ if (visibleColumns[0] <= mountedColumns[0] || visibleColumns[1] >= mountedColumns[1]) {
1071
1067
  startIndex = Math.max(0, visibleColumns[0] - bufferColumnRange);
1068
+ endIndex = Math.min(countColumns - 1, visibleColumns[1] + bufferColumnRange);
1072
1069
 
1073
1070
  me.mountedColumns = [startIndex, endIndex]
1074
1071
  }
@@ -235,10 +235,9 @@ class Toolbar extends BaseToolbar {
235
235
 
236
236
  /**
237
237
  * @param {Number} index
238
- * @param {DOMRect} itemRect
239
238
  * @returns {Promise<void>}
240
239
  */
241
- async scrollToIndex(index, itemRect) {
240
+ async scrollToIndex(index) {
242
241
  await Neo.main.DomAccess.scrollIntoView({
243
242
  delay : 125,
244
243
  id : this.items[index].id,
@@ -85,7 +85,7 @@ class CellModel extends BaseModel {
85
85
  let me = this,
86
86
  {dataFields, view} = me,
87
87
  {store} = view,
88
- currentColumn, newIndex, record;
88
+ currentColumn, currentIndex, newIndex, record;
89
89
 
90
90
  if (me.hasSelection()) {
91
91
  currentColumn = view.getDataField(me.items[0]);
@@ -95,13 +95,16 @@ class CellModel extends BaseModel {
95
95
  record = store.getAt(0)
96
96
  }
97
97
 
98
- newIndex = (dataFields.indexOf(currentColumn) + step) % dataFields.length;
98
+ currentIndex = dataFields.indexOf(currentColumn);
99
+ newIndex = (currentIndex + step) % dataFields.length;
99
100
 
100
101
  while (newIndex < 0) {
101
102
  newIndex += dataFields.length
102
103
  }
103
104
 
104
- me.select(view.getCellId(record, dataFields[newIndex]))
105
+ me.select(view.getCellId(record, dataFields[newIndex]));
106
+
107
+ view.parent.scrollByColumns(currentIndex, step)
105
108
  }
106
109
 
107
110
  /**
@@ -112,10 +115,10 @@ class CellModel extends BaseModel {
112
115
  {view} = me,
113
116
  {store} = view,
114
117
  currentIndex = 0,
115
- dataField, newIndex, record;
118
+ dataField, newIndex;
116
119
 
117
120
  if (me.hasSelection()) {
118
- currentIndex = store.indexOf(view.getRecordByCellId(me.items[0]));
121
+ currentIndex = store.indexOf(view.getRecord(me.items[0]));
119
122
  dataField = view.getDataField(me.items[0])
120
123
  } else {
121
124
  dataField = me.dataFields[0]
@@ -127,10 +130,7 @@ class CellModel extends BaseModel {
127
130
  newIndex += store.getCount()
128
131
  }
129
132
 
130
- record = store.getAt(newIndex);
131
-
132
- me.select(view.getCellId(record, dataField));
133
-
133
+ me.select(view.getCellId(store.getAt(newIndex), dataField));
134
134
  view.scrollByRows(currentIndex, step)
135
135
  }
136
136