neo.mjs 6.0.3 → 6.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.0.3'
23
+ * @member {String} version='6.1.0'
24
24
  */
25
- version: '6.0.3'
25
+ version: '6.1.0'
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.0.3'
23
+ * @member {String} version='6.1.0'
24
24
  */
25
- version: '6.0.3'
25
+ version: '6.1.0'
26
26
  }
27
27
 
28
28
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "6.0.3",
3
+ "version": "6.1.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,4 +1,14 @@
1
1
  .neo-checkboxfield {
2
+ .neo-checkbox-icon {
3
+ color : var(--checkboxfield-color);
4
+ cursor : pointer;
5
+ display : inline-block;
6
+ font-family: var(--checkboxfield-icon-font-family);
7
+ font-size : var(--checkboxfield-icon-font-size);
8
+ margin : var(--checkboxfield-icon-margin);
9
+ width : 20px;
10
+ }
11
+
2
12
  .neo-checkbox-input {
3
13
  margin: 0;
4
14
  width: 0; // using display: none would break the keynav
@@ -14,19 +24,10 @@
14
24
  }
15
25
  }
16
26
 
17
- .neo-checkbox-icon {
18
- color : var(--checkboxfield-color);
19
- cursor : pointer;
20
- display : inline-block;
21
- font-family: var(--checkboxfield-icon-font-family);
22
- font-size : var(--checkboxfield-icon-font-size);
23
- margin : var(--checkboxfield-icon-margin);
24
- width : 20px;
25
- }
26
-
27
27
  .neo-checkbox-label {
28
+ align-items: center;
28
29
  color : var(--textfield-label-color);
29
- display : inline-block;
30
+ display : flex;
30
31
  user-select: none;
31
32
  }
32
33
 
@@ -98,14 +98,33 @@
98
98
  flex : 0 0 1.2rem;
99
99
  }
100
100
 
