neo.mjs 6.10.2 → 6.10.4

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.
Files changed (30) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/apps/learnneo/view/LivePreview.mjs +28 -15
  3. package/apps/learnneo/view/home/HeaderToolbar.mjs +16 -6
  4. package/examples/ServiceWorker.mjs +2 -2
  5. package/package.json +2 -2
  6. package/resources/data/deck/learnneo/p/stylesheet.md +4 -1
  7. package/resources/data/deck/learnneo/t.json +3 -3
  8. package/resources/design-tokens/json/component.json +22 -0
  9. package/resources/design-tokens/json/core.json +4 -0
  10. package/resources/design-tokens/json/semantic.json +8 -0
  11. package/resources/scss/src/component/Base.scss +26 -0
  12. package/resources/scss/src/tooltip/Base.scss +10 -6
  13. package/resources/scss/theme-dark/tooltip/Base.scss +10 -0
  14. package/resources/scss/theme-light/tooltip/Base.scss +10 -0
  15. package/resources/scss/theme-neo-light/button/Base.scss +23 -19
  16. package/resources/scss/theme-neo-light/design-tokens/Component.scss +6 -0
  17. package/resources/scss/theme-neo-light/design-tokens/Core.scss +2 -0
  18. package/resources/scss/theme-neo-light/design-tokens/Semantic.scss +4 -2
  19. package/resources/scss/theme-neo-light/tooltip/Base.scss +13 -0
  20. package/src/DefaultConfig.mjs +2 -2
  21. package/src/Main.mjs +1 -1
  22. package/src/component/Base.mjs +58 -14
  23. package/src/core/Observable.mjs +15 -15
  24. package/src/main/addon/WindowPosition.mjs +56 -28
  25. package/src/util/Array.mjs +2 -12
  26. package/src/worker/App.mjs +34 -3
  27. package/src/worker/Canvas.mjs +4 -4
  28. package/test/components/files/button/Base.mjs +41 -14
  29. package/test/components/files/form/field/Select.mjs +21 -29
  30. package/test/components/siesta.js +32 -2
@@ -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.4'
24
24
  */
25
- version: '6.10.2'
25
+ version: '6.10.4'
26
26
  }
27
27
 
28
28
  /**
@@ -1,6 +1,6 @@
1
- import Base from '../../../src/container/Base.mjs';
1
+ import Base from '../../../src/container/Base.mjs';
2
2
  import TabContainer from '../../../src/tab/Container.mjs';
3
- import TextArea from '../../../src/form/field/TextArea.mjs';
3
+ import TextArea from '../../../src/form/field/TextArea.mjs';
4
4
 
5
5
  /**
6
6
  * @class LearnNeo.view.LivePreview
@@ -22,7 +22,9 @@ class LivePreview extends Base {
22
22
  * @member {Object[]} items
23
23
  */
24
24
  items: [{
25
- module: TabContainer,
25
+ module : TabContainer,
26
+ reference: 'tab-container',
27
+
26
28
  items: [{
27
29
  module: TextArea,
28
30
  hideLabel: true,
@@ -38,15 +40,7 @@ class LivePreview extends Base {
38
40
  reference: 'preview',
39
41
  ntype: 'container'
40
42
  }]
41
- }],
42
- listeners: {
43
- activeIndexChange: () => console.log('activeIndexChange')
44
- }
45
- }
46
- onConstructed() {
47
- console.log('constructed');
48
- this.on('activeIndexChange', () => console.log('onConstructed'));
49
- super.onConstructed();
43
+ }]
50
44
  }
51
45
 
52
46
  afterSetValue(value, oldValue) {
@@ -55,6 +49,14 @@ class LivePreview extends Base {
55
49
  }
56
50
  }
57
51
 
52
+ onConstructed() {
53
+ super.onConstructed();
54
+
55
+ let me = this;
56
+
57
+ me.getReference('tab-container').on('activeIndexChange', me.onActiveIndexChange, me)
58
+ }
59
+
58
60
  doIt(button) {
59
61
  let source = this.getReference('source').getValue();
60
62
 
@@ -92,7 +94,7 @@ class LivePreview extends Base {
92
94
  });
