neo.mjs 6.2.1 → 6.3.1

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.2.1'
23
+ * @member {String} version='6.3.1'
24
24
  */
25
- version: '6.2.1'
25
+ version: '6.3.1'
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.2.1'
23
+ * @member {String} version='6.3.1'
24
24
  */
25
- version: '6.2.1'
25
+ version: '6.3.1'
26
26
  }
27
27
 
28
28
  /**
@@ -0,0 +1,58 @@
1
+ import ConfigurationViewport from '../ConfigurationViewport.mjs';
2
+
3
+ import Component from '../../src/component/Base.mjs';
4
+ import PopoverPlugin from '../../src/plugin/Popover.mjs';
5
+ import Button from '../../src/button/Base.mjs';
6
+
7
+ /**
8
+ * @class Neo.examples.popover.MainContainer
9
+ * @extends Neo.examples.ConfigurationViewport
10
+ */
11
+ class MainContainer extends ConfigurationViewport {
12
+ static config = {
13
+ className : 'Neo.examples.popover.MainContainer',
14
+ autoMount : true,
15
+ configItemLabelWidth: 100,
16
+ configItemWidth : 230,
17
+ layout : {ntype: 'hbox', align: 'stretch'},
18
+ cls : ['examples-container-accordion']
19
+ }
20
+
21
+
22
+ /**
23
+ * @returns {*}
24
+ */
25
+ createExampleComponent() {
26
+ return Neo.ntype({
27
+ ntype : 'container',
28
+ height: '30%',
29
+ layout: {ntype: 'vbox', align: 'center', pack: 'center'},
30
+ items : [{
31
+ module: Component,
32
+ height: 10
33
+ }, {
34
+ module : Button,
35
+ width : 200,
36
+ text : 'Click Me',
37
+ plugins: [{
38
+ module: PopoverPlugin,
39
+ align : 'bc-tc',
40
+ items : [{
41
+ ntype : 'panel',
42
+ headers: [{
43
+ dock: 'top',
44
+ html: 'HEADER'
45
+ }],
46
+ items : [{
47
+ html: 'This is a comment about the button'
48
+ }]
49
+ }]
50
+ }]
51
+ }]
52
+ });
53
+ }
54
+ }
55
+
56
+ Neo.applyClassConfig(MainContainer);
57
+
58
+ export default MainContainer;
@@ -2,5 +2,5 @@ import MainContainer from './MainContainer.mjs';
2
2
 
3
3
  export const onStart = () => Neo.app({
4
4
  mainView: MainContainer,
5
- name : 'Neo.examples.treeSelectionModel'
5
+ name : 'Neo.examples.popover'
6
6
  });
@@ -0,0 +1,13 @@
1
+ {
2
+ "appPath" : "examples/popover/app.mjs",
3
+ "basePath" : "../../",
4
+ "environment": "development",
5
+ "mainPath" : "./Main.mjs",
6
+ "themes" : ["neo-theme-dark", "neo-theme-light"],
7
+ "mainThreadAddons": [
8
+ "DragDrop",
9
+ "ScrollSync",
10
+ "Stylesheet",
11
+ "Popover"
12
+ ]
13
+ }
@@ -57,7 +57,7 @@
57
57
  "id": 8,
58
58
  "name": "V01 . At vero eos et accusam",
59
59
  "parentId": 7,
60
- "collapsed": true,
60
+ "collapsed": false,
61
61
  "isLeaf": false,
62
62
  "content": "Auswählliste",
63
63
  "iconCls": "fa fa-chevron-down color-red"
@@ -10,12 +10,12 @@ import ViewController from '../../src/controller/Component.mjs';
10
10
  import ViewModel from '../../src/model/Component.mjs';
11
11
 
12
12
  /**
13
- * @class Neo.examples.treeSelectionModel.MainContainer
13
+ * @class Neo.examples.treeAccordion.MainContainer
14
14
  * @extends Neo.examples.ConfigurationViewport
15
15
  */
