neo.mjs 8.30.0 → 8.31.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.
package/BACKERS.md CHANGED
@@ -1,22 +1,22 @@
1
1
  # Sponsors & Backers
2
2
 
3
- Truth to be told, the neo.mjs project is in need of sponsors & backers.
3
+ Truth to be told, the Neo.mjs project is in need of sponsors & backers.
4
4
 
5
5
  As an <a href="./LICENSE">MIT-licensed</a> open source project,
6
- you can use neo.mjs for free (as long as you stick to the license & copyright notice).
6
+ you can use Neo.mjs for free (as long as you stick to the license & copyright notice).
7
7
 
8
8
  Prior to the public release, the project was already at <a href=".github/NEOMJS_HISTORY.md">3720 commits</a>.
9
9
 
10
- To get neo.mjs to this point, I spent 3 years of my full and unpaid working time.
10
+ To get Neo.mjs to this point, I spent 3 years of my full and unpaid working time.
11
11
  This does not only include many 100h+ weeks and several sleepless nights,
12
12
  but I also burned most of my personal savings to get here.
13
13
 
14
- I did this, since I truly and deeply believe that neo.mjs has the potential to start a new era of UI development.
14
+ I did this, since I truly and deeply believe that Neo.mjs has the potential to start a new era of UI development.
15
15
 
16
16
  The current version is just a fraction of a much bigger picture I have in mind.
17
17
  Trust me, it is beautiful :)
18
18
 
19
- I can definitely say that I did use several other UI frameworks in the past and that neo.mjs boosted my productivity
19
+ I can definitely say that I did use several other UI frameworks in the past and that Neo.mjs boosted my productivity
20
20
  to an entire new level.
21
21
 
22
22
  This is something I would love to share with you!
@@ -29,35 +29,35 @@ By default, the main Readme will only include sponsors starting at the Gold tier
29
29
  (*) Only valid as long as the sponsorship stays active.
30
30
 
31
31
  # What will I sponsor?
32
- Obviously the continuing of the development for the neo.mjs framework.
32
+ Obviously the continuing of the development for the Neo.mjs framework.
33
33
  To get the idea of what this means in detail, please take a look at the: <a href=".github/VISION.md">Project Vision</a>.
34
34
 
35
- # Why should I sponsor neo.mjs?
35
+ # Why should I sponsor Neo.mjs?
36
36
  I am in need to make up for the invested time, which means that going forward,
37
37
  I have to spend a lot of my time on billable projects.
38
- Of course I will do my best to continue pushing neo.mjs in my free time.
38
+ Of course I will do my best to continue pushing Neo.mjs in my free time.
39
39
 
40
- With active sponsors I could invest more time into neo.mjs,
40
+ With active sponsors I could invest more time into Neo.mjs,
41
41
  which means that you will get new features, guides and bug-fixes a lot faster.
42
42
 
43
43
  # For developers:
44
44
  You can select a Backer tier ($10 / month), which would equal a cup of coffee for me every month (after taxes).
45
- This will not cover my living costs (unless neo.mjs goes viral), but it does have an impact on
45
+ This will not cover my living costs (unless Neo.mjs goes viral), but it does have an impact on
46
46
  my motivation for sure.
47
47
 
48
48
  # For companies:
49
49
  Starting at the bronze tier (special offer), you will be able to place your company logo on the main repository landing page (Readme.md).
50
- This will have a high impact on the neo.mjs community and make it easier for you
51
- to find neo.mjs developers in the future.
50
+ This will have a high impact on the Neo.mjs community and make it easier for you
51
+ to find Neo.mjs developers in the future.
52
52
 
53
53
  Obviously, sponsoring open source projects in general will increase your companies reputation.
54
54
 
55
- As soon as the neo.mjs community grows, it will also increase your company visibility in a good way.
55
+ As soon as the Neo.mjs community grows, it will also increase your company visibility in a good way.
56
56
 
57
- Please make sure to create a feature request ticket inside the neo.mjs <a href="../../issues">Issues Tracker</a>
57
+ Please make sure to create a feature request ticket inside the Neo.mjs <a href="../../issues">Issues Tracker</a>
58
58
  after signing a sponsor program to provide your company logo!
59
59
 
60
- ### Thanks a lot for your support of the neo.mjs project!<br/>
60
+ ### Thanks a lot for your support of the Neo.mjs project!<br/>
61
61
  <a href="https://github.com/sponsors/tobiu">Sponsor Tobias Uhlig</a>
62
62
 
63
63
  <br><br>
package/CONTRIBUTING.md CHANGED
@@ -1,15 +1,15 @@
1
- # neo.mjs Contributing Guide
2
- We are very excited that you are interested in contributing to neo.mjs.<br>
1
+ # Neo.mjs Contributing Guide
2
+ We are very excited that you are interested in contributing to Neo.mjs.<br>
3
3
  No worries, you don't need to be a guru, ninja or rockstar to support the project.
4
4
 
5
5
  ## What you can do to help:
6
6
 
7
- ### 1. neo.mjs just got released, so the most important thing right now is to get more eyes on the project.
7
+ ### 1. Neo.mjs just got released, so the most important thing right now is to get more eyes on the project.
8
8
  1. Add a star to this repository. Ok, this one was easy. Thank you!