93
95
  var params = [];
94
96
  var vars = [];
95
- // Figure out the parts of the source we'll be running.
97
+ // Figure out the parts of the source we'll be running.
96
98
  // o The promises/import() corresponding to the user's import statements
97
99
  // o The vars holding the name of the imported module based on the module name for each import
98
100
  // o The rest of the user-provided source
@@ -105,8 +107,8 @@ class LivePreview extends Base {
105
107
  // const Button = ButtonModule.default;
106
108
  // // Class declaration goes here...
107
109
  // });
108
- // Making the promise part of the eval seems weird, but it made it easier to
109
- // set up the import vars.
110
+ // Making the promise part of the eval seems weird, but it made it easier to
111
+ // set up the import vars.
110
112
 
111
113
  let promises = moduleNameAndPath.map(item => {
112
114
  params.push(`${item.moduleName}Module`);
@@ -164,6 +166,17 @@ class LivePreview extends Base {
164
166
  }
165
167
  return null
166
168
  }
169
+
170
+ /**
171
+ * @param {Object} data
172
+ * @param {Neo.component.Base} data.item
173
+ * @param {Number} data.oldValue
174
+ * @param {String} data.source
175
+ * @param {Number} data.value
176
+ */
177
+ onActiveIndexChange(data) {
178
+ console.log('onActiveIndexChange', data)
179
+ }
167
180
  }
168
181
 
169
182
  Neo.applyClassConfig(LivePreview);
@@ -25,19 +25,29 @@ class HeaderToolbar extends Base {
25
25
  text : 'neo.mjs'
26
26
  }, '->', {
27
27
  text: 'Docs',
28
- ui : 'tertiary'
28
+ ui : 'ghost'
29
29
  }, {
30
30
  text: 'Learn',
31
- ui : 'tertiary'
31
+ ui : 'ghost'
32
32
  }, {
33
33
  cls : ['github-button'],
34
34
  iconCls: 'fa-brands fa-github',
35
- ui : 'tertiary',
36
- url : 'https://github.com/neomjs/neo'
35
+ ui : 'ghost',
36
+ url : 'https://github.com/neomjs/neo',
37
+ tooltip: {
38
+ html: 'GitHub',
39
+ showDelay: '0',
40
+ hideDelay: '0'
41
+ }
37
42
  }, {
38
43
  iconCls: 'fa-brands fa-slack',
39
- ui : 'tertiary',
40
- url : 'https://join.slack.com/t/neomjs/shared_invite/zt-6c50ueeu-3E1~M4T9xkNnb~M_prEEOA'
44
+ ui : 'ghost',
45
+ url : 'https://join.slack.com/t/neomjs/shared_invite/zt-6c50ueeu-3E1~M4T9xkNnb~M_prEEOA',
46
+ tooltip: {
47
+ html: 'Join Slack',
48
+ showDelay: '0',
49
+ hideDelay: '0'
50
+ }
41
51
  }]
42
52
  }
43
53
  }
@@ -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.4'
24
24
  */
25
- version: '6.10.2'
25
+ version: '6.10.4'
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.4",
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",
@@ -18,7 +18,6 @@ and information is lost.
18
18
  <br>
19
19
 
20
20
  To show highlighted Neo.mjs source code use
21
- <pre>
22
21
  &lt;pre data-javascript>
23
22
  // Source code goes here
24
23
  &lt;/pre>
@@ -52,3 +51,7 @@ This is the item contents.
52
51
  This is the item contents.
53
52
  </details>
54
53
 
54
+ <pre data-neo>
55
+ let a = 1;
56
+ </pre>
57
+
@@ -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
+ }
@@ -319,6 +319,28 @@
319
319
  }
320
320
  }
321
321
  }
