neo.mjs 6.10.3 → 6.10.5

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 (86) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/apps/form/model/SideNav.mjs +3 -3
  3. package/apps/form/view/SideNavList.mjs +8 -3
  4. package/apps/form/view/ViewportController.mjs +6 -7
  5. package/apps/portal/Overwrites.mjs +21 -0
  6. package/apps/portal/app.mjs +7 -0
  7. package/apps/portal/data/blog.json +743 -0
  8. package/apps/portal/index.html +15 -0
  9. package/apps/portal/model/BlogPost.mjs +59 -0
  10. package/apps/{learnneo → portal}/model/Content.mjs +3 -3
  11. package/apps/{learnneo → portal}/neo-config.json +1 -1
  12. package/apps/portal/store/BlogPosts.mjs +43 -0
  13. package/apps/{learnneo → portal}/store/Content.mjs +2 -5
  14. package/apps/portal/view/HeaderToolbar.mjs +65 -0
  15. package/apps/portal/view/Viewport.mjs +59 -0
  16. package/apps/portal/view/ViewportController.mjs +79 -0
  17. package/apps/portal/view/blog/Container.mjs +50 -0
  18. package/apps/portal/view/blog/List.mjs +173 -0
  19. package/apps/{newwebsite/view/Viewport.mjs → portal/view/home/MainContainer.mjs} +7 -8
  20. package/apps/{learnneo/view/home → portal/view/learn}/ContentTreeList.mjs +6 -6
  21. package/apps/{learnneo/view/home → portal/view/learn}/ContentView.mjs +3 -3
  22. package/apps/{learnneo/view → portal/view/learn}/LivePreview.mjs +32 -19
  23. package/apps/{learnneo/view/home → portal/view/learn}/MainContainer.mjs +27 -31
  24. package/apps/{learnneo/view/home → portal/view/learn}/MainContainerController.mjs +3 -3
  25. package/apps/{learnneo/view/home → portal/view/learn}/MainContainerModel.mjs +3 -3
  26. package/apps/website/Overwrites.mjs +16 -0
  27. package/apps/website/app.mjs +2 -1
  28. package/apps/website/view/blog/List.mjs +1 -1
  29. package/buildScripts/webpack/development/webpack.config.appworker.mjs +3 -1
  30. package/buildScripts/webpack/json/myApps.template.json +1 -1
  31. package/buildScripts/webpack/production/webpack.config.appworker.mjs +3 -1
  32. package/examples/ServiceWorker.mjs +2 -2
  33. package/package.json +2 -2
  34. package/resources/data/deck/learnneo/p/stylesheet.md +4 -1
  35. package/resources/design-tokens/json/component.json +22 -0
  36. package/resources/design-tokens/json/core.json +4 -0
  37. package/resources/design-tokens/json/semantic.json +8 -0
  38. package/resources/examples/data/formSideNav.json +19 -19
  39. package/resources/scss/src/apps/portal/blog/Container.scss +18 -0
  40. package/resources/scss/src/apps/portal/blog/List.scss +183 -0
  41. package/resources/scss/src/apps/{newwebsite/Viewport.scss → portal/home/MainContainer.scss} +1 -1
  42. package/resources/scss/src/tab/Strip.scss +1 -0
  43. package/resources/scss/src/tooltip/Base.scss +10 -6
  44. package/resources/scss/theme-dark/tooltip/Base.scss +10 -0
  45. package/resources/scss/theme-light/tooltip/Base.scss +10 -0
  46. package/resources/scss/theme-neo-light/button/Base.scss +24 -20
  47. package/resources/scss/theme-neo-light/design-tokens/Component.scss +6 -0
  48. package/resources/scss/theme-neo-light/design-tokens/Core.scss +2 -0
  49. package/resources/scss/theme-neo-light/design-tokens/Semantic.scss +4 -2
  50. package/resources/scss/theme-neo-light/tab/header/Button.scss +8 -7
  51. package/resources/scss/theme-neo-light/tooltip/Base.scss +13 -0
  52. package/src/DefaultConfig.mjs +2 -2
  53. package/src/Main.mjs +1 -1
  54. package/src/component/Base.mjs +5 -0
  55. package/src/component/Toast.mjs +5 -2
  56. package/src/container/Base.mjs +14 -0
  57. package/src/controller/Application.mjs +2 -1
  58. package/src/controller/Base.mjs +14 -3
  59. package/src/controller/Component.mjs +1 -1
  60. package/src/core/Base.mjs +7 -5
  61. package/src/core/Observable.mjs +15 -15
  62. package/src/data/RecordFactory.mjs +3 -3
  63. package/src/form/Container.mjs +50 -0
  64. package/src/form/field/Text.mjs +2 -2
  65. package/src/form/field/TextArea.mjs +5 -0
  66. package/src/main/addon/WindowPosition.mjs +72 -34
  67. package/src/tab/Strip.mjs +21 -36
  68. package/src/tab/header/Button.mjs +5 -7
  69. package/src/worker/App.mjs +26 -1
  70. package/src/worker/Base.mjs +41 -29
  71. package/src/worker/Canvas.mjs +4 -4
  72. package/src/worker/Manager.mjs +4 -3
  73. package/src/worker/mixin/RemoteMethodAccess.mjs +20 -18
  74. package/apps/learnneo/app.mjs +0 -6
  75. package/apps/learnneo/index.html +0 -21
  76. package/apps/learnneo/view/Viewport.mjs +0 -52
  77. package/apps/learnneo/view/ViewportController.mjs +0 -19
  78. package/apps/learnneo/view/home/HeaderToolbar.mjs +0 -47
  79. package/apps/newwebsite/app.mjs +0 -6
  80. package/apps/newwebsite/index.html +0 -13
  81. package/apps/newwebsite/neo-config.json +0 -7
  82. /package/resources/scss/src/apps/{learnneo/home → portal}/HeaderToolbar.scss +0 -0
  83. /package/resources/scss/src/apps/{learnneo → portal}/Viewport.scss +0 -0
  84. /package/resources/scss/src/apps/{learnneo/home → portal/learn}/ContentTreeList.scss +0 -0
  85. /package/resources/scss/src/apps/{learnneo/home → portal/learn}/ContentView.scss +0 -0
  86. /package/resources/scss/src/apps/{learnneo/home → portal/learn}/MainContainer.scss +0 -0
