neo.mjs 8.23.0 → 8.25.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.
@@ -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
 
@@ -96,7 +96,7 @@ class SortZone extends BaseSortZone {
96
96
  mounted() {
97
97
  Neo.main.DomAccess.scrollTo({
98
98
  id : viewWrapperId,
99
- value : view.scrollPosition.y,
99
+ value : view.scrollTop,
100
100
  windowId: this.windowId
101
101
  })
102
102
  }
@@ -1,9 +1,10 @@
1
- import BaseContainer from '../container/Base.mjs';
2
- import ClassSystemUtil from '../util/ClassSystem.mjs';
3
- import GridScrollbar from './Scrollbar.mjs';
4
- import GridView from './View.mjs';
5
- import Store from '../data/Store.mjs';
6
- import * as header from './header/_export.mjs';
1
+ import BaseContainer from '../container/Base.mjs';
2
+ import ClassSystemUtil from '../util/ClassSystem.mjs';
3
+ import GridView from './View.mjs';
4
+ import ScrollManager from './ScrollManager.mjs';
5
+ import Store from '../data/Store.mjs';
6
+ import VerticalScrollbar from './VerticalScrollbar.mjs';
7
+ import * as header from './header/_export.mjs';
7
8
 
8
9
  /**
9
10
  * @class Neo.grid.Container
@@ -119,17 +120,10 @@ class GridContainer extends BaseContainer {
119
120
  */
120
121
  initialResizeEvent = true
121
122
  /**
122
- * Flag for identifying the ownership of a touchmove operation
123
- * @member {Boolean} isTouchMoveOwner=false
123
+ * @member {Neo.grid.ScrollManager|null} scrollManager=null
124
124
  * @protected
125
125
  */
126
- isTouchMoveOwner = false
127
- /**
128
- * Storing touchmove position for mobile envs
129
- * @member {Number} lastTouchY=0
130
- * @protected
131
- */
132
- lastTouchY = 0
126
+ scrollManager = null
133
127
 
134
128
  /**
135
129
  * Convenience method to access the Neo.grid.header.Toolbar
@@ -176,7 +170,7 @@ class GridContainer extends BaseContainer {
176
170
  }];
177
171
 
178
172
  me.scrollbar = Neo.create({
179
- module : GridScrollbar,
173
+ module : VerticalScrollbar,
180
174
  appName,
181
175
  parentId: me.id,
182
176
  rowHeight,
@@ -192,11 +186,29 @@ class GridContainer extends BaseContainer {
192
186
 
193
187
  me.addDomListeners({
194
188
  resize: me.onResize,
195
- scroll: me.onScroll,
196
189
  scope : me
197
190
  })
198
191
  }
199
192
 
193
+ /**
194
+ * @param {Boolean} mounted
195
+ * @protected
196
+ */
197
+ async addResizeObserver(mounted) {
198
+ let me = this,
199
+ {windowId} = me,
200
+ ResizeObserver = await Neo.currentWorker.getAddon('ResizeObserver', windowId),
201
+ resizeParams = {id: me.id, windowId};
202
+
203
+ if (mounted) {
204
+ ResizeObserver.register(resizeParams);
205
+ await me.passSizeToView()
206
+ } else {
207
+ me.initialResizeEvent = true;
208
+ ResizeObserver.unregister(resizeParams)
209
+ }
210
+ }
211
+
200
212
  /**
201
213
  * Triggered after the cellEditing config got changed
202
214
  * @param {Boolean} value
@@ -245,25 +257,6 @@ class GridContainer extends BaseContainer {
245
257
  }
246
258
  }
247
259
 
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
260
  /**
268
261
  * Triggered after the mounted config got changed
269
262
  * @param {Boolean} value
@@ -475,6 +468,8 @@ class GridContainer extends BaseContainer {
475
468
  destroy(...args) {
476
469
  let me = this;
477
470
 
471
+ me.scrollManager.destroy();
472
+
478
473
  me.mounted && Neo.main.addon.ResizeObserver.unregister({
479
474
  id : me.id,
480
475
  windowId: me.windowId
@@ -513,6 +508,21 @@ class GridContainer extends BaseContainer {
513
508
  return `${this.id}__wrapper`
514
509
  }
515
510
 
511
+ /**
512
+ *
513
+ */
514
+ onConstructed() {
515
+ super.onConstructed();
516
+
517
+ let me = this;
518
+
519
+ me.scrollManager = Neo.create({
520
+ module : ScrollManager,
521
+ gridContainer: me,
522
+ gridView : me.view
523
+ })
524
+ }
525
+
516
526
  /**
517
527
  * @param {Object} data
518
528
  */
@@ -530,43 +540,6 @@ class GridContainer extends BaseContainer {
530
540
  }
531
541
  }