322
+ },
323
+ "tooltip": {
324
+ "height": {
325
+ "value": "{height.24}",
326
+ "type": "sizing"
327
+ },
328
+ "spacinghorizontal": {
329
+ "value": "{sem.spacing.xsmall}",
330
+ "type": "spacing"
331
+ },
332
+ "fg": {
333
+ "value": "{sem.color.fg.neutral.inverted}",
334
+ "type": "color"
335
+ },
336
+ "bg": {
337
+ "value": "{sem.color.bg.neutral.contrast}",
338
+ "type": "color"
339
+ },
340
+ "borderradius": {
341
+ "value": "{sem.borderradius.medium}",
342
+ "type": "borderRadius"
343
+ }
322
344
  }
323
345
  }
324
346
  }
@@ -257,6 +257,10 @@
257
257
  "value": "16px",
258
258
  "type": "sizing"
259
259
  },
260
+ "24": {
261
+ "value": "24px",
262
+ "type": "sizing"
263
+ },
260
264
  "32": {
261
265
  "value": "32px",
262
266
  "type": "sizing"
@@ -40,6 +40,10 @@
40
40
  "disabled": {
41
41
  "value": "{gray.200}",
42
42
  "type": "color"
43
+ },
44
+ "contrast": {
45
+ "value": "{gray.900}",
46
+ "type": "color"
43
47
  }
44
48
  },
45
49
  "primary": {
@@ -82,6 +86,10 @@
82
86
  "disabled": {
83
87
  "value": "{gray.300}",
84
88
  "type": "color"
89
+ },
90
+ "inverted": {
91
+ "value": "{white}",
92
+ "type": "color"
85
93
  }
86
94
  },
87
95
  "primary": {
@@ -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
+ }
@@ -1,7 +1,11 @@
1
1
  .neo-tooltip {
2
- border : 1px solid #0d86e7;
3
- box-shadow: 3px 3px 4px rgba(0,0,0, .6);
4
- font-size : 13px;
5
- padding : 5px 10px;
6
- position : absolute;
7
- }
2
+ background-color: var(--tooltip-bg);
3
+ border : var(--tooltip-border);
4
+ border-radius : var(--tooltip-borderradius);
5
+ box-shadow : var(--tooltip-boxshadow);
6
+ color : var(--tooltip-color);
7
+ font-size : var(--tooltip-fontsize);
8
+ height : var(--tooltip-height);
9
+ padding : var(--tooltip-padding);
10
+ position : absolute;
11
+ }
@@ -0,0 +1,10 @@
1
+ :root .neo-theme-neo-light { // .neo-tooltip
2
+ --tooltip-bg : black;
3
+ --tooltip-border : grey;
4
+ --tooltip-borderradius : 4px;
5
+ --tooltip-boxshadow : none;
6
+ --tooltip-color : white;
7
+ --tooltip-fontsize : 14px;
8
+ --tooltip-height : 24px;
9
+ --tooltip-padding : 2px 8px 0;
10
+ }
@@ -0,0 +1,10 @@
1
+ :root .neo-theme-neo-light { // .neo-tooltip
2
+ --tooltip-bg : white;
3
+ --tooltip-border : lightgrey;
4
+ --tooltip-borderradius : 4px;
5
+ --tooltip-boxshadow : none;
6
+ --tooltip-color : black;
7
+ --tooltip-fontsize : 14px;
8
+ --tooltip-height : 24px;
9
+ --tooltip-padding : 2px 8px 0;
10
+ }
@@ -45,28 +45,28 @@ $border-width : 1px;
45
45
  --button-use-gradients : false;
46
46
 
47
47
  // {module: Button; ui: --ghost}
48
- --button-ghost-background-color : var(--button-text-color);
49
- --button-ghost-background-color-active : #{$border-color};
50
- --button-ghost-background-color-disabled : inherit;
51
- --button-ghost-background-color-hover : var(--button-text-color);
48
+ --button-ghost-background-color : var(--cmp-button-ghost-bg-default);
49
+ --button-ghost-background-color-active : var(--cmp-button-ghost-bg-active);
50
+ --button-ghost-background-color-disabled : var(--cmp-button-ghost-bg-disabled);
51
+ --button-ghost-background-color-hover : var(--cmp-button-ghost-bg-hover);
52
52
  --button-ghost-background-image : none;