@@ -19,7 +19,7 @@
19
19
  }
20
20
 
21
21
  .vector {
22
- background-image : url("../../../../../../resources/images/Neo_Vector.svg");
22
+ background-image : url("../../../../../../../resources/images/Neo_Vector.svg");
23
23
  background-position: center center;
24
24
  background-repeat : no-repeat;
25
25
  background-size : contain;
@@ -2,6 +2,7 @@
2
2
  background-color: var(--tab-strip-background-color);
3
3
  height : var(--tab-strip-height);
4
4
  overflow : hidden;
5
+ position : relative;
5
6
 
6
7
  .neo-active-tab-indicator {
7
8
  background-color : var(--tab-indicator-background-color-active);
@@ -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
+ }
@@ -4,7 +4,6 @@ $border-color : #ddd;
4
4
  $border-style : solid;
5
5
  $border-width : 1px;
6
6
 
7
-
8
7
  :root .neo-theme-neo-light { // .neo-button
9
8
  --button-background-color : var(--cmp-button-primary-bg-default);
10
9
  --button-background-color-active : var(--cmp-button-primary-bg-active);
@@ -45,28 +44,28 @@ $border-width : 1px;
45
44
  --button-use-gradients : false;
46
45
 
47
46
  // {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);
47
+ --button-ghost-background-color : var(--cmp-button-ghost-bg-default);
48
+ --button-ghost-background-color-active : var(--cmp-button-ghost-bg-active);
49
+ --button-ghost-background-color-disabled : var(--cmp-button-ghost-bg-disabled);
50
+ --button-ghost-background-color-hover : var(--cmp-button-ghost-bg-hover);
52
51
  --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};
52
+ --button-ghost-badge-background-color : transparent;
53
+ --button-ghost-badge-color : transparent;
54
+ --button-ghost-border : none;
55
+ --button-ghost-border-active : none;
57
56
  --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);
