neo.mjs 6.10.2 → 6.10.3

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='6.10.2'
23
+ * @member {String} version='6.10.3'
24
24
  */
25
- version: '6.10.2'
25
+ version: '6.10.3'
26
26
  }
27
27
 
28
28
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.10.2'
23
+ * @member {String} version='6.10.3'
24
24
  */
25
- version: '6.10.2'
25
+ version: '6.10.3'
26
26
  }
27
27
 
28
28
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "6.10.2",
3
+ "version": "6.10.3",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "homepage": "https://neomjs.github.io/pages/",
44
44
  "devDependencies": {
45
- "@fortawesome/fontawesome-free": "^6.5.0",
45
+ "@fortawesome/fontawesome-free": "^6.5.1",
46
46
  "autoprefixer": "^10.4.16",
47
47
  "chalk": "^5.3.0",
48
48
  "clean-webpack-plugin": "^4.0.0",
@@ -143,13 +143,13 @@
143
143
  "name": "Main Thread Addons"
144
144
  },
145
145
  {
146
- "id": "mainThreadAddonIntro",
146
+ "id": "MainThreadAddonIntro",
147
147
  "parentId": "MainThreadAddons",
148
148
  "isLeaf": true,
149
149
  "name": "Introduction"
150
150
  },
151
151
  {
152
- "id": "mainThreadAddonExample",
152
+ "id": "MainThreadAddonExample",
153
153
  "parentId": "MainThreadAddons",
154
154
  "isLeaf": true,
155
155
  "name": "Example"
@@ -214,4 +214,4 @@
214
214
  "isLeaf": true
215
215
  }
216
216
  ]
217
- }
217
+ }
@@ -25,3 +25,29 @@
25
25
  top : -10000px;
26
26
  z-index : 1000;
27
27
  }