53
- --button-ghost-badge-background-color : var(--button-badge-color);
54
- --button-ghost-badge-color : var(--button-badge-background-color);
55
- --button-ghost-border : #{$border-width} #{$border-style} var(--button-background-color);
56
- --button-ghost-border-active : #{$border-width} #{$border-style} #{$border-color};
53
+ --button-ghost-badge-background-color : transparent;
54
+ --button-ghost-badge-color : transparent;
55
+ --button-ghost-border : none;
56
+ --button-ghost-border-active : none;
57
57
  --button-ghost-border-disabled : inherit;
58
- --button-ghost-border-hover : #{$border-width} #{$border-style} #{$text-color};
59
- --button-ghost-border-pressed : #{$border-width} #{$border-style} #{$text-color};
60
- --button-ghost-glyph-color : var(--button-background-color);
61
- --button-ghost-glyph-color-active : var(--button-background-color);
62
- --button-ghost-glyph-color-disabled : inherit;
63
- --button-ghost-glyph-color-hover : var(--button-background-color);
64
- --button-ghost-opacity-disabled : var(--neo-disabled-opacity);
58
+ --button-ghost-border-hover : none;
59
+ --button-ghost-border-pressed : none;
60
+ --button-ghost-glyph-color : var(--cmp-button-ghost-fg-default);
61
+ --button-ghost-glyph-color-active : inherit;
62
+ --button-ghost-glyph-color-disabled : var(--cmp-button-ghost-fg-disabled);
63
+ --button-ghost-glyph-color-hover : inherit;
64
+ --button-ghost-opacity-disabled : inherit;
65
65
  --button-ghost-ripple-background-color : inherit;
66
- --button-ghost-text-color : var(--button-background-color);
67
- --button-ghost-text-color-active : var(--button-background-color);
68
- --button-ghost-text-color-disabled : inherit;
69
- --button-ghost-text-color-hover : var(--button-background-color);
66
+ --button-ghost-text-color : var(--cmp-button-ghost-fg-default);
67
+ --button-ghost-text-color-active : inherit;
68
+ --button-ghost-text-color-disabled : var(--cmp-button-ghost-fg-disabled);
69
+ --button-ghost-text-color-hover : inherit;
70
70
 
71
71
  // {module: Button; ui: --secondary}
72
72
  --button-secondary-background-color : var(--cmp-button-secondary-bg-default);
@@ -116,3 +116,7 @@ $border-width : 1px;
116
116
  --button-tertiary-text-color-disabled : inherit;
117
117
  --button-tertiary-text-color-hover : var(--button-text-color);
118
118
  }
119
+
120
+ .neo-button {
121
+ min-width: var(--cmp-button-height);
122
+ }
@@ -70,4 +70,10 @@
70
70
  --cmp-tab-unselected-bg-active : var(--sem-color-bg-neutral-active);
71
71
  --cmp-tab-unselected-bg-default : transparent;
72
72
  --cmp-tab-unselected-bg-hover : var(--sem-color-bg-neutral-hover);
73
+
74
+ --cmp-tooltip-bg : var(--sem-color-bg-neutral-contrast);
75
+ --cmp-tooltip-borderradius : var(--sem-borderradius-medium);
76
+ --cmp-tooltip-fg : var(--sem-color-fg-neutral-inverted);
77
+ --cmp-tooltip-height : var(--height-24);
78
+ --cmp-tooltip-spacinghorizontal : var(--sem-spacing-xsmall);
73
79
  }
@@ -6,6 +6,7 @@
6
6
  --core-fontfamily-mono : 'Source Code Pro';
7
7
  --core-fontfamily-sans : 'Source Sans 3';
8
8
  --core-fontfamily-serif : 'Source Serif Pro';
9
+ --core-fontsize-label : 1rem;
9
10
  --core-fontsize-body : 16px;