57
+ --button-ghost-border-hover : none;
58
+ --button-ghost-border-pressed : none;
59
+ --button-ghost-glyph-color : var(--cmp-button-ghost-fg-default);
60
+ --button-ghost-glyph-color-active : inherit;
61
+ --button-ghost-glyph-color-disabled : var(--cmp-button-ghost-fg-disabled);
62
+ --button-ghost-glyph-color-hover : inherit;
63
+ --button-ghost-opacity-disabled : inherit;
65
64
  --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);
65
+ --button-ghost-text-color : var(--cmp-button-ghost-fg-default);
66
+ --button-ghost-text-color-active : inherit;
67
+ --button-ghost-text-color-disabled : var(--cmp-button-ghost-fg-disabled);
68
+ --button-ghost-text-color-hover : inherit;
70
69
 
71
70
  // {module: Button; ui: --secondary}
72
71
  --button-secondary-background-color : var(--cmp-button-secondary-bg-default);
@@ -115,4 +114,9 @@ $border-width : 1px;
115
114
  --button-tertiary-text-color-active : var(--button-text-color);
116
115
  --button-tertiary-text-color-disabled : inherit;
117
116
  --button-tertiary-text-color-hover : var(--button-text-color);
117
+
118
+ // custom overrides
119
+ .neo-button {
120
+ min-width: var(--cmp-button-height);
121
+ }
118
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
  }
@@ -27,15 +27,16 @@
27
27
  --tab-button-padding : 7px var(--cmp-tab-spacinghorizontal) 6px;
28
28
  --tab-button-text-color : var(--sem-color-fg-neutral-subdued);
29
29
  --tab-button-text-transform : uppercase;
30
- }
31
30
 
32
- .neo-button-text {
33
- font-size: var(--sem-typo-label-regular-fontSize) !important;
34
- }
31
+ // custom overrides
32
+ .neo-button-text {
33
+ font-size: var(--sem-typo-label-regular-fontSize) !important;
34
+ }
35
35
 