9
- 2. Tell your friends about neo.mjs.
9
+ 2. Tell your friends about Neo.mjs.
10
10
  3. Write blog posts or post on social media (Facebook, LinkedIn, Twitter, etc.)
11
11
  4. Please stick to our <a href=".github/CODE_OF_CONDUCT.md">Code of Conduct</a>
12
- 5. Interested to see a neo.mjs session at a developer conference? Definitely possible. Just reach out!
12
+ 5. Interested to see a Neo.mjs session at a developer conference? Definitely possible. Just reach out!
13
13
 
14
14
  ### 2. Use the issues tracker
15
15
  1. In case you got an idea for a new feature
@@ -17,9 +17,9 @@ No worries, you don't need to be a guru, ninja or rockstar to support the projec
17
17
  1. Ideally, you create a new breaking test inside the tests folder.
18
18
  This saves a lot of time and ensures the bug will stay fixed once the ticket is resolved.
19
19
  3. Please like or comment on current tickets.
20
- This is a great help to figure out which tickets are the most important ones for the neo.mjs community.
20
+ This is a great help to figure out which tickets are the most important ones for the Neo.mjs community.
21
21
 
22
- ### 3. Contribute to the neo.mjs code base
22
+ ### 3. Contribute to the Neo.mjs code base
23
23
  1. Please ***always*** create a new issue inside our <a href="../../issues">Issues Tracker</a> first and wait for approval.
24
24
  This ensures that your idea fits the scope of the project and makes it less likely to get a rejected PR.
25
25
  We will do our best to reply to new tickets within 7d max. In case we don't, feel free to bump the ticket.
@@ -27,12 +27,12 @@ No worries, you don't need to be a guru, ninja or rockstar to support the projec
27
27
  Otherwise multiple contributors could work on the same item in parallel.
28
28
  3. Please make sure that pull requests are always related to an issue inside our <a href="../../issues">Issues Tracker</a>.
29
29
  4. Please create PRs for the dev branch and not for master.
30
- 5. Please do not copy code from other frameworks / libraries. So far, the entire neo.mjs code base is written from scratch.
30
+ 5. Please do not copy code from other frameworks / libraries. So far, the entire Neo.mjs code base is written from scratch.
31
31
  1. It is simply impossible to verify if contributed code is self-written.
32
32
  2. Adding new npm packages => dependencies / devDependencies is fine, in case there are no licensing conflicts.
33
33
  6. Refer to the <a href="./.github/CODEBASE_OVERVIEW.md">codebase overview</a> to understand how our repository is structured.
34
34
 
35
- ### 4. In case you created a nice app or component using neo.mjs, please let us know about it.
35
+ ### 4. In case you created a nice app or component using Neo.mjs, please let us know about it.
36
36
  1. We are always interested to feature client projects in blog posts or on social media.
37
37
 
38
38
  <br><br>
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.30.0'
23
+ * @member {String} version='8.31.0'
24
24
  */
25
- version: '8.30.0'
25
+ version: '8.31.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-03-07",
19
+ "datePublished": "2025-03-09",
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.30.0'
110
+ html : 'v8.31.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.30.0'
23
+ * @member {String} version='8.31.0'
24
24
  */
25
- version: '8.30.0'
25
+ version: '8.31.0'
26
26
  }
27
27
 