10
11
  --core-fontsize-h1 : 2.5rem;
11
12
  --core-fontsize-h2 : 1.75rem;
@@ -42,6 +43,7 @@
42
43
  --height-48 : 48px;
43
44
  --height-56 : 56px;
44
45
  --height-8 : 8px;
46
+ --height-24 : 24px;
45
47
  --purple-100 : #efe3ff;
46
48
  --purple-200 : #d0aaff;
47
49
  --purple-300 : #b071ff;
@@ -3,11 +3,11 @@
3
3
  --sem-borderradius-medium : var(--borderradius-4);
4
4
  --sem-borderradius-none : var(--borderradius-0);
5
5
  --sem-borderwidth-focus : 3px;
6
- --sem-color-bg-neutral-active : var(--gray-300);
6
+ --sem-color-bg-neutral-active : rgba(0, 0, 0, .1);
7
7
  --sem-color-bg-neutral-default : var(--gray-50);
8
8
  --sem-color-bg-neutral-disabled : var(--gray-200);
9
9
  --sem-color-bg-neutral-highlighted : var(--white);
10
- --sem-color-bg-neutral-hover : var(--gray-200);
10
+ --sem-color-bg-neutral-hover :rgba(0, 0, 0, .05);
11
11
  --sem-color-bg-primary-active : var(--green-600);
12
12
  --sem-color-bg-primary-background : var(--green-50);
13
13
  --sem-color-bg-primary-default : var(--green-400);
@@ -64,4 +64,6 @@
64
64
  --sem-typo-label-regular-fontFamily : var(--core-fontfamily-sans);
65
65
  --sem-typo-label-regular-fontSize : var(--core-fontsize-body);
66
66
  --sem-typo-label-regular-fontWeight : var(--core-fontweight-regular);
67
+ --sem-color-bg-neutral-contrast : var(--gray-900);
68
+ --sem-color-fg-neutral-inverted : var(--white);
67
69
  }
@@ -0,0 +1,13 @@
1
+ :root .neo-theme-neo-light { // .neo-tooltip
2
+ --tooltip-bg : var(--cmp-tooltip-bg);
3
+ --tooltip-border : none;
4
+ --tooltip-borderradius : var(--cmp-tooltip-borderradius);
5
+ --tooltip-boxshadow : none;
6
+ --tooltip-color : var(--cmp-tooltip-fg);
7
+ --tooltip-fontsize : 14px;
8
+ --tooltip-height : var(--cmp-tooltip-height);
9
+ --tooltip-padding : 2px var(--cmp-tooltip-spacinghorizontal) 0;
10
+ }
11
+
12
+
13
+ // 3px 3px 4px rgba(0,0,0, .6);
@@ -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.4'
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.4'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
package/src/Main.mjs CHANGED
@@ -454,7 +454,7 @@ class Main extends core.Base {
454
454
  * @param {String} data.y
455
455
  */
456
456
  windowMoveTo(data) {
457
- this.openWindows[data.windowName].moveTo(data.x, data.y);
457
+ this.openWindows[data.windowName]?.moveTo(data.x, data.y);
458
458
  }
459
459
 
460
460
  /**
@@ -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
  /**
@@ -91,6 +91,21 @@ class Observable extends Base {
91
91
  return null;
92
92
  }
93
93
 
94
+ /**
95
+ * Call the passed function, or a function by *name* which exists in the passed scope's
96
+ * or this component's ownership chain.
97
+ * @param {Function|String} fn A function, or the name of a function to find in the passed scope object/
98
+ * @param {Object} scope The scope to find the function in if it is specified as a string.
99
+ * @param {Array} args Arguments to pass to the callback.
100
+ */
101
+ callback(fn, scope=this, args) {
102
+ if (fn) {
103
+ const handler = this.resolveCallback(fn, scope);
104
+
105
+ handler.fn.apply(handler.scope, args);
106
+ }
107
+ }
108
+
94
109
  /**
95
110
  * @param name
96
111
  */
@@ -127,21 +142,6 @@ class Observable extends Base {
127
142
  }
128
143
  }