36
- .pressed {
37
- .neo-button-glyph, .neo-button-text {
38
- color: var(--sem-color-fg-neutral-contrast) !important;
36
+ .pressed {
37
+ .neo-button-glyph, .neo-button-text {
38
+ color: var(--sem-color-fg-neutral-contrast) !important;
39
+ }
39
40
  }
40
41
  }
41
42
 
@@ -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.3'
239
+ * @default '6.10.5'
240
240
  * @memberOf! module:Neo
241
241
  * @name config.version
242
242
  * @type String
243
243
  */
244
- version: '6.10.3'
244
+ version: '6.10.5'
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
  /**
@@ -335,6 +335,11 @@ class Base extends CoreBase {
335
335
  * @member {Number|String|null} width_=null
336
336
  */
337
337
  width_: null,
338
+ /**
339
+ * The custom windowIs (timestamp) this component belongs to
340
+ * @member {Number|null} windowId_=null
341
+ */
342
+ windowId_: null,
338
343
  /**
339
344
  * @member {String[]|null} wrapperCls_=null
340
345
  */
@@ -56,6 +56,10 @@ class Toast extends Base {
56
56
  * @member {Boolean} closable=false
57
57
  */
58
58
  closable_: false,
59
+ /**
60
+ * @member {Boolean} floating=true
61
+ */
62
+ floating: true,
59
63
  /**
60
64
  * If set, it shows this icon in front of the text
61
65
  * e.g. 'fa fa-cog'
@@ -107,8 +111,7 @@ class Toast extends Base {
107
111
  ]},
108
112
  {cls: ['neo-toast-close', 'fa', 'fa-close'], removeDom: true}
109
113
  ]
110
- }]},
111
- floating : true
114
+ }]}
112
115
  }
113
116
 
114
117
  /**
@@ -217,6 +217,20 @@ class Base extends Component {
217
217
  }
218
218
  }
219
219
 
220
+ /**
221
+ * Triggered after the windowId config got changed
222
+ * @param {Number|null} value
223
+ * @param {Number|null} oldValue
224
+ * @protected
225
+ */
226
+ afterSetWindowId(value, oldValue) {
227
+ value && this.items?.forEach(item => {
228
+ if (Neo.isObject(item)) {
229
+ item.windowId = value
230
+ }
231
+ })
232
+ }
233
+
220
234
  /**
221
235
  * Convert items object to an array for onward storage as _items
222
236
  * @param {Object|Object[]} value
@@ -107,7 +107,8 @@ class Application extends Base {
107
107
  if (value) {
108
108
  return ClassSystemUtil.beforeSetInstance(value, null, {
109
109
  appName : this.name,
110
- parentId: this.parentId
110
+ parentId: this.parentId,
111
+ windowId: Neo.config.windowId
111
112
  })
112
113
  }
113
114
 
@@ -21,6 +21,12 @@ class Base extends CoreBase {
21
21
  * @protected
22
22
  */
23
23
  ntype: 'controller',
24
+ /**
25
+ * If the URL does not contain a hash value when creating this controller instance,
26
+ * neo will set this hash value for us.
27
+ * @member {String|null} defaultHash=null
28
+ */
29
+ defaultHash: null,
24
30
  /**
25
31
  * @member {String|null} defaultRoute=null
26
32
  */
@@ -90,9 +96,14 @@ class Base extends CoreBase {
90
96
  *
91
97
  */
92
98
  onConstructed() {
93
- let currentHash = HashHistory.first();
99
+ let currentHash = HashHistory.first(),
100
+ defaultHash = this.defaultHash;
94
101
 
95
- currentHash && this.onHashChange(currentHash, null)
102
+ if (currentHash) {
103
+ this.onHashChange(currentHash, null)
104
+ } else if (defaultHash) {
105
+ Neo.Main.setRoute({value: defaultHash})
106
+ }
96
107
  }
97
108
 
98
109
  /**
@@ -185,7 +196,7 @@ class Base extends CoreBase {
185
196
  * @returns {Number}
186
197
  */
187
198
  #sortRoutes(route1, route2) {
188
- return route1.match(amountSlashesRegex).length - route2.match(amountSlashesRegex).length
199
+ return (route1.match(amountSlashesRegex) || []).length - (route2.match(amountSlashesRegex)|| []).length
189
200
  }
190
201
  }
191
202
 
@@ -189,7 +189,7 @@ class Component extends Base {
189
189
  handlerScope = me.getHandlerScope(eventHandler, component);
190
190
 
191
191
  if (!handlerScope) {
192
- Logger.logError('Unknown event handler for', eventHandler, component)
192
+ console.error('Unknown event handler for', eventHandler, component)
193
193
  } else if (handlerScope !== true) {
194
194
  listener.fn = handlerScope[eventHandler].bind(handlerScope)
195
195
  }
package/src/core/Base.mjs CHANGED
@@ -189,12 +189,14 @@ class Base {
189
189
  delayable = me.delayable ? Neo.merge({}, me.delayable, ctorDelayable) : ctorDelayable;
190
190
 
191
191
  Object.entries(delayable).forEach(([key, value]) => {
192
- let map = {
193
- debounce() {me[key] = new debounce(me[key], me, value.timer)},
194
- throttle() {me[key] = new throttle(me[key], me, value.timer)}
195
- };
192
+ if (value) {
193
+ let map = {
194
+ debounce() {me[key] = new debounce(me[key], me, value.timer)},
195
+ throttle() {me[key] = new throttle(me[key], me, value.timer)}
196
+ };
196
197
 
197
- map[value.type]?.()
198
+ map[value.type]?.()
199
+ }
198
200
  })
199
201
  }
200
202
 
@@ -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
  */
@@ -68,6 +68,9 @@ class RecordFactory extends Base {
68
68
  properties;
69
69
 
70
70
  Object.defineProperties(me, {
71
+ [Symbol.for('isRecord')]: {
72
+ value: true
73
+ },
71
74
  _isModified: {
72
75
  value : false,
73
76
  writable: true
@@ -87,9 +90,6 @@ class RecordFactory extends Base {
87
90
  parsedValue = instance.parseRecordValue(me, field, value, config);
88
91
 
89
92
  properties = {
90
- [Symbol.for('isRecord')]: {
91
- value: true
92
- },
93
93
  [symbol]: {
94
94
  value : parsedValue,
95
95
  writable: true
@@ -136,6 +136,56 @@ class Container extends BaseContainer {
136
136
  return fields
137
137
  }
138
138
 
139
+
140
+ /**
141
+ * This function will return one of the following states:
142
+ * - clean => all fields are clean (untouched)
143
+ * - invalid => at least one field is invalid
144
+ * - valid => all required fields are valid
145
+ * - inProgress => at least one field is valid, at least one field is clean
146
+ * @returns {Promise<String>}
147
+ */
148
+ async getFormState() {
149
+ let fields = await this.getFields(),
150
+ i = 0,
151
+ hasCleanFields = false,
152
+ hasInvalidFields = false,
153
+ hasUncleanFields = false,
154
+ hasValidFields = false,
155
+ len = fields.length,
156
+ field, isClean, isValid;
157
+
158
+ for (; i < len; i++) {
159
+ field = fields[i];
160
+ isClean = field.clean;
161
+ isValid = field.isValid();
162
+
163
+ if (!isClean && !isValid) {
164
+ return 'invalid'
165
+ } else if (isValid) {
166
+ hasValidFields = true
167
+ } else if (!isValid) {
168
+ hasInvalidFields = true
169
+ }
170
+
171
+ if (isClean) {
172
+ hasCleanFields = true
173
+ } else {
174
+ hasUncleanFields = true
175
+ }
176
+ }
177
+
178
+ if (!hasInvalidFields) {
179
+ return 'valid'
180
+ }
181
+
182
+ if (hasCleanFields && !hasUncleanFields) {
183
+ return 'clean'
184
+ }
185
+
186
+ return 'inProgress'
187
+ }
188
+
139
189
  /**
140
190
  * @returns {Promise<Object>}
141
191
  */
@@ -703,7 +703,7 @@ class Text extends Base {
703
703
 
704
704
  me.silentVdomUpdate = true;
705
705
 
706
- me.validate(false);
706
+ oldValue !== undefined && me.validate(me.clean);
707
707
  me.changeInputElKey('required', value ? value : null);
708
708
  me.labelText = me.labelText; // apply the optional text if needed
709
709
 
@@ -1279,7 +1279,7 @@ class Text extends Base {
1279
1279
  cls;
1280
1280
 
1281
1281
  if (!me.readOnly) {
1282
- me.validate(); // silent
1282
+ me.validate(false);
1283
1283
 
1284
1284
  cls = me.cls; // has to get set after validate()
1285
1285
 
@@ -37,6 +37,11 @@ class TextArea extends Text {
37
37
  * @member {String[]} baseCls=['neo-textarea','neo-textfield']
38
38
  */
39
39
  baseCls: ['neo-textarea', 'neo-textfield'],
40
+ /**
41
+ * True shows a clear trigger in case the field has a non-empty value.
42
+ * @member {Boolean} clearable=false
43
+ */
44
+ clearable: false,
40
45
  /**
41
46
  * The visible width of the text control, in average character widths.
42
47
  * If it is specified, it must be a positive integer.