28
+
29
+ .neo-masked {
30
+ position : relative;
31
+
32
+ .neo-load-mask {
33
+ position : absolute;
34
+ inset : 0;
35
+ background-color : inherit;
36
+ z-index : 4;
37
+ display : grid;
38
+ justify-content : center;
39
+ align-content : center;
40
+ overflow : clip;
41
+
42
+ .neo-load-mask-body {
43
+ display : flex;
44
+ flex-flow : row nowrap;
45
+ gap : 0.7em;
46
+
47
+ .fa-spinner {
48
+ width : 1em;
49
+ height : 1em;
50
+ }
51
+ }
52
+ }
53
+ }
@@ -236,12 +236,12 @@ const DefaultConfig = {
236
236
  useVdomWorker: true,
237
237
  /**
238
238
  * buildScripts/injectPackageVersion.mjs will update this value
239
- * @default '6.10.2'
239
+ * @default '6.10.3'
240
240
  * @memberOf! module:Neo
241
241
  * @name config.version
242
242
  * @type String
243
243
  */
244
- version: '6.10.2'
244
+ version: '6.10.3'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
@@ -199,6 +199,12 @@ class Base extends CoreBase {
199
199
  * @member {String|null} html_=null
200
200
  */
201
201
  html_: null,
202
+ /**
203
+ * Set to `true` to show a spinner centered in the component.
204
+ * Set to a string to show a message next to a spinner centered in the component.
205
+ * @member {Boolean|String} isLoading=false
206
+ */
207
+ isLoading_: false,
202
208
  /**
203
209
  * Internal flag which will get set to true while an update request (worker messages) is in progress
204
210
  * @member {Boolean} isVdomUpdating=false
@@ -653,6 +659,48 @@ class Base extends CoreBase {
653
659
  ComponentManager.register(this)
654
660
  }
655
661
 
662
+ /**
663
+ * Triggered after the isLoading config got changed
664
+ * @param {Boolean|String} value
665
+ * @param {Boolean|String} oldValue
666
+ * @protected
667
+ */
668
+ afterSetIsLoading(value, oldValue) {
669
+ if (!(value === false && oldValue === undefined)) {
670
+ let me = this,
671
+ { cls, vdom } = me,
672
+ maskIndex;
673
+
674
+ if (oldValue !== undefined && vdom.cn) {
675
+ maskIndex = vdom.cn.findIndex(c => c.cls.includes('neo-load-mask'));
676
+
677
+ // Remove the load mask
678
+ if (maskIndex !== -1) {
679
+ vdom.cn.splice(maskIndex, 1)
680
+ }
681
+ }
682
+
683
+ if (value) {
684
+ vdom.cn.push(me.loadMask = {
685
+ cls: ['neo-load-mask'],
686
+ cn : [{
687
+ cls: ['neo-load-mask-body'],
688
+ cn : [{
689
+ cls: ['fa', 'fa-spinner', 'fa-spin']
690
+ }, {
691
+ cls : ['neo-loading-message'],
692
+ html : value,
693
+ removeDom: !Neo.isString(value)
694
+ }]
695
+ }]
696
+ })
697
+ }
698
+
699
+ NeoArray.toggle(cls, 'neo-masked', value);
700
+ me.set({cls, vdom})
701
+ }
702
+ }
703
+
656
704
  /**
657
705
  * Triggered after the maxHeight config got changed
658
706
  * @param {Number|String|null} value
@@ -1231,8 +1279,12 @@ class Base extends CoreBase {
1231
1279
  * Triggers all stored resolve() callbacks
1232
1280
  */
1233
1281
  doResolveUpdateCache() {
1234
- this.resolveUpdateCache.forEach(item => item());
1235
- this.resolveUpdateCache = [];
1282
+ let me = this;
1283
+
1284
+ if (me.resolveUpdateCache) {
1285
+ me.resolveUpdateCache.forEach(item => item());
1286
+ me.resolveUpdateCache = []
1287
+ }
1236
1288
  }
1237
1289
 
1238
1290
  /**
@@ -1959,10 +2011,11 @@ class Base extends CoreBase {
1959
2011
  resolve?.();
1960
2012
 
1961
2013
  if (me.needsVdomUpdate) {
1962
- me.childUpdateCache = []; // if a new update is scheduled, we can clear the cache => these updates are included
2014
+ // if a new update is scheduled, we can clear the cache => these updates are included
2015
+ me.childUpdateCache = [];
1963
2016
 
1964
2017
  me.update()
1965
- } else {
2018
+ } else if (me.childUpdateCache) {
1966
2019
  [...me.childUpdateCache].forEach(id => {
1967
2020
  Neo.getComponent(id)?.update();
1968
2021
  NeoArray.remove(me.childUpdateCache, id)
@@ -2133,16 +2186,7 @@ class Base extends CoreBase {
2133
2186
  me._hidden = true; // silent update
2134
2187
  me.mounted = false;
2135
2188
 
2136
- Neo.currentWorker.promiseMessage('main', {
2137
- action : 'updateDom',
2138
- appName: me.appName,
2139
- deltas : [{
2140
- action: 'removeNode',
2141
- id : me.vdom.id
2142
- }]
2143
- }).catch(err => {
2144
- console.log('Error attempting to unmount component', err, me)
2145
- })
2189
+ Neo.applyDeltas(me.appName, {action: 'removeNode', id: me.vdom.id})
2146
2190
  }
2147
2191
 
2148
2192
  /**
@@ -117,18 +117,8 @@ class NeoArray extends Base {
117
117
  * @param {*} item
118
118
  * @param {Boolean} [add]
119
119
  */
120
- static toggle(arr, item, add) {
121
- let operation;
122
-
123
- if (add === true) {
124
- operation = 'add';
125
- } else if (add === false) {
126
- operation = 'remove';
127
- } else {
128
- operation = this.hasItem(arr, item) ? 'remove' : 'add';
129
- }
130
-
131
- this[operation](arr, item);
120
+ static toggle(arr, item, add = !this.hasItem(arr, item)) {
121
+ this[add ? 'add' : 'remove'](arr, item);
132
122
  }
133
123
 
134
124
  /**
@@ -107,16 +107,22 @@ class App extends Base {
107
107
  * }).then(id => console.log(id))
108
108
  *
109
109
  * @param {Object} config
110
+ * @param {String} [config.importPath] you can lazy load missing classes via this config. dev mode only.
110
111
  * @param {String} [config.parentId] passing a parentId will put your instance into a container
111
112
  * @param {Number} [config.parentIndex] if a parentId is passed, but no index, neo will use add()
112
113
  * @returns {String} the instance id
113
114
  */
114
- createNeoInstance(config) {
115
+ async createNeoInstance(config) {
116
+ if (config.importPath) {
117
+ await import(/* webpackIgnore: true */ config.importPath);
118
+ delete config.importPath
119
+ }
120
+
115
121
  let appName = Object.keys(Neo.apps)[0], // fallback in case no appName was provided
116
122
  Container = Neo.container?.Base,
117
123
  index, instance, parent;
118
124
 
119
- config = {appName: appName, ...config};
125
+ config = {appName, ...config};
120
126
 
121
127
  if (config.parentId) {
122
128
  parent = Neo.getComponent(config.parentId);
@@ -1,17 +1,44 @@
1
1
  StartTest(t => {
2
- t.it('Checking if neo.mjs got started', async t => {
3
- if (!globalThis.Neo?.Main) {
4
- console.log('Starting the neo.mjs workers setup');
5
-
6
- await import('../../../../src/MicroLoader.mjs');
7
- }
8
-
9
- setTimeout(() => {
10
- Neo.worker.App.createNeoInstance({
11
- ntype : 'button',
12
- iconCls: 'fa fa-home',
13
- text : 'Hello Siesta'
14
- })
15
- }, 300)
2
+ let button;
3
+
4
+ t.beforeEach(async t => {
5
+ button && await Neo.worker.App.destroyNeoInstance(button);
6
+ });
7
+
8
+ t.it('Sanity', async t => {
9
+ button = await Neo.worker.App.createNeoInstance({
10
+ ntype : 'button',
11
+ iconCls: 'fa fa-home',
12
+ text : 'Hello Siesta'
13
+ });
14
+ });
15
+
16
+ t.it('Should show isLoading UI', async t => {
17
+ button = await Neo.worker.App.createNeoInstance({
18
+ ntype : 'button',
19
+ iconCls : 'fa fa-home',
20
+ text : 'Hello Siesta',
21
+ isLoading : 'Loading...'
22
+ });
23
+
24
+ // Spinner and text exist
25
+ await t.waitForSelector('button .fa-spinner.fa-spin');
26
+ t.selectorExists('button .neo-loading-message:contains(Loading...)');
27
+
28
+ await Neo.worker.App.setConfigs({ id: button, isLoading : true });
29
+
30
+ // Just a spinner now, no text
31
+ await t.waitForSelectorNotFound('button .neo-loading-message:contains(Loading...)');
32
+ t.selectorExists('button .fa-spinner');
33
+
34
+ await Neo.worker.App.setConfigs({ id: button, isLoading : 'New loading message' });
35
+
36
+ await t.waitForSelector('button .neo-loading-message:contains(New loading message)');
37
+ t.selectorExists('button .fa-spinner.fa-spin');
38
+
39
+ await Neo.worker.App.setConfigs({ id: button, isLoading : false });
40
+
41
+ // Not loading now
42
+ await t.waitForSelectorNotFound('button .fa-spinner');
16
43
  });
17
44
  });
@@ -1,35 +1,27 @@
1
1
  StartTest(t => {
2
- t.it('Checking if neo.mjs got started', async t => {
3
- if (!globalThis.Neo?.Main) {
4
- console.log('Starting the neo.mjs workers setup');
2
+ t.it('Sanity', async t => {
3
+ Neo.worker.App.createNeoInstance({
4
+ ntype : 'selectfield',
5
+ labelPosition: 'inline',
6
+ labelText : 'US States',
7
+ labelWidth : 80,
8
+ width : 300,
5
9
 
6
- await import('../../../../../src/MicroLoader.mjs');
7
- }
10
+ store : {
11
+ autoLoad : true,
12
+ keyProperty: 'abbreviation',
13
+ url : '../../resources/examples/data/us_states.json',
8
14
 
9
- setTimeout(() => {
10
- Neo.worker.App.createNeoInstance({
11
- ntype : 'selectfield',
12
- labelPosition: 'inline',
13
- labelText : 'US States',
14
- labelWidth : 80,
15
- width : 300,
16
-
17
- store : {
18
- autoLoad : true,
19
- keyProperty: 'abbreviation',
20
- url : '../../resources/examples/data/us_states.json',
21
-
22
- model: {
23
- fields: [{
24
- name: 'abbreviation',
25
- type: 'string'
26
- }, {
27
- name: 'name',
28
- type: 'string'
29
- }]
30
- }
15
+ model: {
16
+ fields: [{
17
+ name: 'abbreviation',
18
+ type: 'string'
19
+ }, {
20
+ name: 'name',
21
+ type: 'string'
22
+ }]
31
23
  }
32
- })
33
- }, 1000)
24
+ }
25
+ });
34
26
  });
35
27
  });
@@ -1,8 +1,38 @@
1
1
  const project = new Siesta.Project.Browser();
2
2
 
3
3
  project.configure({
4
- title : 'Neo Component Tests',
5
- isEcmaModule: true
4
+ title : 'Neo Component Tests',
5
+ isEcmaModule : true,
6
+ preload : [{
7
+ type : 'js',
8
+ url : '../../src/MicroLoader.mjs',
9
+ isEcmaModule : true
10
+ }],
11
+ testClass : Class('My.Test.Class', {
12
+ isa : Siesta.Test.Browser,
13
+ override : {
14
+ setup(callback, errback) {
15
+ this.SUPER(function() {
16
+ // We need to call the startup callback only when we know we are
17
+ // ready to start testing.
18
+ const
19
+ { global } = this,
20
+ startupTimer = setInterval(() => {
21
+ if (global.Neo?.worker?.App && global.Neo.worker.Manager && global.Neo.Main) {
22
+ clearInterval(startupTimer);
23
+
24
+ // TODO: Find what we actually need to wait for
25
+ setTimeout(callback, 300);
26
+ }
27
+ }, 100);
28
+ }, errback);
29
+ },
30
+ async beforeEach() {
31
+ this.SUPER(...arguments);
32
+ this.SUPER(t => t.waitFor(50));
33
+ }
34
+ }
35
+ })
6
36
  });
7
37
 
8
38
  project.plan(