129
144
 
130
- /**
131
- * Call the passed function, or a function by *name* which exists in the passed scope's
132
- * or this component's ownership chain.
133
- * @param {Function|String} fn A function, or the name of a function to find in the passed scope object/
134
- * @param {Object} scope The scope to find the function in if it is specified as a string.
135
- * @param {Array} args Arguments to pass to the callback.
136
- */
137
- callback(fn, scope=this, args) {
138
- if (fn) {
139
- const handler = this.resolveCallback(fn, scope);
140
-
141
- handler.fn.apply(handler.scope, args);
142
- }
143
- }
144
-
145
145
  /**
146
146
  * @param {Object} config
147
147
  */
@@ -1,7 +1,6 @@
1
1
  import Base from '../../core/Base.mjs';
2
2
 
3
3
  /**
4
- *
5
4
  * @class Neo.main.addon.WindowPosition
6
5
  * @extends Neo.core.Base
7
6
  * @singleton
@@ -13,14 +12,22 @@ class WindowPosition extends Base {
13
12
  * @protected
14
13
  */
15
14
  className: 'Neo.main.addon.WindowPosition',
15
+ /**
16
+ * @member {Boolean} adjustWindowPositions=true
17
+ */
18
+ adjustWindowPositions: true,
16
19
  /**
17
20
  * @member {String|null} intervalId=null
18
21
  */
19
22
  intervalId: null,
20
23
  /**
21
- * @member {Number} intervalTime=100
24
+ * @member {Number} intervalTime=20
22
25
  */
23
26
  intervalTime: 20,
27
+ /**
28
+ * @member {Boolean} observeResize_=true
29
+ */
30
+ observeResize_: true,
24
31
  /**
25
32
  * Remote method access for other workers
26
33
  * @member {Object} remote
@@ -29,6 +36,7 @@ class WindowPosition extends Base {
29
36
  remote: {
30
37
  app: [
31
38
  'registerWindow',
39
+ 'setConfigs',
32
40
  'setDock',
33
41
  'unregisterWindow'
34
42
  ]
@@ -65,8 +73,21 @@ class WindowPosition extends Base {
65
73
  me.screenLeft = win.screenLeft;
66
74
  me.screenTop = win.screenTop;
67
75
 
68
- win.addEventListener('mouseout', me.onMouseOut.bind(me));
69
- win.addEventListener('resize', me.onResize.bind(me));
76
+ win.addEventListener('mouseout', me.onMouseOut.bind(me))
77
+ }
78
+
79
+ /**
80
+ * Triggered after the observeResize config got changed
81
+ * @param {Boolean} value
82
+ * @param {Boolean} oldValue
83
+ * @protected
84
+ */
85
+ afterSetObserveResize(value, oldValue) {
86
+ let me = this;
87
+
88
+ if (value) {
89
+ window[value ? 'addEventListener' : 'removeEventListener']('resize', me.onResize.bind(me))
90
+ }
70
91
  }
71
92
 
72
93
  /**
@@ -82,8 +103,8 @@ class WindowPosition extends Base {
82
103
  windowName: key,
83
104
  x : position.left,
84
105
  y : position.top
85
- });
86
- });
106
+ })
107
+ })
87
108
  }
88
109
 
89
110
  /**
@@ -100,7 +121,7 @@ class WindowPosition extends Base {
100
121
  if (me.screenLeft !== screenLeft || me.screenTop !== screenTop) {
101
122
  winData = Neo.Main.getWindowData();
102
123
 
103
- me.adjustPositions();
124
+ me.adjustWindowPositions && me.adjustPositions();
104
125
 
105
126
  Manager.sendMessage('app', {
106
127
  action: 'windowPositionChange',
@@ -111,7 +132,7 @@ class WindowPosition extends Base {
111
132
  });
112
133
 
113
134
  me.screenLeft = screenLeft;
114
- me.screenTop = screenTop;
135
+ me.screenTop = screenTop
115
136
  }
116
137
  }
117
138
 
@@ -124,7 +145,7 @@ class WindowPosition extends Base {
124
145
  */