28
28
  /**
@@ -0,0 +1,40 @@
1
+ import Model from '../../../src/data/Model.mjs';
2
+
3
+ /**
4
+ * @class Neo.examples.grid.animatedRowSorting.MainModel
5
+ * @extends Neo.data.Model
6
+ */
7
+ class MainModel extends Model {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='Neo.examples.grid.animatedRowSorting.MainModel'
11
+ * @protected
12
+ */
13
+ className: 'Neo.examples.grid.animatedRowSorting.MainModel',
14
+ /**
15
+ * @member {Object[]} fields
16
+ */
17
+ fields: [{
18
+ name: 'country',
19
+ type: 'String'
20
+ }, {
21
+ name: 'firstname',
22
+ type: 'String'
23
+ }, {
24
+ name: 'id',
25
+ type: 'Int'
26
+ }, {
27
+ name: 'lastname',
28
+ type: 'String'
29
+ }, {
30
+ name: 'progress',
31
+ type: 'Int'
32
+ }],
33
+ /**
34
+ * @member {Boolean} trackModifiedFields=true
35
+ */
36
+ trackModifiedFields: true
37
+ }
38
+ }
39
+
40
+ export default Neo.setupClass(MainModel);
@@ -0,0 +1,36 @@
1
+ import Model from './MainModel.mjs';
2
+ import Store from '../../../src/data/Store.mjs';
3
+
4
+ /**
5
+ * @class Neo.examples.grid.animatedRowSorting.MainStore
6
+ * @extends Neo.data.Store
7
+ */
8
+ class MainStore extends Store {
9
+ static config = {
10
+ className: 'Neo.examples.grid.animatedRowSorting.MainStore',
11
+ model : Model,
12
+
13
+ data: [
14
+ {country : 'DE', firstname: 'Max', id: 1, lastname : 'Johnson', progress : 50},
15
+ {country : 'UK', firstname: 'Paul', id: 2, lastname : 'Walker', progress : 10},
16
+ {country : 'US', firstname: 'Sam', id: 3, lastname : 'Anderson', progress : 90},
17
+ {country : 'GR', firstname: 'William', id: 4, lastname : 'Wilson', progress : 30},
18
+ {country : 'AT', firstname: 'Carol', id: 5, lastname : 'Jackson', progress : 70},
19
+ {country : 'NL', firstname: 'Amanda', id: 6, lastname : 'King', progress : 100},
20
+ {country : 'FR', firstname: 'Sarah', id: 7, lastname : 'Scott', progress : 35}
21
+ ],
22
+
23
+ filters: [{
24
+ property: 'firstname',
25
+ operator: 'like',
26
+ value : null
27
+ }],
28
+
29
+ sorters: [{
30
+ property : 'id',
31
+ direction: 'ASC'
32
+ }]
33
+ }
34
+ }
35
+
36
+ export default Neo.setupClass(MainStore);
@@ -0,0 +1,87 @@
1
+ import BaseViewport from '../../../src/container/Viewport.mjs';
2
+ import GridContainer from '../../../src/grid/Container.mjs';
3
+ import MainStore from './MainStore.mjs';
4
+ import NumberField from '../../../src/form/field/Number.mjs';
5
+ import Toolbar from '../../../src/toolbar/Base.mjs';
6
+
7
+ /**
8
+ * @class Neo.examples.grid.animatedRowSorting.Viewport
9
+ * @extends Neo.container.Viewport
10
+ */
11
+ class Viewport extends BaseViewport {
12
+ static config = {
13
+ /**
14
+ * @member {String} className='Neo.examples.grid.animatedRowSorting.Viewport'
15
+ * @protected
16
+ */
17
+ className: 'Neo.examples.grid.animatedRowSorting.Viewport',
18
+ /**
19
+ * @member {Object} style={padding:'1em'}
20
+ */
21
+ style: {padding: '1em'},
22
+ /**
23
+ * @member {Object[]} items
24
+ */
25
+ items: [{
26
+ module: Toolbar,
27
+ flex : 'none',
28
+ style : {marginBottom: '1em', maxWidth: 'fit-content'},
29
+
30
+ items : [{
31
+ ntype : 'textfield',
32
+ labelPosition: 'inline',
33
+ labelText : 'Firstname',
34
+ listeners : {change: 'up.onFirstnameFieldChange'},
35
+ width : 120
36
+ },{
37
+ module : NumberField,
38
+ clearable : false,
39
+ labelPosition: 'inline',
40
+ labelText : 'Transition Duration',
41
+ listeners : {change: 'up.onTransitionDurationFieldChange'},
42
+ maxValue : 1000,
43
+ minValue : 200,
44
+ stepSize : 100,
45
+ style : {marginLeft: '1em'},
46
+ value : 500,
47
+ width : 180
48
+ }]
49
+ }, {
50
+ module : GridContainer,
51
+ reference: 'grid',
52
+ store : MainStore,
53
+
54
+ columnDefaults: {
55
+ width: 200
56
+ },
57
+
58
+ viewConfig: {
59
+ animatedRowSorting: true
60
+ },
61
+
62
+ columns: [
63
+ {dataField: 'id', text: 'Id', width: 100},
64
+ {dataField: 'firstname', text: 'Firstname'},
65
+ {dataField: 'lastname', text: 'Lastname'},
66
+ {dataField: 'progress', text: 'Progress', type: 'progress'},
67
+ {dataField: 'country', text: 'Country'}
68
+ ]
69
+ }]
70
+ }
71
+
72
+ /**
73
+ * @param {Object} data
74
+ */
75
+ onFirstnameFieldChange(data) {
76
+ this.getReference('grid').store.getFilter('firstname').value = data.value
77
+ }
78
+
79
+ /**
80
+ * @param {Object} data
81
+ */
82
+ onTransitionDurationFieldChange(data) {
83
+ this.getReference('grid').view.getPlugin('grid-animate-rows').transitionDuration = data.value
84
+ }
85
+ }
86
+
87
+ export default Neo.setupClass(Viewport);
@@ -0,0 +1,6 @@
1
+ import Viewport from './Viewport.mjs';
2
+
3
+ export const onStart = () => Neo.app({
4
+ mainView: Viewport,
5
+ name : 'Neo.examples.grid.animatedRowSorting'
6
+ })
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE HTML>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <meta charset="UTF-8">
6
+ <title>Neo Grid: Animated Row Sorting</title>
7
+ </head>
8
+ <body>
9
+ <script src="../../../src/MicroLoader.mjs" type="module"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,8 @@
1
+ {
2
+ "appPath" : "examples/grid/animatedRowSorting/app.mjs",
3
+ "basePath" : "../../../",
4
+ "environment" : "development",
5
+ "mainPath" : "./Main.mjs",
6
+ "mainThreadAddons": ["DragDrop", "Navigator", "ResizeObserver", "Stylesheet"],
7
+ "themes" : ["neo-theme-dark", "neo-theme-light"]
8
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.30.0",
3
+ "version": "8.31.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -48,7 +48,7 @@
48
48
  "homepage": "https://neomjs.com/",
49
49
  "devDependencies": {
50
50
  "@fortawesome/fontawesome-free": "^6.7.2",
51
- "autoprefixer": "^10.4.20",
51
+ "autoprefixer": "^10.4.21",
52
52
  "chalk": "^5.4.1",
53
53
  "clean-webpack-plugin": "^4.0.0",
54
54
  "commander": "^13.1.0",
@@ -56,7 +56,7 @@
56
56
  "envinfo": "^7.14.0",
57
57
  "fs-extra": "^11.3.0",
58
58
  "highlightjs-line-numbers.js": "^2.9.0",
59
- "inquirer": "^12.4.2",
59
+ "inquirer": "^12.4.3",
60
60
  "marked": "^15.0.7",
61
61
  "monaco-editor": "0.50.0",
62
62
  "neo-jsdoc": "1.0.1",
@@ -263,12 +263,12 @@ const DefaultConfig = {
263
263
  useVdomWorker: true,
264
264
  /**
265
265
  * buildScripts/injectPackageVersion.mjs will update this value
266
- * @default '8.30.0'
266
+ * @default '8.31.0'
267
267
  * @memberOf! module:Neo
268
268
  * @name config.version
269
269
  * @type String
270
270
  */
271
- version: '8.30.0'
271
+ version: '8.31.0'
272
272
  };
273
273
 
274
274
  Object.assign(DefaultConfig, {
@@ -360,7 +360,8 @@ class Collection extends Base {
360
360
  * @protected
361
361
  */
362
362
  cacheUpdate(opts) {
363
- console.log('cacheUpdate', opts, this[toAddArray]);
363
+ // disabled for now
364
+ // console.log('cacheUpdate', opts, this[toAddArray]);
364
365
  return;
365
366
 
366
367
  let me = this,
@@ -400,7 +400,7 @@ class Store extends Base {
400
400
  onCollectionMutate(opts) {
401
401
  let me = this;
402
402
 
403
- if (me.configsApplied && !me.isLoading) {
403
+ if (me.isConstructed && !me.isLoading) {
404
404
  me.fire('load', me.items)
405
405
  }
406
406
  }
@@ -411,9 +411,8 @@ class Store extends Base {
411
411
  onCollectionSort() {
412
412
  let me = this;
413
413
 
414
- if (me.configsApplied) {
415
- // console.log('onCollectionSort', me.collection.items);
416
- // me.fire('load', me.items);
414
+ if (me.isConstructed) {
415
+ //me.fire('load', me.items)
417
416
  }
418
417
  }
419
418
 
@@ -426,14 +425,18 @@ class Store extends Base {
426
425
  let me = this;
427
426
 
428
427
  if (me.data) {
429
- me.afterSetData(me.data);
428
+ me.afterSetData(me.data)
430
429
  }
431
430
 
432
- if (me.autoLoad) {
433
- me.timeout(100).then(() => { // todo
431
+ // Being constructed does not mean that related afterSetStore() methods got executed
432
+ // => break the sync flow to ensure potential listeners got applied
433
+ Promise.resolve().then(() => {
434
+ if (me.getCount() > 0) {
435
+ me.fire('load', me.items)
436
+ } else if (me.autoLoad) {
434
437
  me.load()
435
- })
436
- }
438
+ }
439
+ })
437
440
  }
438
441
 
439
442
  /**
@@ -322,6 +322,29 @@ class GridContainer extends BaseContainer {
322
322
  }
323
323
  }
324
324
 
325
+ /**
326
+ * Triggered after the store config got changed
327
+ * @param {Number} value
328
+ * @param {Number} oldValue
329
+ * @protected
330
+ */
331
+ afterSetStore(value, oldValue) {
332
+ let me = this,
333
+ listeners = {
334
+ filter: me.onStoreFilter,
335
+ load : me.onStoreLoad,
336
+ scope : me
337
+ };
338
+
339
+ value ?.on(listeners);
340
+ oldValue?.un(listeners);
341
+
342
+ // in case we dynamically change the store, the view needs to get the new reference
343
+ if (me.view) {
344
+ me.view.store = value
345
+ }
346
+ }
347
+
325
348
  /**
326
349
  * Triggered before the columns config gets changed.
327
350
  * @param {Object[]} value
@@ -348,36 +371,13 @@ class GridContainer extends BaseContainer {
348
371
 
349
372
  /**
350
373
  * Triggered before the store config gets changed.
351
- * @param {Neo.data.Store} value
352
- * @param {Neo.data.Store} oldValue
374
+ * @param {Object|Neo.data.Store|null} value
375
+ * @param {Neo.data.Store} oldValue
353
376
  * @protected
354
377
  */
355
378
  beforeSetStore(value, oldValue) {
356
- oldValue?.destroy();
357
-
358
379
  if (value) {
359
- let me = this,
360
-
361
- listeners = {
362
- filter : me.onStoreFilter,
363
- load : me.onStoreLoad,
364
- recordChange: me.onStoreRecordChange,
365
- scope : me
366
- };
367
-
368
- if (value instanceof Store) {
369
- value.on(listeners);
370
- value.getCount() > 0 && me.onStoreLoad(value.items)
371
- } else {
372
- value = ClassSystemUtil.beforeSetInstance(value, Store, {
373
- listeners
374
- })
375
- }
376
-
377
- // in case we dynamically change the store, the view needs to get the new reference
378
- if (me.view) {
379
- me.view.store = value
380
- }
380
+ value = ClassSystemUtil.beforeSetInstance(value, Store)
381
381
  }
382
382
 
383
383
  return value
@@ -476,24 +476,14 @@ class GridContainer extends BaseContainer {
476
476
  })
477
477
  }
478
478
 
479
- /**
480
- * @param {Array} inputData
481
- */
482
- createViewData(inputData) {
483
- let me = this;
484
-
485
- me.getVdomRoot()['aria-rowcount'] = inputData.length + 2; // 1 based & the header row counts as well
486
- me.update();
487
-
488
- me.view.createViewData()
489
- }
490
-
491
479
  /**
492
480
  * @param args
493
481
  */
494
482
  destroy(...args) {
495
483
  let me = this;
496
484
 
485
+ me.store = null; // remove the listeners
486
+
497
487
  me.scrollManager.destroy();
498
488
 
499
489
  me.mounted && Neo.main.addon.ResizeObserver.unregister({
@@ -570,14 +560,14 @@ class GridContainer extends BaseContainer {
570
560
 
571
561
  me.store.sort(opts);
572
562
  me.removeSortingCss(opts.property);
573
- me.onStoreLoad(me.store.items)
563
+ me.view.onStoreLoad()
574
564
  }
575
565
 
576
566
  /**
577
567
  *
578
568
  */
579
569
  onStoreFilter() {
580
- this.onStoreLoad(this.store.items)
570
+ this.updateRowCount()
581
571
  }
582
572
 
583
573
  /**
@@ -587,37 +577,13 @@ class GridContainer extends BaseContainer {
587
577
  onStoreLoad(data) {
588
578
  let me = this;
589
579
 
590
- if (me.rendered) {
591
- me.createViewData(data);
592
-
593
- me.timeout(50).then(() => {
594
- Neo.main.DomAccess.scrollTo({
595
- direction: 'top',
596
- id : me.view.vdom.id,
597
- value : 0
598
- })
599
- })
580
+ me.updateRowCount();
600
581
 
601
- if (me.store.sorters.length < 1) {
602
- me.removeSortingCss()
603
- }
582
+ if (me.store.sorters?.length < 1) {
583
+ me.removeSortingCss()
604
584
  }
605
585
  }
606
586
 
607
- /**
608
- * Gets triggered after changing the value of a record field.
609
- * E.g. myRecord.foo = 'bar';
610
- * @param {Object} opts
611
- * @param {String} opts.field The name of the field which got changed
612
- * @param {Neo.data.Model} opts.model The model instance of the changed record
613
- * @param {*} opts.oldValue
614
- * @param {Object} opts.record
615
- * @param {*} opts.value
616
- */
617
- onStoreRecordChange(opts) {
618
- this.view.onStoreRecordChange(opts)
619
- }
620
-
621
587
  /**
622
588
  * @param {Boolean} silent=false
623
589
  * @returns {Promise<void>}
@@ -701,6 +667,16 @@ class GridContainer extends BaseContainer {
701
667
  })
702
668
  }
703
669
  }
670
+
671
+ /**
672
+ * @param {Boolean} silent=false
673
+ */
674
+ updateRowCount(silent=false) {
675
+ let me = this;
676
+
677
+ this.getVdomRoot()['aria-rowcount'] = me.store.getCount() + 2; // 1 based & the header row counts as well
678
+ !silent && this.update()
679
+ }
704
680
  }
705
681
 
706
682
  export default Neo.setupClass(GridContainer);
package/src/grid/View.mjs CHANGED
@@ -21,6 +21,10 @@ class GridView extends Component {
21
21
  * @protected
22
22
  */
23
23
  ntype: 'grid-view',
24
+ /**
25
+ * @member {Boolean} animatedRowSorting_=false
26
+ */
27
+ animatedRowSorting_: false,
24
28
  /**
25
29
  * Internal flag. Gets calculated when mounting the grid.Container
26
30
  * @member {Number} availableHeight_=0
@@ -93,6 +97,11 @@ class GridView extends Component {
93
97
  * @protected
94
98
  */
95
99
  mountedRows: [0, 0],
100
+ /**
101
+ * Optional config values for Neo.grid.plugin.AnimateRows
102
+ * @member {Object} pluginAnimateRowsConfig=null
103
+ */
104
+ pluginAnimateRowsConfig: null,
96
105
  /**
97
106
  * @member {String} role='rowgroup'
98
107
  */
@@ -204,6 +213,30 @@ class GridView extends Component {
204
213
  }])
205
214
  }
206
215
 
216
+ /**
217
+ * Triggered after the animatedRowSorting config got changed
218
+ * @param {Boolean} value
219
+ * @param {Boolean} oldValue
220
+ * @protected
221
+ */
222
+ afterSetAnimatedRowSorting(value, oldValue) {
223
+ if (value && !this.getPlugin('grid-animate-rows')) {
224
+ import('./plugin/AnimateRows.mjs').then(module => {
225
+ let me = this,
226
+ plugins = me.plugins || [];
227
+
228
+ plugins.push({
229
+ module : module.default,
230
+ appName : me.appName,
231
+ windowId: me.windowId,
232
+ ...me.pluginAnimateRowsConfig
233
+ });
234
+
235
+ me.plugins = plugins
236
+ })
237
+ }
238
+ }
239
+
207
240
  /**
208
241
  * Triggered after the availableHeight config got changed
209
242
  * @param {Number} value
@@ -364,6 +397,25 @@ class GridView extends Component {
364
397
  oldValue !== undefined && this.createViewData()
365
398
  }
366
399
 
400
+ /**
401
+ * Triggered after the store config got changed
402
+ * @param {Number} value
403
+ * @param {Number} oldValue
404
+ * @protected
405
+ */
406
+ afterSetStore(value, oldValue) {
407
+ let me = this,
408
+ listeners = {
409
+ filter : me.onStoreFilter,
410
+ load : me.onStoreLoad,
411
+ recordChange: me.onStoreRecordChange,
412
+ scope : me
413
+ };
414
+
415
+ oldValue?.un(listeners);
416
+ value ?.on(listeners);
417
+ }
418
+
367
419
  /**
368
420
  * @param {Object} data
369
421
  * @param {String} [data.cellId]
@@ -608,7 +660,8 @@ class GridView extends Component {
608
660
  * @param args
609
661
  */
610
662
  destroy(...args) {
611
- this.store = null;
663
+ this.store = null; // remove the listeners
664
+
612
665
  super.destroy(...args)
613
666
  }
614
667
 
@@ -849,6 +902,33 @@ class GridView extends Component {
849
902
  this.fireRowEvent(data, 'rowDoubleClick')
850
903
  }
851
904
 
905
+ /**
906
+ *
907
+ */
908
+ onStoreFilter() {
909
+ this.onStoreLoad()
910
+ }
911
+
912
+ /**
913
+ * @param {Object[]} data
914
+ * @protected
915
+ */
916
+ onStoreLoad(data) {
917
+ let me = this;
918
+
919
+ me.createViewData();
920
+
921
+ if (me.mounted) {
922
+ me.timeout(50).then(() => {
923
+ Neo.main.DomAccess.scrollTo({
924
+ direction: 'top',
925
+ id : me.vdom.id,
926
+ value : 0
927
+ })
928
+ })
929
+ }
930
+ }
931
+
852
932
  /**
853
933
  * @param {Object} data
854
934
  * @param {Object[]} data.fields Each field object contains the keys: name, oldValue, value
@@ -84,7 +84,6 @@ class Component extends Column {
84
84
  delete componentConfig.module;
85
85
  delete componentConfig.ntype;
86
86
 
87
-
88
87
  componentConfig[recordProperty] = record;
89
88
 
90
89
  component.set(componentConfig)
@@ -176,7 +176,7 @@ class Button extends BaseButton {
176
176
  let me = this,
177
177
  {cls} = me;
178
178
 
179
- if (value === true) {
179
+ if (value) {
180
180
  NeoArray.remove(cls, 'neo-sort-hidden');
181
181
 
182
182
  me.addDomListeners({
@@ -211,7 +211,6 @@ class Button extends BaseButton {
211
211
  */
212
212
  destroy(...args) {
213
213
  this.filterField?.destroy();
214
-
215
214
  super.destroy(...args)
216
215
  }
217
216
 
@@ -0,0 +1,234 @@
1
+ import Base from '../../plugin/Base.mjs';
2
+ import CssUtil from '../../util/Css.mjs';
3
+ import NeoArray from '../../util/Array.mjs';
4
+
5
+ /**
6
+ * @class Neo.grid.plugin.AnimateRows
7
+ * @extends Neo.plugin.Base
8
+ */
9
+ class AnimateRows extends Base {
10
+ /**
11
+ * Valid values for transitionEasing
12
+ * @member {String[]} transitionEasings=['ease','ease-in','ease-out','ease-in-out','linear']
13
+ * @protected
14
+ * @static
15
+ */
16
+ static transitionEasings = ['ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear']
17
+
18
+ static config = {
19
+ /**
20
+ * @member {String} className='Neo.grid.plugin.AnimateRows'
21
+ * @protected
22
+ */
23
+ className: 'Neo.grid.plugin.AnimateRows',
24
+ /**
25
+ * @member {String} ntype='plugin-grid-animate-rows'
26
+ * @protected
27
+ */
28
+ ntype: 'plugin-grid-animate-rows',
29
+ /**
30
+ * Time in ms. Please ensure to match the CSS based value, in case you change the default.
31
+ * @member {Number} transitionDuration_=500
32
+ */
33
+ transitionDuration_: 500,
34
+ /**
35
+ * The easing used for fadeIn, fadeOut and position changes.
36
+ * Valid values: 'ease','ease-in','ease-out','ease-in-out','linear'
37
+ * @member {String} transitionEasing_='ease-out'
38
+ */
39
+ transitionEasing_: 'ease-out'
40
+ }
41
+
42
+ /**
43
+ * The id of the setTimeout() call which gets triggered after a transition is done.
44
+ * @member {Number|null} transitionTimeoutId=null
45
+ * @protected
46
+ */
47
+ transitionTimeoutId = null
48
+
49
+ /**
50
+ * @param {Object} config
51
+ */
52
+ construct(config) {
53
+ super.construct(config);
54
+
55
+ let me = this,
56
+ {owner} = me,
57
+ {store} = owner;
58
+
59
+ // Remove the previous view listeners
60
+ owner.store = null;
61
+
62
+ owner.onStoreFilter = me.onStoreFilter.bind(me);
63
+ owner.onStoreLoad = me.onStoreLoad .bind(me);
64
+
65
+ // Add the re-bound listeners
66
+ owner.store = store;
67
+
68
+ me.updateTransitionDetails()
69
+ }
70
+
71
+ /**
72
+ * Triggered after the transitionDuration config got changed.
73
+ * @param {Number} value
74
+ * @param {Number} oldValue
75
+ * @protected
76
+ */
77
+ afterSetTransitionDuration(value, oldValue) {
78
+ this.isConstructed && this.updateTransitionDetails(Neo.isNumber(oldValue))
79
+ }
80
+
81
+ /**
82
+ * Triggered after the transitionEasing config got changed.
83
+ * @param {String} value
84
+ * @param {String} oldValue
85
+ * @protected
86
+ */
87
+ afterSetTransitionEasing(value, oldValue) {
88
+ this.isConstructed && this.updateTransitionDetails(!!oldValue)
89
+ }
90
+
91
+ /**
92
+ * Triggered before the transitionEasing config gets changed
93
+ * @param {String} value
94
+ * @param {String} oldValue
95
+ * @protected
96
+ */
97
+ beforeSetTransitionEasing(value, oldValue) {
98
+ return this.beforeSetEnumValue(value, oldValue, 'transitionEasing')
99
+ }
100
+
101
+ /**
102
+ * @param {Object} args
103
+ */
104
+ destroy(...args) {
105
+ CssUtil.deleteRules(this.appName, `#${this.owner.id} .neo-grid-row`);
106
+ super.destroy(...args)
107
+ }
108
+
109
+ /**
110
+ * @param {Object} data
111
+ * @param {Boolean} data.isFiltered
112
+ * @param {Object[]} data.items
113
+ * @param {Object[]} data.oldItems
114
+ * @param {Neo.data.Store} data.scope
115
+ */
116
+ onStoreFilter(data) {
117
+ this.updateView()
118
+ }
119
+
120
+ /**
121
+ * @param {Object[]} data
122
+ * @protected
123
+ */
124
+ onStoreLoad(data) {
125
+ this.updateView()
126
+ }
127
+
128
+ /**
129
+ * We do not want to apply the style to each list item itself,
130
+ * so we are using Neo.util.Css
131
+ * @param {Boolean} deleteRule=false
132
+ * @protected
133
+ */
134
+ async updateTransitionDetails(deleteRule=false) {
135
+ let me = this,
136
+ duration = me.transitionDuration,
137
+ easing = me.transitionEasing,
138
+ {id} = me.owner;
139
+
140
+ if (deleteRule) {
141
+ await CssUtil.deleteRules(me.appName, `#${id} .neo-grid-row`)
142
+ }
143
+
144
+ CssUtil.insertRules(me.appName, [
145
+ `#${id} .neo-grid-row {`,
146
+ 'transition:',
147
+ `background-color ${duration}ms ${easing},`,
148
+ `opacity ${duration}ms ${easing},`,
149
+ `transform ${duration}ms ${easing}`,
150
+ '}'
151
+ ].join(''))
152
+ }
153
+
154
+ /**
155
+ *
156
+ */
157
+ updateView() {
158
+ let me = this,
159
+ {owner} = me,
160
+ {mountedRows} = owner,
161
+ addedRows = [],
162
+ hasChange = false,
163
+ map = {},
164
+ rowsContainer = owner.getVdomRoot().cn,
165
+ id, mapItem, record, row, rowIndex, transform;
166
+
167
+ rowsContainer.forEach(row => {
168
+ map[row.id] = row
169
+ });
170
+
171
+ // Creates the new start & end indexes inside mountedRows
172
+ owner.updateMountedAndVisibleRows();
173
+
174
+ for (rowIndex=mountedRows[0]; rowIndex < mountedRows[1]; rowIndex++) {
175
+ record = owner.store.getAt(rowIndex);
176
+ id = owner.getRowId(record, rowIndex)
177
+ mapItem = map[id];
178
+
179
+ if (mapItem) {
180
+ // Inside the map (previous state) & vdom => move OP
181
+ transform = `translate(0px, ${rowIndex * owner.rowHeight}px)`;
182
+
183
+ if (mapItem.style.transform !== transform) {
184
+ mapItem.style.opacity = .9; // slightly less than 1 to see visual overlays while moving
185
+ mapItem.style.transform = transform;
186
+ NeoArray.toggle(mapItem.cls, 'neo-even', rowIndex % 2 !== 0);
187
+ hasChange = true
188
+ }
189
+
190
+ delete map[id]
191
+ } else {
192
+ // Inside the vdom, but not the map => insert OP
193
+ row = owner.createRow({record, rowIndex});
194
+
195
+ row.style.opacity = 0;
196
+
197
+ addedRows .push(row);
198
+ rowsContainer.push(row);
199
+
200
+ owner.updateDepth = -1; // Added rows might contain components
201
+ hasChange = true
202
+ }
203
+ }
204
+
205
+ // Only rows which need to get removed are still inside the map
206
+ Object.values(map).forEach(row => {
207
+ row.style.opacity = 0;
208
+ hasChange = true
209
+ });
210
+
211
+ if (hasChange) {
212
+ clearTimeout(me.transitionTimeoutId);
213
+
214
+ owner.promiseUpdate().then(() => {
215
+ if (addedRows.length > 0) {
216
+ // Added rows need a 2nd DOM update to change the opacity from 0 to 1.
217
+ // If we added them with 1 directly, there would not be a fade-in transition.
218
+ addedRows.forEach(row => {
219
+ row.style.opacity = 1
220
+ });
221
+
222
+ owner.update()
223
+ }
224
+ });
225
+
226
+ me.transitionTimeoutId = setTimeout(() => {
227
+ me.transitionTimeoutId = null;
228
+ owner.createViewData()
229
+ }, me.transitionDuration)
230
+ }
231
+ }
232
+ }
233
+
234
+ export default Neo.setupClass(AnimateRows);
@@ -80,7 +80,7 @@ class Animate extends Base {
80
80
  owner.onStoreFilter = me.onStoreFilter.bind(me);
81
81
  owner.onStoreSort = me.onStoreSort .bind(me);
82
82
 
83
- this.updateTransitionDetails(false)
83
+ this.updateTransitionDetails()
84
84
  }
85
85
 
86
86
  /**
@@ -152,6 +152,14 @@ class Animate extends Base {
152
152
  return item
153
153
  }
154
154
 
155
+ /**
156
+ * @param {Object} args
157
+ */
158
+ destroy(...args) {
159
+ CssUtil.deleteRules(this.appName, `#${this.owner.id} .neo-list-item`);
160
+ super.destroy(...args)
161
+ }
162
+
155
163
  /**
156
164
  * @param {Object} record
157
165
  * @param {Number} index
@@ -412,18 +420,19 @@ class Animate extends Base {
412
420
  }
413
421
 
414
422
  /**
415
- * We do not want to apply the style to each list item itself,
416
- * so we are using Neo.util.Css
417
- * @param {Boolean} deleteRule
423
+ * We do not want to apply the style to each list item itself, so we are using Neo.util.Css
424
+ * @param {Boolean} deleteRule=false
418
425
  * @protected
419
426
  */
420
- updateTransitionDetails(deleteRule) {
427
+ async updateTransitionDetails(deleteRule=false) {
421
428
  let me = this,
422
429
  duration = me.transitionDuration,
423
430
  easing = me.transitionEasing,
424
- id = me.owner.id;
431
+ {id} = me.owner;
425
432
 
426
- deleteRule && CssUtil.deleteRules(me.appName, `#${id} .neo-list-item`);
433
+ if (deleteRule) {
434
+ await CssUtil.deleteRules(me.appName, `#${id} .neo-list-item`)
435
+ }
427
436
 
428
437
  CssUtil.insertRules(me.appName, [
429
438
  `#${id} .neo-list-item {`,
@@ -295,18 +295,16 @@ class Manager extends Base {
295
295
 
296
296
  /**
297
297
  * Handler method for worker message events
298
- * @param {Object} e
298
+ * @param {Object} event
299
299
  */
300
- onWorkerMessage(e) {
300
+ onWorkerMessage(event) {
301
301
  let me = this,
302
- {data} = e,
302
+ {data} = event,
303
303
  transfer = null,
304
304
  promise;
305
305
 
306
306
  const {action, destination: dest, replyId} = data;
307
307
 
308
- // console.log('Main: Incoming Worker message: ' + data.origin + ':' + action, data);
309
-
310
308
  me.fire('message:'+action, data);
311
309
 
312
310
  if (action === 'reply') {
@@ -317,14 +315,14 @@ class Manager extends Base {
317
315
  data.data.autoMount && me.fire('automount', data);
318
316
  data.data.updateVdom && me.fire('updateVdom', data);
319
317
 
320
- // we want to delay the message until the rendering queue has processed it
321
- // see: https://github.com/neomjs/neo/issues/2864
318
+ // We want to delay the message until the rendering queue has processed it
319
+ // See: https://github.com/neomjs/neo/issues/2864
322
320
  me.promiseForwardMessage(data).then(msgData => {
323
321
  me.sendMessage(msgData.destination, msgData)
324
322
  })
325
323
  }
326
324
  } else {
327
- if (data.destination === 'main') {
325
+ if (dest === 'main') {
328
326
  data = data.data
329
327
  }
330
328