532
542
 
533
- /**
534
- * @param {Object} data
535
- * @param {Number} data.scrollLeft
536
- * @param {Object} data.target
537
- * @param {Object} data.touches
538
- */
539
- onScroll({scrollLeft, target, touches}) {
540
- let me = this,
541
- deltaY, lastTouchY;
542
-
543
- // We must ignore events for grid-scrollbar
544
- if (target.id.includes('grid-container')) {
545
- me.headerToolbar.scrollLeft = scrollLeft;
546
- me.view.scrollPosition = {x: scrollLeft, y: me.view.scrollPosition.y};
547
-
548
- if (touches) {
549
- if (!me.view.isTouchMoveOwner) {
550
- me.isTouchMoveOwner = true
551
- }
552
-
553
- if (me.isTouchMoveOwner) {
554
- lastTouchY = touches.lastTouch.clientY - touches.firstTouch.clientY;
555
- deltaY = me.lastTouchY - lastTouchY;
556
-
557
- deltaY !== 0 && Neo.main.DomAccess.scrollTo({
558
- direction: 'top',
559
- id : me.view.vdom.id,
560
- value : me.view.scrollPosition.y + deltaY
561
- })
562
-
563
- me.lastTouchY = lastTouchY
564
- }
565
- }
566
-
567
- }
568
- }
569
-
570
543
  /**
571
544
  * @param {Object} opts
572
545
  * @param {String} opts.direction
@@ -657,6 +630,58 @@ class GridContainer extends BaseContainer {
657
630
  }
658
631
  })
659
632
  }
633
+
634
+ /**
635
+ * Used for keyboard navigation (selection models)
636
+ * @param {Number} index
637
+ * @param {Number} step
638
+ */
639
+ scrollByColumns(index, step) {
640
+ let me = this,
641
+ {view} = me,
642
+ {columnPositions, containerWidth, mountedColumns, visibleColumns} = view,
643
+ countColumns = columnPositions.getCount(),
644
+ newIndex = index + step,
645
+ column, mounted, scrollLeft, visible;
646
+
647
+ if (newIndex >= countColumns) {
648
+ newIndex %= countColumns;
649
+ step = newIndex - index
650
+ }
651
+
652
+ while (newIndex < 0) {
653
+ newIndex += countColumns;
654
+ step += countColumns
655
+ }
656
+
657
+ mounted = newIndex >= mountedColumns[0] && newIndex <= mountedColumns[1];
658
+
659
+ // Not using >= or <=, since the first / last column might not be fully visible
660
+ visible = newIndex > visibleColumns[0] && newIndex < visibleColumns[1];
661
+
662
+ if (!visible) {
663
+ // Leaving the mounted area will re-calculate the visibleColumns for us
664
+ if (mounted) {
665
+ visibleColumns[0] += step;
666
+ visibleColumns[1] += step
667
+ }
668
+
669
+ column = columnPositions.getAt(newIndex);
670
+
671
+ if (step < 0) {
672
+ scrollLeft = column.x
673
+ } else {
674
+ scrollLeft = column.x - containerWidth + column.width
675
+ }
676
+
677
+ Neo.main.DomAccess.scrollTo({
678
+ direction: 'left',
679
+ id : me.id,
680
+ value : scrollLeft,
681
+ windowId : me.windowId
682
+ })
683
+ }
684
+ }
660
685
  }
661
686
 
662
687
  export default Neo.setupClass(GridContainer);