125
146
  dockDirectionChange(oldValue, newValue) {
126
147
  return (oldValue === 'bottom' || oldValue === 'top') && (newValue === 'left' || newValue === 'right')
127
- || (newValue === 'bottom' || newValue === 'top') && (oldValue === 'left' || oldValue === 'right');
148
+ || (newValue === 'bottom' || newValue === 'top') && (oldValue === 'left' || oldValue === 'right')
128
149
  }
129
150
 
130
151
  /**
@@ -134,29 +155,26 @@ class WindowPosition extends Base {
134
155
  let win = window,
135
156
  left, top;
136
157
 
137
- switch (data.dock) {
158
+ switch(data.dock) {
138
159
  case 'bottom':
139
160
  left = win.screenLeft;
140
161
  top = win.outerHeight + win.screenTop - 50;
141
- break;
162
+ break
142
163
  case 'left':
143
164
  left = win.screenLeft - data.size;
144
165
  top = win.screenTop + 28;
145
- break;
166
+ break
146
167
  case 'right':
147
168
  left = win.outerWidth + win.screenLeft;
148
169
  top = win.screenTop + 28;
149
- break;
170
+ break
150
171
  case 'top':
151
172
  left = win.screenLeft;
152
173
  top = win.screenTop - data.size + 78;
153
- break;
174
+ break
154
175
  }
155
176
 
156
- return {
157
- left: left,
158
- top : top
159
- };
177
+ return {left, top}
160
178
  }
161
179
 
162
180
  /**
@@ -167,11 +185,11 @@ class WindowPosition extends Base {
167
185
 
168
186
  if (!event.toElement) {
169
187
  if (!me.intervalId) {
170
- me.intervalId = setInterval(me.checkMovement.bind(me), me.intervalTime);
188
+ me.intervalId = setInterval(me.checkMovement.bind(me), me.intervalTime)
171
189
  }
172
190
  } else if (me.intervalId) {
173
191
  clearInterval(me.intervalId);
174
- me.intervalId = null;
192
+ me.intervalId = null
175
193
  }
176
194
  }
177
195
 
@@ -188,21 +206,21 @@ class WindowPosition extends Base {
188
206
  case 'bottom':
189
207
  case 'top':
190
208
  width = win.outerWidth;
191
- break;
209
+ break
192
210
  case 'left':
193
211
  case 'right':
194
212
  height = win.outerHeight - 28;
195
- break;
213
+ break
196
214
  }
197
215
 
198
216
  Neo.Main.windowResizeTo({
199
217
  height : height,
200
218
  width : width,
201
219
  windowName: key
202
- });
220
+ })
203
221
  });
204
222
 
205
- me.adjustPositions();
223
+ me.adjustWindowPositions && me.adjustPositions()
206
224
  }
207
225
 
208
226
  /**
@@ -212,7 +230,17 @@ class WindowPosition extends Base {
212
230
  * @param {Number} data.size
213
231
  */
214
232
  registerWindow(data) {
215
- this.windows[data.name] = data;
233
+ this.windows[data.name] = data
234
+ }
235
+
236
+ /**
237
+ * Set configs from within the app worker
238
+ * @param {Object} data
239
+ * @param {String} data.appName
240
+ */
241
+ setConfigs(data) {
242
+ delete data.appName;
243
+ this.set(data)
216
244
  }
217
245
 
218
246
  /**
@@ -239,14 +267,14 @@ class WindowPosition extends Base {
239
267
  height : dock === 'bottom' || dock === 'top' ? win.size : window.outerHeight - 28,
240
268
  width : dock === 'left' || dock === 'right' ? win.size : window.outerWidth,
241
269
  windowName: name
242
- });
270
+ })
243
271
  }
244
272
 
245
273
  Neo.Main.windowMoveTo({
246
274
  windowName: name,
247
275
  x : position.left,
248
276
  y : position.top
249
- });
277
+ })
250
278
  }
251
279
  }
252
280
 
@@ -255,7 +283,7 @@ class WindowPosition extends Base {
255
283
  * @param {String} data.name
256
284
  */