101
- // The file input and label is only visible in the ready state
102
- &:not(.neo-file-upload-state-ready) {
101
+ // When we have a file selected, the file input and label are hidden
102
+ &:not(.neo-field-empty) {
103
103
  input[type="file"],label {
104
104
  display : none;
105
105
  }
106
106
  }
107
107
 
108
- &.neo-invalid {
108
+ &.neo-field-empty {
109
+ // The input field and its label are visible when no file is selected.
110
+ // It takes up the whole component, and is the only interactive item
111
+ :not(input[type="file"],label) {
112
+ display : none;
113
+ }
114
+ input::file-selector-button {
115
+ position : absolute;
116
+ border : 0 none;
117
+ margin : 0;
118
+ top : 0;
119
+ left : 0;
120
+ right : 0;
121
+ bottom : 0;
122
+ background-color : var(--fileuploadfield-background-color);
123
+ color : var(--fileuploadfield-color);
124
+ }
125
+ }
126
+
127
+ &.neo-invalid:not(.neo-field-empty) {
109
128
  .neo-file-upload-error-message {
110
129
  display : initial;
111
130
  }
@@ -233,25 +252,6 @@
233
252
  }
234
253
  }
235
254
 
236
- .neo-file-upload-state-ready:not(.neo-invalid) {
237
- // Only the input field and its label is visible when in ready state
238
- // It takes up the whole component, and is the only interactive item
239
- :not(input[type="file"],label) {
240
- display : none;
241
- }
242
- input::file-selector-button {
243
- position : absolute;
244
- border : 0 none;
245
- margin : 0;
246
- top : 0;
247
- left : 0;
248
- right : 0;
249
- bottom : 0;
250
- background-color : var(--fileuploadfield-background-color);
251
- color : var(--fileuploadfield-color);
252
- }
253
- }
254
-
255
255
  .neo-file-upload-body {
256
256
  flex : 1 1 0%;
257
257
  display : flex;
@@ -1,75 +1,86 @@
1
1
  .neo-switchfield {
2
- @keyframes switch-off-transform {
3
- 0% {transform: translate(100%) scaleX(1)}
4
- 25% {transform: translate(100%) scaleX(1.33)}
5
- 100% {transform: translate(0) scaleX(1)}
6
- }
7
-
8
- @keyframes switch-on-transform {
9
- 0% {transform: translate(0) scaleX(1)}
10
- 25% {transform: translate(0) scaleX(1.33)}
11
- 100% {transform: translate(100%) scaleX(1)}
12
- }
13
-
14
2
  .neo-switchfield-input {
15
- background-clip : padding-box;
16
- background-color: var(--switchfield-background-color);
17
- border-color : transparent;
18
- border-radius : 1.25em;
19
- color : var(--switchfield-inactive-color);
20
- display : inline-block;
21
- font-size : 1.5em;
22
- height : 1.5em;
3
+ background-color: var(--switchfield-color-off-idle);
4
+ border-radius : 999px;
5
+ height : var(--switchfield-height);
6
+ margin : 0;
23
7
  position : relative;
24
- transition : transform 0.25s linear 0.25s;
25
- vertical-align : middle;
26
- width : 3em;
8
+ width : var(--switchfield-width);
27
9
 
28
10
  -webkit-appearance : none;
29
11
  -webkit-tap-highlight-color: transparent;
30
12
 
31
- &::after {
32
- content : "\f00c\f00d";
33
- font-family : "Font Awesome 6 Free";
34
- font-size : 1.25rem;
35
- font-weight : 900;
36
- left : 0.5em;
37
- letter-spacing: 1em;
38
- line-height : 1.5;
39
- position : absolute;
40
- top : 0.15em;
41
- z-index : 1;
13
+ &:after,
14
+ &:before {
15
+ content : "";
16
+ position: absolute;
42
17
  }
43
18
 
44
- &::before {
45
- animation : switch-off-transform 0.25s ease-out forwards;
46
- background-clip : padding-box;
47
- background-color: var(--switchfield-thumb-color);
48
- border : 0.125em solid transparent;
49
- border-radius : 100%;
50
- bottom : 0;
51
- content : "";
19
+ &:before {
20
+ background-color: var(--switchfield-color-knob);
21
+ border-radius : 50%;
22
+ height : var(--switchfield-knobsize);
52
23
  left : 0;
53
- position : absolute;
54
- right : 50%;
55
- top : 0;
56
- transform-origin: right center;
57
- z-index : 2;
24
+ top : 1.5px;
25
+ transition : transform 150ms ease-out;
26
+ width : var(--switchfield-knobsize);
27
+ }
28
+
29
+ &:after {
30
+ border : 1px solid var(--switchfield-color-knob);
31
+ border-radius: 50%;
32
+ bottom : 7px;
33
+ height : 8px;
34
+ right : 7px;
35
+ width : 8px;
58
36
  }
59
37
 
60
38
  &:checked {
61
- background-color: var(--switchfield-checked);
62
- color : var(--switchfield-inactive-color);
39
+ background-color: var(--switchfield-color-on-idle);
63
40
 
64
- &::before {
65
- animation : switch-on-transform 0.25s ease-out forwards;
66
- transform-origin: left center;
41
+ &:before {
42
+ transform: translateX(100%);
43
+ }
44
+
45
+ &:after {
46
+ background-color: var(--switchfield-color-knob);
47
+ border : 0;
48
+ bottom : 8px;
49
+ left : 9px;
50
+ right : auto;
51
+ width : 1px;
52
+ }
53
+
54
+ &:hover {
55
+ background-color: var(--switchfield-color-on-hovered);
67
56
  }
68
57
  }
69
58
 
70
59
  &:focus {
71
- color : var(--switchfield-active-color);
72
- outline: none;
60
+ outline: var(--switchfield-borderwidth-focused) solid var(--switchfield-bordercolor-focused);
61
+ }
62
+
63
+ &:hover {
64
+ background-color: var(--switchfield-color-off-hovered);
65
+ }
66
+ }
67
+
68
+ .neo-checkbox-value-label {
69
+ margin-left: 1em;
70
+ }
71
+ }
72
+
73
+ .neo-disabled {
74
+ .neo-switchfield,
75
+ &.neo-switchfield {
76
+ opacity: var(--switchfield-opacity-disabled);
77
+
78
+ .neo-switchfield-input {
79
+ background-color: var(--switchfield-color-off-disabled);
80
+
81
+ &:checked {
82
+ background-color: var(--switchfield-color-on-disabled);
83
+ }
73
84
  }
74
85
  }
75
86
  }
@@ -1,7 +1,15 @@
1
1
  :root .neo-theme-dark { // .neo-switchfield
2
- --switchfield-active-color : white;
3
- --switchfield-background-color: darkgrey;
4
- --switchfield-checked : green;
5
- --switchfield-inactive-color : grey;
6
- --switchfield-thumb-color : white;
2
+ --switchfield-bordercolor-focused: #57B2EF;
3
+ --switchfield-borderwidth-focused: 4px;
4
+ --switchfield-color-knob : #fff;
5
+ --switchfield-color-off-disabled : #BDBDBD;
6
+ --switchfield-color-off-hovered : #9E9E9E;
7
+ --switchfield-color-off-idle : #BDBDBD;
8
+ --switchfield-color-on-disabled : #005A96;
9
+ --switchfield-color-on-hovered : #004C7E;
10
+ --switchfield-color-on-idle : #005A96;
11
+ --switchfield-height : 24px;
12
+ --switchfield-knobsize : 20px;
13
+ --switchfield-opacity-disabled : .5;
14
+ --switchfield-width : 40px;
7
15
  }
@@ -1,7 +1,15 @@
1
1
  :root .neo-theme-light { // .neo-switchfield
2
- --switchfield-active-color : white;
3
- --switchfield-background-color: darkgrey;
4
- --switchfield-checked : green;
5
- --switchfield-inactive-color : grey;
6
- --switchfield-thumb-color : white;
2
+ --switchfield-bordercolor-focused: #57B2EF;
3
+ --switchfield-borderwidth-focused: 4px;
4
+ --switchfield-color-knob : #fff;
5
+ --switchfield-color-off-disabled : #BDBDBD;
6
+ --switchfield-color-off-hovered : #9E9E9E;
7
+ --switchfield-color-off-idle : #BDBDBD;
8
+ --switchfield-color-on-disabled : #005A96;
9
+ --switchfield-color-on-hovered : #004C7E;
10
+ --switchfield-color-on-idle : #005A96;
11
+ --switchfield-height : 24px;
12
+ --switchfield-knobsize : 20px;
13
+ --switchfield-opacity-disabled : .5;
14
+ --switchfield-width : 40px;
7
15
  }
@@ -236,12 +236,12 @@ const DefaultConfig = {
236
236
  useVdomWorker: true,
237
237
  /**
238
238
  * buildScripts/injectPackageVersion.mjs will update this value
239
- * @default '6.0.3'
239
+ * @default '6.1.0'
240
240
  * @memberOf! module:Neo
241
241
  * @name config.version
242
242
  * @type String
243
243
  */
244
- version: '6.0.3'
244
+ version: '6.1.0'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
package/src/core/Util.mjs CHANGED
@@ -42,7 +42,7 @@ class Util extends Base {
42
42
  * @returns {Boolean|String} Returns false for non string inputs
43
43
  */
44
44
  static capitalize(value) {
45
- return Util.isString(value) && value[0].toUpperCase() + value.slice(1);
45
+ return value[0].toUpperCase() + value.slice(1)
46
46
  }
47
47
 
48
48
  /**
@@ -51,10 +51,6 @@ class Util extends Base {
51
51
  * @returns {Object} The camelcase styles object
52
52
  */
53
53
  static createStyleObject(string) {
54
- if (!string) {
55
- return null;
56
- }
57
-
58
54
  let parts;
59
55
 
60
56
  // split(';') does fetch semicolons inside brackets
@@ -74,10 +70,11 @@ class Util extends Base {
74
70
  parts[0] = parts[0].replace(/-([a-z])/g, (str, letter) => {
75
71
  return letter.toUpperCase();
76
72
  });
77
- obj[parts[0]] = parts[1];
73
+
74
+ obj[parts[0]] = parts[1]
78
75
  }
79
76
  return obj;
80
- }, {});
77
+ }, {})
81
78
  }
82
79
 
83
80
  /**
@@ -90,11 +87,11 @@ class Util extends Base {
90
87
 
91
88
  Object.entries(styles).forEach(([key, value]) => {
92
89
  if (value !== undefined && value !== null) {
93
- style += Util.decamel(key) + ':' + value + ';';
90
+ style += Util.decamel(key) + ':' + value + ';'
94
91
  }
95
92
  });
96
93
 
97
- return style;
94
+ return style
98
95
  }
99
96
 
100
97
  /**
@@ -104,7 +101,7 @@ class Util extends Base {
104
101
  * @returns {String} The lowercase output
105
102
  */
106
103
  static decamel(value) {
107
- return value.replace(Util.decamelRegEx, '$1-$2').toLowerCase();
104
+ return value.replace(Util.decamelRegEx, '$1-$2').toLowerCase()
108
105
  }
109
106
 
110
107
  /**
@@ -122,7 +119,7 @@ class Util extends Base {
122
119
  * @returns {Boolean}
123
120
  */
124
121
  static isBoolean(value) {
125
- return typeof value === 'boolean';
122
+ return typeof value === 'boolean'
126
123
  }
127
124
 
128
125
  /**
@@ -131,7 +128,7 @@ class Util extends Base {
131
128
  * @returns {Boolean}
132
129
  */
133
130
  static isDefined(value) {
134
- return typeof value !== 'undefined';
131
+ return typeof value !== 'undefined'
135
132
  }
136
133
 
137
134
  /**
@@ -141,26 +138,26 @@ class Util extends Base {
141
138
  */
142
139
  static isEmpty(value) {
143
140
  if (value === null || value === undefined) {
144
- return true;
141
+ return true
145
142
  }
146
143
 
147
144
  if (Array.isArray(value)) {
148
- return value.length === 0;
145
+ return value.length === 0
149
146
  }
150
147
 
151
148
  if (value instanceof Date) {
152
- return false;
149
+ return false
153
150
  }
154
151
 
155
152
  if (Util.isObject(value)) {
156
- return Object.keys(value).length === 0;
153
+ return Object.keys(value).length === 0
157
154
  }
158
155
 
159
156
  if (Util.isString(value)) {
160
- return value === '';
157
+ return value === ''
161
158
  }
162
159
 
163
- return false;
160
+ return false
164
161
  }
165
162
 
166
163
  /**
@@ -169,7 +166,7 @@ class Util extends Base {
169
166
  * @returns {Boolean}
170
167
  */
171
168
  static isFunction(value) {
172
- return typeof value === 'function';
169
+ return typeof value === 'function'
173
170
  }
174
171
 
175
172
  /**
@@ -178,7 +175,7 @@ class Util extends Base {
178
175
  * @returns {Boolean}
179
176
  */
180
177
  static isNumber(value){
181
- return typeof value === 'number' && isFinite(value);
178
+ return typeof value === 'number' && isFinite(value)
182
179
  }
183
180
 
184
181
  /**
@@ -187,7 +184,7 @@ class Util extends Base {
187
184
  * @returns {Boolean}
188
185
  */
189
186
  static isObject(value) {
190
- return value !== null && typeof value === 'object' && !Array.isArray(value);
187
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
191
188
  }
192
189
 
193
190
  /**
@@ -196,7 +193,7 @@ class Util extends Base {
196
193
  * @returns {Boolean}
197
194
  */
198
195
  static isString(value) {
199
- return typeof value === 'string';
196
+ return typeof value === 'string'
200
197
  }
201
198
 
202
199
  /**
@@ -210,14 +207,14 @@ class Util extends Base {
210
207
  let len;
211
208
 
212
209
  if (!iterable || !(len = iterable.length)) {
213
- return [];
210
+ return []
214
211
  }
215
212
 
216
213
  if (typeof iterable === 'string') {
217
- return iterable.split('');
214
+ return iterable.split('')
218
215
  }
219
216
 
220
- return Array.prototype.slice.call(iterable, start || 0, end || len);
217
+ return Array.prototype.slice.call(iterable, start || 0, end || len)
221
218
  }
222
219
  }
223
220
 
@@ -132,7 +132,7 @@ class FileUpload extends Base {
132
132
  ]
133
133
  },
134
134
 
135
- cls : [],
135
+ cls : ['neo-field-empty'],
136
136
 
137
137
  /**
138
138
  * An Object containing a default set of headers to be passed to the server on every HTTP request.
@@ -370,9 +370,15 @@ class FileUpload extends Base {
370
370
  }
371
371
 
372
372
  async clear() {
373
- const me = this;
373
+ const
374
+ me = this,
375
+ { cls } = me;
376
+
377
+ NeoArray.add(cls, 'neo-field-empty');
378
+ me.cls = cls;
374
379
 
375
380
  me.vdom.cn[3] = {
381
+ id : `${me.id}-input`,
376
382
  cls : 'neo-file-upload-input',
377
383
  tag : 'input',
378
384
  type : 'file',
@@ -380,6 +386,7 @@ class FileUpload extends Base {
380
386
  };
381
387
  me.state = 'ready';
382
388
  me.error = '';
389
+ me.file = me.document = null;
383
390
 
384
391
  // We have to wait for the DOM to have changed, and the input field to be visible
385
392
  await new Promise(resolve => setTimeout(resolve, 100));
@@ -393,10 +400,16 @@ class FileUpload extends Base {
393
400
  onInputValueChange({ files }) {
394
401
  const
395
402
  me = this,
396
- { types } = me,
403
+ {
404
+ types,
405
+ cls
406
+ } = me,
397
407
  body = me.vdom.cn[1];
398
408
 
399
409
  if (files.length) {
410
+ NeoArray.remove(cls, 'neo-field-empty');
411
+ me.cls = cls;
412
+
400
413
  const
401
414
  file = files.item(0),
402
415
  pointPos = file.name.lastIndexOf('.'),
@@ -434,6 +447,7 @@ class FileUpload extends Base {
434
447
  headers = { ...me.headers };
435
448
 
436
449
  // Show the action button
450
+ me.file = file;
437
451
  me.state = 'starting';
438
452
 
439
453
  // We have to wait for the DOM to have changed, and the action button to be visible
@@ -642,9 +656,12 @@ class FileUpload extends Base {
642
656
  afterSetDocument(document) {
643
657
  if (document) {
644
658
  const
645
- me = this;
659
+ me = this,
660
+ { cls } = me;
661
+
662
+ NeoArray.remove(cls, 'neo-field-empty');
663
+ me.cls = cls;
646
664
 
647
- me.preExistingDocument = true;
648
665
  me.documentId = document.id;
649
666
  me.fileSize = me.formatSize(document.size);
650
667
  me.vdom.cn[1].cn[0].innerHTML = document.fileName;
@@ -693,8 +710,7 @@ class FileUpload extends Base {
693
710
  status.innerHTML = me.fileSize;
694
711
  break;
695
712
  case 'not-downloadable':
696
- status.innerHTML = me.preExistingDocument ?
697
- me.fileSize : `${me.successfullyUploaded} \u2022 ${me.fileSize}`;
713
+ status.innerHTML = me.document ? me.fileSize : `${me.successfullyUploaded} \u2022 ${me.fileSize}`;
698
714
  break;
699
715
  case 'deleted':
700
716
  status.innerHTML = me.fileWasDeleted;
@@ -708,6 +724,7 @@ class FileUpload extends Base {
708
724
 
709
725
  NeoArray.remove(cls, 'neo-file-upload-state-' + oldValue);
710
726
  NeoArray.add(cls, 'neo-file-upload-state-' + value);
727
+ NeoArray[me.file || me.document ? 'remove' : 'add', 'neo-field-empty'];
711
728
  me.cls = cls;
712
729
  }
713
730
 
@@ -1,5 +1,4 @@
1
1
  import CheckBox from './CheckBox.mjs';
2
- import NeoArray from '../../util/Array.mjs';
3
2
 
4
3
  /**
5
4
  * @class Neo.form.field.Switch
@@ -124,6 +124,25 @@ class AccordionTree extends TreeList {
124
124
  return ClassSystemUtil.beforeSetInstance(value, TreeAccordionModel);
125
125
  }
126
126
 
127
+ /**
128
+ * Remove all items from the accordion
129
+ * If you do not need to update the view after clearing, set `withUpdate = false`
130
+ *
131
+ * @param {Boolean} [withUpdate=true]
132
+ */
133
+ clear(withUpdate = true) {
134
+ delete this.getVdomRoot().cn[0].cn
135
+
136
+ if (withUpdate) this.update();
137
+ }
138
+
139
+ /**
140
+ * Remove all items from the selection
141
+ */
142
+ clearSelection() {
143
+ this.selectionModel.deselectAll();
144
+ }
145
+
127
146
  /**
128
147
  * @param {String} [parentId] The parent node
129
148
  * @param {Object} [vdomRoot] The vdom template root for the current sub tree
@@ -304,24 +323,57 @@ class AccordionTree extends TreeList {
304
323
  let me = this,
305
324
  listenerId;
306
325
 
326
+ me.clear(false);
327
+
307
328
  if (!me.mounted && me.rendering) {
308
329
  listenerId = me.on('mounted', () => {
309
330
  me.un('mounted', listenerId);
310
331
  me.createItems(null, me.getListItemsRoot(), 0);
311
332
  me.timeout(0).then(() => {
312
- me.update()
333
+ me.update();
313
334
  });
314
335
  });
315
336
  } else {
316
337
  me.createItems(null, me.getListItemsRoot(), 0);
317
338
  me.timeout(0).then(() => {
318
- me.update()
339
+ me.update();
319
340
  });
320
341
  }
321
342
  }
322
343
 
323
344
  onStoreRecordChange() {
324
345
  }
346
+
347
+ /**
348
+ * Set the selection either bei record id or record.
349
+ * You can pass a record or a recordId as value
350
+ *
351
+ * @param {Record|Record[]|Number|Number[]|String|String[]} value
352
+ */
353
+ setSelection(value) {
354
+ if (value === null) {
355
+ this.clearSelection();
356
+ return;
357
+ }
358
+
359
+ // In case you pass in an array use only the first item
360
+ if (Neo.isArray(value)) value = value[0];
361
+
362
+ const me = this;
363
+ let recordKeyProperty, elId;
364
+
365
+ if (Neo.isObject(value)) {
366
+ // Record
367
+ recordKeyProperty = value[me.getKeyProperty()];
368
+ } else {
369
+ // RecordId
370
+ recordKeyProperty = value;
371
+ }
372
+
373
+ elId = me.getItemId(recordKeyProperty);
374
+
375
+ me.selectionModel.selectAndScrollIntoView(elId);
376
+ }
325
377
  }
326
378
 
327
379
  Neo.applyClassConfig(AccordionTree);