16
16
  class MainContainer extends ConfigurationViewport {
17
17
  static config = {
18
- className : 'Neo.examples.treeSelectionModel.MainContainer',
18
+ className : 'Neo.examples.treeAccordion.MainContainer',
19
19
  autoMount : true,
20
20
  configItemLabelWidth: 100,
21
21
  configItemWidth : 230,
@@ -46,6 +46,14 @@ class MainContainer extends ConfigurationViewport {
46
46
  listeners : {change: me.onConfigChange.bind(me, 'firstParentIsVisible')},
47
47
  style : {marginTop: '10px'},
48
48
  valueLabelText: 'firstParentIsVisible'
49
+ }, {
50
+ module : CheckBox,
51
+ checked : treeList.showIcon,
52
+ hideLabel : true,
53
+ hideValueLabel: false,
54
+ listeners : {change: me.onConfigChange.bind(me, 'showIcon')},
55
+ style : {marginTop: '10px'},
56
+ valueLabelText: 'showIcon'
49
57
  }, {
50
58
  module : NumberField,
51
59
  clearable: true,
@@ -89,7 +97,7 @@ class MainContainer extends ConfigurationViewport {
89
97
  },
90
98
 
91
99
  autoLoad: true,
92
- url : '../../examples/treeSelectionModel/tree.json'
100
+ url : '../../examples/treeAccordion/tree.json'
93
101
  });
94
102
 
95
103
  return Neo.ntype({
@@ -135,7 +143,7 @@ class MainContainer extends ConfigurationViewport {
135
143
  }, {
136
144
  module: Panel,
137
145
  height: 150,
138
- width : '100%',
146
+ flex : 1,
139
147
 
140
148
  itemDefaults: {
141
149
  style: {
@@ -0,0 +1,6 @@
1
+ import MainContainer from './MainContainer.mjs';
2
+
3
+ export const onStart = () => Neo.app({
4
+ mainView: MainContainer,
5
+ name : 'Neo.examples.treeAccordion'
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 Tree</title>
7
+ </head>
8
+ <body>
9
+ <script src="../../src/MicroLoader.mjs" type="module"></script>
10
+ </body>
11
+ </html>
@@ -1,5 +1,5 @@
1
1
  {
2
- "appPath" : "examples/treeSelectionModel/app.mjs",
2
+ "appPath" : "examples/treeAccordion/app.mjs",
3
3
  "basePath" : "../../",
4
4
  "environment": "development",
5
5
  "mainPath" : "./Main.mjs",
@@ -0,0 +1,112 @@
1
+ [
2
+ {
3
+ "id": 1,
4
+ "name": "ROOT",
5
+ "parentId": null,
6
+ "collapsed": false,
7
+ "isLeaf": false
8
+ },
9
+ {
10
+ "id": 2,
11
+ "name": "Lorem ipsum dolor sit amet, consetetur sadipscing elitr,",
12
+ "parentId": 1,
13
+ "isLeaf": true,
14
+ "content": "Textfeld",
15
+ "iconCls": "fa fa-t color-blue"
16
+ },
17
+ {
18
+ "id": 3,
19
+ "name": "Sed diam nonumy",
20
+ "parentId": 1,
21
+ "isLeaf": true,
22
+ "content": "Textfeld",
23
+ "iconCls": "fa fa-t color-blue"
24
+ },
25
+ {
26
+ "id": 4,
27
+ "name": "Eirmod",
28
+ "parentId": 1,
29
+ "isLeaf": true,
30
+ "content": "Textfeld",
31
+ "iconCls": "fa fa-t color-blue"
32
+ },
33
+ {
34
+ "id": 5,
35
+ "name": "Tempor",
36
+ "parentId": 1,
37
+ "isLeaf": true,
38
+ "content": "Textfeld",
39
+ "iconCls": "fa fa-t color-blue"
40
+ },
41
+ {
42
+ "id": 6,
43
+ "name": "Invidunt ut labore",
44
+ "parentId": 1,
45
+ "isLeaf": true,
46
+ "content": "Textfeld",
47
+ "iconCls": "fa fa-t color-blue"
48
+ },
49
+ {
50
+ "id": 7,
51
+ "name": "Dolore magna aliquyam",
52
+ "parentId": null,
53
+ "collapsed": false,
54
+ "isLeaf": false
55
+ },
56
+ {
57
+ "id": 8,
58
+ "name": "V01 . At vero eos et accusam",
59
+ "parentId": 7,
60
+ "collapsed": false,
61
+ "isLeaf": false,
62
+ "content": "Auswählliste",
63
+ "iconCls": "fa fa-chevron-down color-red"
64
+ },
65
+ {
66
+ "id": 9,
67
+ "name": "Justo 1",
68
+ "parentId": 8,
69
+ "isLeaf": true,
70
+ "content": "Child",
71
+ "iconCls": "fa-regular fa-square-check color-green"
72
+ },
73
+ {
74
+ "id": 10,
75
+ "name": "Justo 2",
76
+ "parentId": 8,
77
+ "isLeaf": true,
78
+ "content": "Child",
79
+ "iconCls": "fa-regular fa-square-check color-green"
80
+ },
81
+ {
82
+ "id": 11,
83
+ "name": "Rebum",
84
+ "parentId": null,
85
+ "collapsed": false,
86
+ "isLeaf": false
87
+ },
88
+ {
89
+ "id": 12,
90
+ "name": "F0801 - Stet ",
91
+ "parentId": 11,
92
+ "isLeaf": true,
93
+ "content": "Datumfeld",
94
+ "iconCls": "fa-solid fa-calendar-days color-yellow"
95
+ },
96
+ {
97
+ "id": 13,
98
+ "name": "F0802 - Kasd",
99
+ "parentId": 11,
100
+ "isLeaf": true,
101
+ "content": "Datumfeld",
102
+ "iconCls": "fa-solid fa-calendar-days color-yellow"
103
+ },
104
+ {
105
+ "id": 14,
106
+ "name": "E30 - Gubergren",
107
+ "parentId": 11,
108
+ "isLeaf": true,
109
+ "content": "Mehrfachauswahl",
110
+ "iconCls": "fa-regular fa-square-check color-green"
111
+ }
112
+ ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "6.2.1",
3
+ "version": "6.3.1",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -0,0 +1,111 @@
1
+ [popover] {
2
+ inset: unset;
3
+ border-radius: 5px;
4
+ box-shadow: 2px 2px 7px #111;
5
+ background-color: #222;
6
+
7
+ //&.neo-popover[popover] {
8
+ // display: revert;
9
+ //}
10
+
11
+ &.bc-tc {
12
+ position-fallback: --bc-tc;
13
+ translate: -50%;
14
+ margin: .5rem 0;
15
+ }
16
+ &.tc-bc {
17
+ position-fallback: --tc-bc;
18
+ translate: -50%;
19
+ margin: .5rem 0;
20
+ }
21
+ &.tl-tr {
22
+ position-fallback: --tl-tr;
23
+ margin: 0 .5rem;
24
+ }
25
+ &.tr-tl {
26
+ position-fallback: --tl-tr;
27
+ margin: 0 .5rem;
28
+ }
29
+ &.cl-cr {
30
+ position-fallback: --cl-cr;
31
+ margin: 0 .25rem;
32
+ translate: 0 -50%;
33
+ }
34
+ &.cr-cl {
35
+ position-fallback: --cr-cl;
36
+ margin: 0 .25rem;
37
+ translate: 0 -50%;
38
+ }
39
+ }
40
+
41
+ @position-fallback --bc-tc {
42
+ @try {
43
+ bottom: anchor(top);
44
+ left: anchor(center);
45
+ }
46
+
47
+ @try {
48
+ top: anchor(bottom);
49
+ left: anchor(center);
50
+ }
51
+ }
52
+
53
+ @position-fallback --tc-bc {
54
+ @try {
55
+ top: anchor(bottom);
56
+ left: anchor(center);
57
+ }
58
+
59
+ @try {
60
+ bottom: anchor(top);
61
+ left: anchor(center);
62
+ }
63
+ }
64
+
65
+ @position-fallback --tl-tr {
66
+ @try {
67
+ top: anchor(top);
68
+ left: anchor(right);
69
+ }
70
+
71
+ @try {
72
+ top: anchor(top);
73
+ right: anchor(left);
74
+ }
75
+ }
76
+
77
+ @position-fallback --tr-tl {
78
+ @try {
79
+ top: anchor(top);
80
+ right: anchor(left);
81
+ }
82
+
83
+ @try {
84
+ top: anchor(top);
85
+ left: anchor(right);
86
+ }
87
+ }
88
+
89
+ @position-fallback --cr-cl {
90
+ @try {
91
+ top: anchor(center);
92
+ right: anchor(left);
93
+ }
94
+
95
+ @try {
96
+ top: anchor(center);
97
+ left: anchor(right);
98
+ }
99
+ }
100
+
101
+ @position-fallback --cl-cr {
102
+ @try {
103
+ top: anchor(center);
104
+ left: anchor(right);
105
+ }
106
+
107
+ @try {
108
+ top: anchor(center);
109
+ right: anchor(left);
110
+ }
111
+ }
@@ -236,12 +236,12 @@ const DefaultConfig = {
236
236
  useVdomWorker: true,
237
237
  /**
238
238
  * buildScripts/injectPackageVersion.mjs will update this value
239
- * @default '6.2.1'
239
+ * @default '6.3.1'
240
240
  * @memberOf! module:Neo
241
241
  * @name config.version
242
242
  * @type String
243
243
  */
244
- version: '6.2.1'
244
+ version: '6.3.1'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
@@ -123,8 +123,8 @@ class Container extends BaseContainer {
123
123
 
124
124
  await this.loadModules();
125
125
 
126
- ComponentManager.getChildComponents(this).forEach(item => {
127
- item instanceof BaseField && fields.push(item)
126
+ ComponentManager.getChildComponents(this).forEach(field => {
127
+ field instanceof BaseField && fields.push(field)
128
128
  });
129
129
 
130
130
  return fields
@@ -135,40 +135,52 @@ class Container extends BaseContainer {
135
135
  */
136
136
  async getValues() {
137
137
  let fields = await this.getFields(),
138
+ Radio = Neo.form.field.Radio,
138
139
  values = {},
139
- itemName, key, ns, nsArray, value;
140
+ fieldName, key, ns, nsArray, value;
140
141
 
141
- fields.forEach(item => {
142
- value = item.getValue();
142
+ fields.forEach(field => {
143
+ value = field.getValue();
143
144
 
144
- if (item.name) {
145
- itemName = item.name;
145
+ if (field.name) {
146
+ fieldName = field.name;
146
147
 
147
- if (item.formGroup) {
148
- itemName = item.formGroup + '.' + itemName;
148
+ if (field.formGroup) {
149
+ fieldName = field.formGroup + '.' + fieldName;
149
150
  }
150
151
 
151
- nsArray = itemName.split('.');
152
+ nsArray = fieldName.split('.');
152
153
  key = nsArray.pop();
153
154
  ns = Neo.nsWithArrays(nsArray, true, values);
154
155
  } else {
155
- key = item.id;
156
+ key = field.id;
156
157
  ns = values
157
158
  }
158
159
 
160
+ // Ensuring that Radios will not return arrays
161
+ if (Radio && field instanceof Radio) {
162
+ // Only overwrite an existing value with a checked value
163
+ if (Object.hasOwn(ns, key)) {
164
+ if (value !== field.uncheckedValue) {
165
+ ns[key] = value
166
+ }
167
+ } else {
168
+ ns[key] = value
169
+ }
170
+ }
159
171
  /*
160
172
  * CheckBoxes need custom logic
161
173
  * => we only want to pass the uncheckedValue in case the field does not belong to a group
162
174
  * (multiple fields using the same name)
163
175
  */
164
- if (Object.hasOwn(ns, key) && value !== undefined) {
165
- if (ns[key] === item.uncheckedValue) {
176
+ else if (Object.hasOwn(ns, key) && value !== undefined) {
177
+ if (ns[key] === field.uncheckedValue) {
166
178
  ns[key] = []
167
179
  } else if (!Array.isArray(ns[key])) {
168
180
  ns[key] = [ns[key]]
169
181
  }
170
182
 
171
- value !== item.uncheckedValue && ns[key].unshift(value)
183
+ value !== field.uncheckedValue && ns[key].unshift(value)
172
184
  } else if (value !== undefined) {
173
185
  ns[key] = value
174
186
  }
@@ -223,11 +235,11 @@ class Container extends BaseContainer {
223
235
  fields = await me.getFields(),
224
236
  path, value;
225
237
 
226
- fields.forEach(item => {
227
- path = me.getFieldPath(item);
238
+ fields.forEach(field => {
239
+ path = me.getFieldPath(field);
228
240
  value = Neo.nsWithArrays(path, false, values);
229
241
 
230
- item.reset(path ? value : null)
242
+ field.reset(path ? value : null)
231
243
  })
232
244
  }
233
245
 
@@ -241,38 +253,38 @@ class Container extends BaseContainer {
241
253
  fields = await me.getFields(),
242
254
  fieldConfigs, isCheckBox, isRadio, path, value;
243
255
 
244
- fields.forEach(item => {
245
- path = me.getFieldPath(item);
256
+ fields.forEach(field => {
257
+ path = me.getFieldPath(field);
246
258
  fieldConfigs = Neo.nsWithArrays(path, false, configs);
247
259
 
248
260
  if (fieldConfigs) {
249
261
  if (suspendEvents) {
250
- item.suspendEvents = true
262
+ field.suspendEvents = true
251
263
  }
252
264
 
253
- isCheckBox = Neo.form.field?.CheckBox && item instanceof Neo.form.field.CheckBox;
265
+ isCheckBox = Neo.form.field?.CheckBox && field instanceof Neo.form.field.CheckBox;
266
+ isRadio = Neo.form.field?.Radio && field instanceof Neo.form.field.Radio;
254
267
  value = fieldConfigs.value;
255
268
 
256
- if (isCheckBox) {
269
+ if (isRadio) {
270
+ fieldConfigs.checked = field.value === value;
271
+ delete fieldConfigs.value
272
+ } else if (isCheckBox) {
257
273
  if (Neo.typeOf(value) === 'Array') {
258
- if (value.includes(item.value)) {
274
+ if (value.includes(field.value)) {
259
275
  fieldConfigs.checked = true
260
276
  }
261
277
  } else {
262
- fieldConfigs.checked = item.value === value
278
+ fieldConfigs.checked = field.value === value
263
279
  }
264
- } else if (value !== undefined) {
265
- isRadio = Neo.form.field?.Radio && item instanceof Neo.form.field.Radio;
266
280
 
267
- if (isRadio) {
268
- fieldConfigs.checked = item.value === value
269
- }
281
+ delete fieldConfigs.value
270
282
  }
271
283
 
272
- item.set(fieldConfigs)
284
+ field.set(fieldConfigs)
273
285
 
274
286
  if (suspendEvents) {
275
- delete item.suspendEvents
287
+ delete field.suspendEvents
276
288
  }
277
289
  }
278
290
  })
@@ -299,8 +311,8 @@ class Container extends BaseContainer {
299
311
  fields = await this.getFields(),
300
312
  validField;
301
313
 
302
- fields.forEach(item => {
303
- validField = item.validate?.(false);
314
+ fields.forEach(field => {
315
+ validField = field.validate?.(false);
304
316
 
305
317
  if (!validField) {
306
318
  isValid = false
@@ -76,9 +76,7 @@ class Base extends Component {
76
76
  * @param {*} oldValue
77
77
  */
78
78
  afterSetValue(value, oldValue) {
79
- if (oldValue !== undefined) {
80
- this.fireChangeEvent(value, oldValue)
81
- }
79
+ oldValue !== undefined && this.fireChangeEvent(value, oldValue)
82
80
  }
83
81
 
84
82
  /**
@@ -134,27 +132,42 @@ class Base extends Component {
134
132
  */
135
133
  fireChangeEvent(value, oldValue) {
136
134
  let me = this,
137
- FormContainer = Neo.form?.Container;
135
+ FormContainer = Neo.form?.Container,
136
+ opts = {component: me, oldValue, value};
138
137
 
139
- me.fire('change', {
140
- component: me,
141
- oldValue,
142
- value
143
- });
138
+ if (Neo.isFunction(me.getGroupValue)) {
139
+ opts.groupValue = me.getGroupValue()
140
+ }
141
+
142
+ me.fire('change', opts);
144
143
 
145
144
  if (!me.suspendEvents) {
146
145
  ComponentManager.getParents(me).forEach(parent => {
147
146
  if (FormContainer && parent instanceof FormContainer) {
148
- parent.fire('fieldChange', {
149
- component: me,
150
- oldValue,
151
- value
152
- })
147
+ parent.fire('fieldChange', opts)
153
148
  }
154
149
  })
155
150
  }
156
151
  }
157
152
 
153
+ /**
154
+ * Forms in neo can be nested. This method will return the closest parent which is a form.Container or null.
155
+ * @returns {Neo.form.Container|null}
156
+ */
157
+ getClosestForm() {
158
+ let me = this,
159
+ FormContainer = Neo.form?.Container,
160
+ parent;
161
+
162
+ for (parent of ComponentManager.getParents(me)) {
163
+ if (FormContainer && parent instanceof FormContainer) {
164
+ return parent
165
+ }
166
+ }
167
+
168
+ return null
169
+ }
170
+
158
171
  /**
159
172
  * Override this method as needed
160
173
  * @returns {Object|null}
@@ -213,15 +226,17 @@ class Base extends Component {
213
226
  super.onFocusLeave?.(data);
214
227
 
215
228
  let me = this,
216
- FormContainer = Neo.form?.Container;
229
+ FormContainer = Neo.form?.Container,
230
+ opts = {...data, component: me, value: me.getValue()};
231
+
232
+ if (Neo.isFunction(me.getGroupValue)) {
233
+ opts.groupValue = me.getGroupValue()
234
+ }
217
235
 
218
236
  if (!me.suspendEvents) {
219
237
  ComponentManager.getParents(me).forEach(parent => {
220
238
  if (FormContainer && parent instanceof FormContainer) {
221
- parent.fire('fieldFocusLeave', {
222
- ...data,
223
- component: me
224
- })
239
+ parent.fire('fieldFocusLeave', opts)
225
240
  }
226
241
  })
227
242
  }
@@ -194,7 +194,7 @@ class CheckBox extends Base {
194
194
  me.update();
195
195
 
196
196
  if (oldValue !== undefined) {
197
- me.fireChangeEvent(me.getValue(), null)
197
+ me.fireChangeEvent(me.getValue(), me.getOldValue())
198
198
  }
199
199
  }
200
200
 
@@ -433,6 +433,23 @@ class CheckBox extends Base {
433
433
  return true
434
434
  }
435
435
 
436
+ /**
437
+ * @returns {String[]}
438
+ */
439
+ getGroupValue() {
440
+ let form = this.getClosestForm(),
441
+ fields = ComponentManager.find({path: this.getPath()}),
442
+ value = [];
443
+
444
+ fields.forEach(field => {
445
+ if (field.checked && field.getClosestForm() === form) {
446
+ NeoArray.add(value, field.value)
447
+ }
448
+ });
449
+
450
+ return value
451
+ }
452
+
436
453
  /**
437
454
  * @returns {String}
438
455
  */
@@ -462,6 +479,17 @@ class CheckBox extends Base {
462
479
  }
463
480
 
464
481
  /**
482
+ * Counterpart to getValue(), returning the uncheckedValue if checked
483
+ * @returns {String|null}
484
+ */
485
+ getOldValue() {
486
+ let me = this;
487
+
488
+ return me.checked ? me.uncheckedValue : me.value
489
+ }
490
+
491
+ /**
492
+ * Returns this.value if checked, otherwise this.uncheckedValue
465
493
  * @returns {String|null}
466
494
  */
467
495
  getValue() {
@@ -482,7 +510,7 @@ class CheckBox extends Base {
482
510
  */
483
511
  isValid() {
484
512
  this.validate(true); // silent
485
-
513
+
486
514
  return this.error ? false : super.isValid()
487
515
  }
488
516
 
@@ -45,10 +45,22 @@ class Radio extends CheckBox {
45
45
  }
46
46
 
47
47
  /**
48
- * @returns {String|null}
48
+ * Radios should only fire change & fieldChange events if checked.
49
+ * If there was just 1 radio, you can not uncheck it.
50
+ * @param {*} value
51
+ * @param {*} oldValue
49
52
  */
50
- getValue() {
51
- return this.checked ? this.value : undefined
53
+ fireChangeEvent(value, oldValue) {
54
+ this.checked && super.fireChangeEvent(value, oldValue)
55
+ }
56
+
57
+ /**
58
+ * @returns {String[]}
59
+ */
60
+ getGroupValue() {
61
+ let value = super.getGroupValue();
62
+
63
+ return value.length > 0 ? value[0] : []
52
64
  }
53
65
 
54
66
  /**
@@ -0,0 +1,75 @@
1
+ import Base from '../../core/Base.mjs';
2
+ import DomAccess from '../DomAccess.mjs'
3
+ /**
4
+ * Addon for Popover
5
+ * @class Neo.main.addon.Popover
6
+ * @extends Neo.core.Base
7
+ * @singleton
8
+ */
9
+ class Popover extends Base {
10
+ static config = {
11
+ /**
12
+ * @member {String} className='Neo.main.addon.Popover'
13
+ * @protected
14
+ */
15
+ className: 'Neo.main.addon.Popover',
16
+ /**
17
+ * Remote method access for other workers
18
+ * @member {Object} remote={app: [//...]}
19
+ * @protected
20
+ */
21
+ remote: {
22
+ app: [
23
+ 'hide',
24
+ 'show',
25
+ 'toggle'
26
+ ]
27
+ },
28
+ /**
29
+ * @member {Boolean} singleton=true
30
+ * @protected
31
+ */
32
+ singleton: true
33
+ }
34
+
35
+ /**
36
+ * @param {Object} data
37
+ * @param {String} data.id
38
+ * @returns {Boolean}
39
+ */
40
+ hide(data) {
41
+ this.getPopover(data.id).hidePopover();
42
+ return true;
43
+ }
44
+
45
+ /**
46
+ * @param {Object} data
47
+ * @param {String} data.id
48
+ * @returns {Boolean}
49
+ */
50
+ show(data) {
51
+ this.getPopover(data.id).showPopover();
52
+ return true;
53
+ }
54
+
55
+ /**
56
+ * @param {Object} data
57
+ * @param {String} data.id
58
+ * @returns {Boolean}
59
+ */
60
+ toggle(data) {
61
+ this.getPopover(data.id).togglePopover();
62
+ return true;
63
+ }
64
+
65
+ getPopover(parentId) {
66
+ const parent = document.getElementById(parentId),
67
+ popover = document.getElementById(parent.getAttribute('popovertarget'));
68
+
69
+ return popover;
70
+ }
71
+ }
72
+
73
+ let instance = Neo.applyClassConfig(Popover);
74
+
75
+ export default instance;
@@ -0,0 +1,149 @@
1
+ import Base from '../../core/Base.mjs';
2
+
3
+ /**
4
+ * Helper class to include Google's Material Web Components into your neo.mjs app
5
+ * https://www.amcharts.com/docs/v4/
6
+ * @class Neo.main.addon.PrefixField
7
+ * @extends Neo.core.Base
8
+ * @singleton
9
+ */
10
+ class PrefixField extends Base {
11
+ static config = {
12
+ /**
13
+ * @member {String} className='Neo.main.addon.PrefixField'
14
+ * @protected
15
+ */
16
+ className: 'Neo.main.addon.PrefixField',
17
+ /**
18
+ * @member {Boolean} singleton=true
19
+ * @protected
20
+ */
21
+ singleton: true,
22
+ /**
23
+ * Remote method access for other workers
24
+ * @member {Object} remote
25
+ * @protected
26
+ */
27
+ remote: {
28
+ app: [
29
+ 'initialize',
30
+ 'destroy',
31
+ 'updateAccept',
32
+ 'updatePattern',
33
+ 'updateSlots',
34
+ ]
35
+ },
36
+
37
+ /**
38
+ * regex to calculate if entered value is acceptable
39
+ * Preset to numbers only
40
+ *
41
+ * @member {regex|null} accept
42
+ */
43
+ accept_: null,
44
+ /**
45
+ * @member {String} pattern=null
46
+ */
47
+ pattern_: null,
48
+ /**
49
+ * Only add a String. A Set will be automatically created
50
+ * @member {String|Set|null} slots=null
51
+ */
52
+ slots_: null
53
+ }
54
+
55
+ destroy() {
56
+
57
+ }
58
+
59
+ elIds = new Map();
60
+
61
+ prev;
62
+
63
+ back = false;
64
+
65
+ /**
66
+ *
67
+ * @param {Object} data
68
+ * @param {String} data.elId
69
+ * @param {String} data.pattern
70
+ * @param {String} data.slots
71
+ * @param {String} data.accept
72
+ */
73
+ initialize(data) {
74
+ const me = this;
75
+
76
+ me.elId = data.elId;
77
+
78
+ const el = me.el = document.getElementById(data.elId),
79
+ pattern = me.pattern = data.pattern,
80
+ slots = me.slots = new Set(data.slots || "_");
81
+
82
+ me.accept = data.accept;
83
+ me.prev = (j => Array.from(pattern, (c, i) => slots.has(c) ? j = i + 1 : j))(0);
84
+ me.first = [...pattern].findIndex(c => slots.has(c));
85
+
86
+ me.addListeners();
87
+ me.addCss();
88
+ }
89
+
90
+ addCss() {
91
+ this.el.classList.add('tiny-prefix-field-input');
92
+ }
93
+
94
+ addListeners() {
95
+ const me = this,
96
+ el = me.el,
97
+ formatFn = me.format.bind(me);
98
+
99
+ el.addEventListener("keypress", me.onKeyDown.bind(me));
100
+ el.addEventListener("input", formatFn);
101
+ el.addEventListener("focusin", formatFn);
102
+ el.addEventListener("focusout", me.onBlur.bind(me));
103
+ }
104
+
105
+ onBlur() {
106
+ const pattern = this.pattern,
107
+ el = this.el;
108
+
109
+ return el.value === pattern && (el.value = "");
110
+ }
111
+
112
+ onKeyDown(event) {
113
+ this.back = (event.key === "Backspace");
114
+ }
115
+
116
+ clean(input) {
117
+ const el = this.el,
118
+ accept = new RegExp(this.accept || "\\d", "g"),
119
+ pattern = this.pattern,
120
+ slots = this.slots;
121
+
122
+ input = input.match(accept) || [];
123
+
124
+ return Array.from(pattern, c =>
125
+ input[0] === c || slots.has(c) ? input.shift() || c : c
126
+ );
127
+ }
128
+
129
+ format() {
130
+ const me = this,
131
+ el = this.el,
132
+ prev = this.prev,
133
+ clean = this.clean.bind(this);
134
+ console.log(el.selectionStart, el.selectionEnd);
135
+ const [i, j] = [el.selectionStart, el.selectionEnd].map(i => {
136
+ i = clean(el.value.slice(0, i)).findIndex(c => me.slots.has(c));
137
+ return i < 0 ? prev[prev.length - 1] : me.back ? prev[i - 1] || me.first : i;
138
+ });
139
+
140
+ el.value = clean(el.value).join``;
141
+ el.setSelectionRange(i, j);
142
+
143
+ this.back = false;
144
+ }
145
+ }
146
+
147
+ let instance = Neo.applyClassConfig(PrefixField);
148
+
149
+ export default instance;
@@ -0,0 +1,194 @@
1
+ import Base from './Base.mjs';
2
+ import Container from '../container/Base.mjs'
3
+ import NeoArray from "../util/Array.mjs";
4
+
5
+ /**
6
+ * Popover usable as tooltip
7
+ * @class Neo.plugin.Popover
8
+ * @extends Neo.plugin.Base
9
+ *
10
+ * @example
11
+ *
12
+ * module : Button,
13
+ * width : 200,
14
+ * text : 'Click Me',
15
+ * plugins: [{
16
+ * module: PopoverPlugin,
17
+ * align : 'bc-tc',
18
+ * items : [{
19
+ * ntype : 'panel',
20
+ * headers: [{
21
+ * dock: 'top',
22
+ * html: 'HEADER'
23
+ * }],
24
+ * items : [{
25
+ * html: 'This is a comment about the button'
26
+ * }]
27
+ * }]
28
+ * }]
29
+ */
30
+ class Popover extends Base {
31
+ /**
32
+ * Valid values for align
33
+ * @member {String[]} alignValues=['bc-tc','tc-bc','tl-tr','tr-tl','cl-cr','cr-cl',null]
34
+ * @protected
35
+ * @static
36
+ *
37
+ * todo add more
38
+ */
39
+ static alignValues = ['bc-tc', 'tc-bc', 'tl-tr', 'tr-tl', 'cl-cr', 'cr-cl', null]
40
+
41
+ static config = {
42
+ /**
43
+ * @member {String} className='Neo.plugin.Popover'
44
+ * @protected
45
+ */
46
+ className: 'Neo.plugin.Popover',
47
+ /**
48
+ * @member {String} ntype='popover'
49
+ * @protected
50
+ */
51
+ ntype: 'plugin-popover',
52
+
53
+
54
+ /**
55
+ * Define popover to popovertarget alignment
56
+ * Defaults to bottom center of popover is aligned to top center of owner
57
+ * @type {string} align='bc-tc'
58
+ */
59
+ align_: 'bc-tc',
60
+ /**
61
+ * Custom cls to add to the owner component
62
+ * @member {String} ownerCls='neo-prefixfield'
63
+ */
64
+ popovertargetCls: 'neo-popover-target',
65
+ /**
66
+ * Custom cls to add to the owner component
67
+ * @member {String} ownerCls='neo-prefixfield'
68
+ */
69
+ popoverBaseCls: 'neo-popover',
70
+ }
71
+
72
+ /**
73
+ * @param {Object} config
74
+ */
75
+ construct(config) {
76
+ let me = this;
77
+
78
+ super.construct(config);
79
+
80
+ me.popoverId = Neo.getId('popover');
81
+
82
+ // prepare owner
83
+ me.preparePopoverTarget();
84
+ me.addPopover();
85
+
86
+ me.addListeners();
87
+ }
88
+
89
+ /**
90
+ * Add listeners
91
+ * @protected
92
+ */
93
+ addListeners() {
94
+ const me = this;
95
+
96
+ me.owner.addDomListeners([
97
+ {mouseover: me.onTargetMouseOver, scope: me},
98
+ {mouseout: me.onTargetMouseOut, scope: me}
99
+ ]);
100
+ }
101
+
102
+ /**
103
+ * Create the popover and add it to the parent component of the owner
104
+ * @protected
105
+ */
106
+ addPopover() {
107
+ const me = this,
108
+ owner = me.owner,
109
+ parent = Neo.get(me.owner.parentId),
110
+ popover = {
111
+ module: Container,
112
+ id : me.popoverId,
113
+
114
+ baseCls: [me.popoverBaseCls],
115
+ cls : [me.align],
116
+
117
+ layout: 'base',
118
+ items : me.items || [],
119
+
120
+ vdom: {
121
+ // Possible Values are auto, manual.
122
+ popover: 'auto',
123
+ anchor : owner.id
124
+ }
125
+ };
126
+
127
+ parent.add(popover);
128
+ }
129
+
130
+ /**
131
+ * Checks if the new value for "align" is valid
132
+ * @param {String|null} value
133
+ * @param {String|null} oldValue
134
+ * @protected
135
+ * @returns {String|null} value
136
+ */
137
+ beforeSetAlign(value, oldValue) {
138
+ return this.testInputValue(value, oldValue, 'alignValues', 'align');
139
+ }
140
+
141
+ /**
142
+ * @event mouseout
143
+ * @param {Object} data
144
+ * @protected
145
+ */
146
+ onTargetMouseOut(data) {
147
+ Neo.main.addon.Popover.hide({id: data.component.id});
148
+ }
149
+
150
+ /**
151
+ * @event mouseover
152
+ * @param {Object} data
153
+ * @protected
154
+ */
155
+ onTargetMouseOver(data) {
156
+ Neo.main.addon.Popover.show({id: data.component.id});
157
+ }
158
+
159
+ /**
160
+ * @protected
161
+ */
162
+ preparePopoverTarget() {
163
+ const me = this,
164
+ target = me.owner,
165
+ targetVdom = target.vdom;
166
+
167
+ target.addCls(me.popovertargetCls);
168
+ targetVdom.popovertarget = me.popoverId;
169
+ }
170
+
171
+ /**
172
+ * Checks if the new value for propertyName is valid
173
+ * @param {String|null} value
174
+ * @param {String|null} oldValue
175
+ * @param {String} validValuesName
176
+ * @param {String} propertyName
177
+ * @protected
178
+ * @returns {String|null} value
179
+ */
180
+ testInputValue(value, oldValue, validValuesName, propertyName) {
181
+ const validValues = this.getStaticConfig(validValuesName);
182
+
183
+ if (!NeoArray.hasItem(validValues, value)) {
184
+ Neo.logError(this.id + ' -> layout: supported values for "' + propertyName + '" are', validValues);
185
+ return oldValue;
186
+ }
187
+
188
+ return value;
189
+ }
190
+ }
191
+
192
+ Neo.applyClassConfig(Popover);
193
+
194
+ export default Popover;
@@ -7,6 +7,20 @@ import VDomUtil from "../util/VDom.mjs";
7
7
  /**
8
8
  * @class Neo.tree.Accordion
9
9
  * @extends Neo.tree.List
10
+ *
11
+ * Accordion Store expects the following fields
12
+ *
13
+ * [
14
+ * iconCls, // can be defined in fields:icon
15
+ * content, // can be defined in fields:text
16
+ * name, // can be defined in fields:header
17
+ *
18
+ * collapsed, // collapsed state for non-leaf-items
19
+ * isLeaf, // defines it item is leaf-item
20
+ * id, // defines item id
21
+ * parentId // leaf or sub-items need a parentId
22
+ * ]
23
+ *
10
24
  */
11
25
  class AccordionTree extends TreeList {
12
26
  static config = {
@@ -25,19 +39,24 @@ class AccordionTree extends TreeList {
25
39
  */
26
40
  baseCls: ['neo-tree-list'],
27
41
  /**
28
- * @member {Boolean} showCollapseExpandAllIcons=true
42
+ * Set to false to hide the initial root item
43
+ * @member {Boolean} firstParentIsVisible=true
29
44
  */
30
- showCollapseExpandAllIcons: false,
45
+ firstParentIsVisible_: true,
46
+ /**
47
+ * Define the field names for the store to show header, text and icon
48
+ * @member {Object} fields={header:'name',icon:'iconCls',text:'content'}
49
+ */
50
+ fields: {
51
+ header: 'name',
52
+ icon : 'iconCls',
53
+ text : 'content'
54
+ },
31
55
  /**
32
56
  * Set to false will auto expand root parent items and disallow collapsing
33
57
  * @member {Boolean} rootParentIsCollapsible=false
34
58
  */
35
59
  rootParentsAreCollapsible_: false,
36
- /**
37
- * Set to false to hide the initial root item
38
- * @member {Boolean} firstParentIsVisible=true
39
- */
40
- firstParentIsVisible_: true,
41
60
  /**
42
61
  * Currently selected item, which is bindable
43
62
  * @member {Record[]|null} selection=null
@@ -50,6 +69,15 @@ class AccordionTree extends TreeList {
50
69
  * bind : {html: data => data.selection[0].name}
51
70
  */
52
71
  selection_: null,
72
+ /**
73
+ * Set to false will hide the icons for all leaf items
74
+ * @member {Boolean} showIcon=true
75
+ */
76
+ showIcon_: true,
77
+ /**
78
+ * @member {Boolean} showCollapseExpandAllIcons=true
79
+ */
80
+ showCollapseExpandAllIcons: false,
53
81
  /**
54
82
  * @member {Object} _vdom
55
83
  */
@@ -114,6 +142,31 @@ class AccordionTree extends TreeList {
114
142
  }
115
143
  }
116
144
 
145
+ /**
146
+ * Called when changing showIcon
147
+ * Changes the display of the icons
148
+ *
149
+ * @param {Boolean} value
150
+ * @param {Boolean} oldValue
151
+ */
152
+ afterSetShowIcon(value, oldValue) {
153
+ const me = this,
154
+ store = me.store,
155
+ hide = !value;
156
+
157
+ store.items.forEach((record) => {
158
+ const itemId = me.getItemId(record[me.getKeyProperty()]),
159
+ vdom = me.getVdomChild(itemId),
160
+ itemVdom = VDomUtil.getByFlag(vdom, 'icon');
161
+
162
+ if (record.isLeaf) {
163
+ itemVdom.removeDom = hide;
164
+ }
165
+ })
166
+
167
+ me.update()
168
+ }
169
+
117
170
  /**
118
171
  * Triggered before the selectionModel config gets changed.
119
172
  * @param {Neo.selection.Model} value
@@ -211,10 +264,11 @@ class AccordionTree extends TreeList {
211
264
  cls,
212
265
  id,
213
266
  cn : [{
267
+ flag : 'icon',
214
268
  tag : 'span',
215
- cls : ['neo-accordion-item-icon', item.iconCls],
216
- id : id + '__item',
217
- removeDom: !item.isLeaf
269
+ cls : ['neo-accordion-item-icon', item[me.fields.icon]],
270
+ id : id + '__icon',
271
+ removeDom: (!item.isLeaf || !me.showIcon)
218
272
  }, {
219
273
  cls : [itemCls + '-content'],
220
274
  id : id + '__item-content',
@@ -224,13 +278,13 @@ class AccordionTree extends TreeList {
224
278
  tag : 'span',
225
279
  cls : [itemCls + '-content-header'],
226
280
  id : id + '__item-content-header',
227
- innerHTML: item.name
281
+ innerHTML: item[me.fields.header]
228
282
  }, {
229
283
  flag : 'content',
230
284
  tag : 'span',
231
285
  cls : [itemCls + '-content-text'],
232
286
  id : id + '__item-content-text',
233
- innerHTML: item.content
287
+ innerHTML: item[me.fields.text]
234
288
  }]
235
289
  }],
236
290
  style: {
@@ -273,7 +327,6 @@ class AccordionTree extends TreeList {
273
327
  const me = this,
274
328
  selectionModel = me.selectionModel,
275
329
  itemId = item.id,
276
- // ! todo make it String
277
330
  id = Number(itemId.split('__')[1]),
278
331
  record = me.store.get(id);
279
332
 
@@ -354,11 +407,11 @@ class AccordionTree extends TreeList {
354
407
 
355
408
  /**
356
409
  * Update a record
357
- * @param {Object} data
358
- * @param {Object[]} data.fields
359
- * @param {Number} data.index
410
+ * @param {Object} data
411
+ * @param {Object[]} data.fields
412
+ * @param {Number} data.index
360
413
  * @param {Neo.data.Model} data.model
361
- * @param {Record} data.record
414
+ * @param {Record} data.record
362
415
  */
363
416
  onStoreRecordChange(data) {
364
417
  let me = this,