257
285
  unregisterWindow(data) {
258
- delete this.windows[data.name];
286
+ delete this.windows[data.name]
259
287
  }
260
288
  }
261
289
 
@@ -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
  /**
@@ -29,6 +29,7 @@ class App extends Base {
29
29
  main: [
30
30
  'createNeoInstance',
31
31
  'destroyNeoInstance',
32
+ 'fireEvent',
32
33
  'setConfigs'
33
34
  ]
34
35
  },
@@ -107,16 +108,22 @@ class App extends Base {
107
108
  * }).then(id => console.log(id))
108
109
  *
109
110
  * @param {Object} config
111
+ * @param {String} [config.importPath] you can lazy load missing classes via this config. dev mode only.
110
112
  * @param {String} [config.parentId] passing a parentId will put your instance into a container
111
113
  * @param {Number} [config.parentIndex] if a parentId is passed, but no index, neo will use add()
112
114
  * @returns {String} the instance id
113
115
  */
114
- createNeoInstance(config) {
116
+ async createNeoInstance(config) {
117
+ if (config.importPath) {
118
+ await import(/* webpackIgnore: true */ config.importPath);
119
+ delete config.importPath
120
+ }
121
+
115
122
  let appName = Object.keys(Neo.apps)[0], // fallback in case no appName was provided
116
123
  Container = Neo.container?.Base,
117
124
  index, instance, parent;
118
125
 
119
- config = {appName: appName, ...config};
126
+ config = {appName, ...config};
120
127
 
121
128
  if (config.parentId) {
122
129
  parent = Neo.getComponent(config.parentId);
@@ -183,6 +190,30 @@ class App extends Base {
183
190
  return false
184
191
  }
185
192
 
193
+ /**
194
+ * Fires a custom event based on core.Observable on any app realm based Neo instance from main
195
+ * @param {Object} data
196
+ * @param {String} data.id
197
+ * @param {String} data.name
198
+ */
199
+ fireEvent(data) {
200
+ let instance = Neo.get(data.id),
201
+ name;
202
+
203
+ if (instance) {
204
+ name = data.name;
205
+
206
+ delete data.id;
207
+ delete data.name;
208
+
209
+ instance.fire(name, data);
210
+
211
+ return true
212
+ }
213
+
214
+ return false
215
+ }
216
+
186
217
  /**
187
218
  * Only needed for the SharedWorkers context
188
219
  * @param {String} eventName
@@ -190,7 +221,7 @@ class App extends Base {
190
221
  */
191
222
  fireMainViewsEvent(eventName, data) {
192
223
  this.ports.forEach(port => {
193
- Neo.apps[port.appName].mainViewInstance.fire(eventName, data)
224
+ Neo.apps[port.appName].mainView.fire(eventName, data)
194
225
  })
195
226
  }
196
227
 
@@ -45,7 +45,7 @@ class Canvas extends Base {
45
45
 
46
46
  me.sendMessage('app', {action: 'registerPort', transfer: port2}, [port2]);
47
47
 
48
- me.channelPorts.app = port1;
48
+ me.channelPorts.app = port1
49
49
  }
50
50
 
51
51
  /**
@@ -58,7 +58,7 @@ class Canvas extends Base {
58
58
  action : 'reply',
59
59
  replyId: data.id,
60
60
  success: true
61
- });
61
+ })
62
62
  }
63
63
 
64
64
  /**
@@ -75,8 +75,8 @@ class Canvas extends Base {
75
75
  /* webpackMode: "lazy" */
76
76
  `../../${path}/canvas.mjs`
77
77
  ).then(module => {
78
- module.onStart();
79
- });
78
+ module.onStart()
79
+ })
80
80
  }
81
81
  }
82
82
 
@@ -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(