neo.mjs 6.0.0 → 6.0.2

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.0'
23
+ * @member {String} version='6.0.2'
24
24
  */
25
- version: '6.0.0'
25
+ version: '6.0.2'
26
26
  }
27
27
 
28
28
  /**
@@ -390,31 +390,18 @@ class MainContainerController extends ComponentController {
390
390
  logo.vdom.src = logoPath + (theme === 'neo-theme-dark' ? 'covid_logo_dark.jpg' : 'covid_logo_light.jpg');
391
391
  logo.update();
392
392
 
393
+ cls = [...component.cls];
393
394
 
394
- if (Neo.config.useCssVars) {
395
- cls = [...component.cls];
396
-
397
- component.cls.forEach(item => {
398
- if (item.includes('neo-theme')) {
399
- NeoArray.remove(cls, item);
400
- }
401
- });
395
+ component.cls.forEach(item => {
396
+ if (item.includes('neo-theme')) {
397
+ NeoArray.remove(cls, item);
398
+ }
399
+ });
402
400
 
403
- NeoArray.add(cls, theme);
404
- component.cls = cls;
401
+ NeoArray.add(cls, theme);
402
+ component.cls = cls;
405
403
 
406
- button.set({
407
- iconCls,
408
- text: buttonText
409
- });
410
- } else {
411
- Neo.main.addon.Stylesheet.swapStyleSheet({
412
- href: href,
413
- id : 'neo-theme'
414
- }).then(data => {
415
- button.text = buttonText;
416
- });
417
- }
404
+ button.set({iconCls, text: buttonText});
418
405
 
419
406
  if (mapView) {
420
407
  mapView.mapboxStyle = mapViewStyle;
@@ -580,42 +580,23 @@ class MainContainerController extends ComponentController {
580
580
  logo.vdom.src = logoPath + (theme === 'neo-theme-dark' ? 'covid_logo_dark.jpg' : 'covid_logo_light.jpg');
581
581
  logo.update();
582
582
 
583
+ [component.appName, ...me.connectedApps].forEach(appName => {
584
+ component = me.getMainView(appName);
583
585
 
584
- if (Neo.config.useCssVars) {
585
- [component.appName, ...me.connectedApps].forEach(appName => {
586
- component = me.getMainView(appName);
586
+ cls = [...component.cls];
587
587
 
588
- cls = [...component.cls];
589
-
590
- component.cls.forEach(item => {
591
- if (item.includes('neo-theme')) {
592
- NeoArray.remove(cls, item);
593
- }
594
- });
595
-
596
- NeoArray.add(cls, theme);
597
- component.cls = cls;
598
- });
599
-
600
- button.set({
601
- iconCls,
602
- text: buttonText
603
- });
604
- } else {
605
- [component.appName, ...me.connectedApps].forEach(appName => {
606
- Neo.main.addon.Stylesheet.swapStyleSheet({
607
- appName,
608
- href,
609
- id: 'neo-theme'
610
- });
588
+ component.cls.forEach(item => {
589
+ if (item.includes('neo-theme')) {
590
+ NeoArray.remove(cls, item)
591
+ }
611
592
  });
612
- }
613
593
 
614
- button.set({
615
- iconCls,
616
- text: buttonText
594
+ NeoArray.add(cls, theme);
595
+ component.cls = cls;
617
596
  });
618
597
 
598
+ button.set({iconCls, text: buttonText});
599
+
619
600
  if (mapView) {
620
601
  mapView.mapboxStyle = mapViewStyle;
621
602
  } else {
@@ -192,20 +192,18 @@ class MainContainerController extends Component {
192
192
  theme = 'neo-theme-dark';
193
193
  }
194
194
 
195
- if (Neo.config.useCssVars) {
196
- cls = [...view.cls];
195
+ cls = [...view.cls];
197
196
 
198
- view.cls.forEach((item, index) => {
199
- if (item.includes('neo-theme')) {
200
- NeoArray.remove(cls, item);
201
- }
202
- });
197
+ view.cls.forEach(item => {
198
+ if (item.includes('neo-theme')) {
199
+ NeoArray.remove(cls, item)
200
+ }
201
+ });
203
202
 
204
- NeoArray.add(cls, theme);
205
- view.cls = cls;
203
+ NeoArray.add(cls, theme);
204
+ view.cls = cls;
206
205
 
207
- button.text = buttonText;
208
- }
206
+ button.text = buttonText
209
207
  }
210
208
 
211
209
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.0.0'
23
+ * @member {String} version='6.0.2'
24
24
  */
25
- version: '6.0.0'
25
+ version: '6.0.2'
26
26
  }
27
27
 
28
28
  /**
@@ -0,0 +1,151 @@
1
+ import ConfigurationViewport from '../ConfigurationViewport.mjs';
2
+
3
+ import PrefixPlugin from '../../src/plugin/PrefixField.mjs';
4
+ import SelectField from '../../src/form/field/Select.mjs';
5
+ import TextField from '../../src/form/field/Text.mjs';
6
+
7
+ /**
8
+ * @class Neo.examples.fieldWithPrefix.MainContainer
9
+ * @extends Neo.examples.ConfigurationViewport
10
+ */
11
+ class MainContainer extends ConfigurationViewport {
12
+ static config = {
13
+ className : 'Neo.examples.fieldWithPrefix.MainContainer',
14
+ autoMount : true,
15
+ configItemLabelWidth: 100,
16
+ configItemWidth : 230,
17
+ layout : {ntype: 'hbox', align: 'stretch'},
18
+ cls : ['examples-container-accordion']
19
+ }
20
+
21
+ onPluginConfigChange(config, opts) {
22
+ const textfield = this.exampleComponent.items[0],
23
+ plugin = textfield.getPlugin({flag: 'prefix'});
24
+
25
+ if (config === 'accept') {
26
+ plugin.accept = opts.record.value;
27
+ } else {
28
+ plugin[config] = opts.value;
29
+ textfield.value = '';
30
+ }
31
+ }
32
+
33
+ createConfigurationComponents() {
34
+ let me = this,
35
+ textfield = me.exampleComponent.items[0],
36
+ plugin = textfield.plugins[0];
37
+
38
+ return [{
39
+ module : TextField,
40
+ clearable: true,
41
+ labelText: 'pattern',
42
+ listeners: {change: me.onPluginConfigChange.bind(me, 'pattern')},
43
+ value : plugin.pattern,
44
+ style : {marginTop: '10px'}
45
+ }, {
46
+ module : TextField,
47
+ clearable: true,
48
+ labelText: 'slots',
49
+ listeners: {change: me.onPluginConfigChange.bind(me, 'slots')},
50
+ value : '_',
51
+ style : {marginTop: '10px'}
52
+ }, {
53
+ module: SelectField,
54
+ store : {
55
+ model: {fields: [{name: 'id'}, {name: 'name'}, {name: 'value'}]},
56
+ data : [
57
+ {id: '0', name: 'empty=/\\d/', value: null},
58
+ {id: '1', name: '[0-9]', value: '[0-9]'},
59
+ {id: '2', name: '[A-H]', value: '[A-H]'},
60
+ {id: '3', name: '/\\w/', value: /\w/},
61
+ {id: '4', name: '/\\d/', value: /\d/},
62
+ {id: '5', name: '[0-9a-f]', value: '[0-9a-f]'}
63
+ ]
64
+ },
65
+
66
+ value : '4',
67
+ displayField: 'name',
68
+ valueField : 'value',
69
+
70
+ clearable: true,
71
+ labelText: 'accept',
72
+ listeners: {change: me.onPluginConfigChange.bind(me, 'accept')},
73
+ style : {marginTop: '10px'}
74
+ }];
75
+ }
76
+
77
+ /**
78
+ * @returns {*}
79
+ */
80
+ createExampleComponent() {
81
+ return Neo.ntype({
82
+ ntype : 'container',
83
+ width : 350,
84
+ cls : ['example-fieldWithPrefix'],
85
+ layout: {ntype: 'vbox', align: 'stretch'},
86
+ items : [{
87
+ module : TextField,
88
+ labelText: 'Phone Number',
89
+ plugins : [
90
+ {
91
+ module : PrefixPlugin,
92
+ flag : 'prefix',
93
+ pattern: '+1 (___) ___-___-____',
94
+ slots : '_'
95
+ }
96
+ ]
97
+ }, {
98
+ module : TextField,
99
+ labelText: '[0-9] Date',
100
+ plugins : [
101
+ {
102
+ module : PrefixPlugin,
103
+ flag : 'prefix',
104
+ pattern: 'dd/mm/yyyy hh:mm',
105
+ slots : 'dmyh'
106
+ }
107
+ ]
108
+ }, {
109
+ module : TextField,
110
+ labelText: '[A-H] MAC Adress',
111
+ plugins : [
112
+ {
113
+ module : PrefixPlugin,
114
+ flag : 'prefix',
115
+ pattern: 'XX:XX:XX:XX:XX:XX',
116
+ slots : 'X',
117
+ accept : '[A-H]'
118
+ }
119
+ ]
120
+ }, {
121
+ module : TextField,
122
+ labelText: '/\\w/ Alphanumeric',
123
+ plugins : [
124
+ {
125
+ module : PrefixPlugin,
126
+ flag : 'prefix',
127
+ pattern: '__-__-__-____',
128
+ slots : '_',
129
+ accept : /\w/
130
+ }
131
+ ]
132
+ }, {
133
+ module : TextField,
134
+ labelText: '/\\d/ Credit Card',
135
+ plugins : [
136
+ {
137
+ module : PrefixPlugin,
138
+ flag : 'prefix',
139
+ pattern: '.... .... .... ....',
140
+ slots : '.',
141
+ accept : /\d/
142
+ }
143
+ ]
144
+ }]
145
+ })
146
+ }
147
+ }
148
+
149
+ Neo.applyClassConfig(MainContainer);
150
+
151
+ export default MainContainer;
@@ -0,0 +1,6 @@
1
+ import MainContainer from './MainContainer.mjs';
2
+
3
+ export const onStart = () => Neo.app({
4
+ mainView: MainContainer,
5
+ name : 'Neo.examples.fieldWithPrefix'
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>Field with Prefix</title>
7
+ </head>
8
+ <body>
9
+ <script src="../../src/MicroLoader.mjs" type="module"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,7 @@
1
+ {
2
+ "appPath" : "examples/fieldWithPrefix/app.mjs",
3
+ "basePath" : "../../",
4
+ "environment": "development",
5
+ "mainPath" : "./Main.mjs",
6
+ "themes" : ["neo-theme-dark", "neo-theme-light"]
7
+ }
@@ -105,7 +105,7 @@ class MainContainer extends ConfigurationViewport {
105
105
  id : 2,
106
106
  fileName : 'test.pdf',
107
107
  size : 10664885,
108
- status : 'UN_DOWNLOADABLE'
108
+ status : 'AVAILABLE'
109
109
  },
110
110
  uploadUrl : 'http://127.0.0.1:3000/file-upload-test',
111
111
  documentStatusUrl : 'http://127.0.0.1:3000/document-status-not-downloadable?documentId={documentId}',
@@ -41,7 +41,7 @@ app.get('/document-status-downloadable', async(req, res) => {
41
41
 
42
42
  app.get('/document-status-not-downloadable', async(req, res) => {
43
43
  res.set('Content-Type', 'application/json');
44
- res.send('{"status":"UN_DOWNLOADABLE","fileName":"testfile.pdf","size":9653413}');
44
+ res.send('{"status":"AVAILABLE","fileName":"testfile.pdf","size":9653413}');
45
45
  });
46
46
 
47
47
  app.get('/document-status-non-existent', async(req, res) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "6.0.0",
3
+ "version": "6.0.2",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -0,0 +1,25 @@
1
+ .example-fieldWithPrefix {
2
+ .neo-prefixfield {
3
+ .neo-input-wrapper {
4
+ position: relative;
5
+ overflow: visible;
6
+
7
+ &::before {
8
+ position: absolute;
9
+ content: "";
10
+ width: 8px;
11
+ height: 8px;
12
+ background-color: indianred;
13
+ border-radius: 50%;
14
+ top: 2px;
15
+ left: -12px;
16
+ }
17
+ }
18
+
19
+ &.neo-focus {
20
+ .neo-input-wrapper::before {
21
+ background-color: darkseagreen;
22
+ }
23
+ }
24
+ }
25
+ }
@@ -15,7 +15,7 @@
15
15
  position : relative;
16
16
  display : flex;
17
17
  align-items : center;
18
- padding : 0.5rem;
18
+ padding : 0.4rem;
19
19
  gap : 0.5rem;
20
20
  border : 1px solid var(--fileuploadfield-border-color);
21
21
  border-radius : 2px;
@@ -73,6 +73,19 @@
73
73
  }
74
74
  }
75
75
 
76
+ .neo-file-upload-label {
77
+ position : absolute;
78
+ top : 0;
79
+ left : 0;
80
+ right : 0;
81
+ bottom : 0;
82
+ display : flex;
83
+ align-items : center;
84
+ justify-content : center;
85
+ background-color : var(--fileuploadfield-background-color);
86
+ cursor : pointer;
87
+ }
88
+
76
89
  .neo-file-upload-filename {
77
90
  font-weight : bold;
78
91
  }
@@ -82,11 +95,12 @@
82
95
  overflow : hidden;
83
96
  text-overflow : ellipsis;
84
97
  color : inherit; // For when it becomes a link
98
+ flex : 0 0 1.2rem;
85
99
  }
86
100
 
87
- // The file input is only visible in the ready state
101
+ // The file input and label is only visible in the ready state
88
102
  &:not(.neo-file-upload-state-ready) {
89
- input[type="file"] {
103
+ input[type="file"],label {
90
104
  display : none;
91
105
  }
92
106
  }
@@ -217,9 +231,9 @@
217
231
  }
218
232
 
219
233
  .neo-file-upload-state-ready {
220
- // Only the input field is visible when in ready state
234
+ // Only the input field and its label is visible when in ready state
221
235
  // It takes up the whole component, and is the only interactive item
222
- :not(input[type="file"]) {
236
+ :not(input[type="file"],label) {
223
237
  display : none;
224
238
  }
225
239
  input::file-selector-button {
@@ -232,7 +246,6 @@
232
246
  bottom : 0;
233
247
  background-color : var(--fileuploadfield-background-color);
234
248
  color : var(--fileuploadfield-color);
235
- cursor : pointer;
236
249
  }
237
250
  }
238
251
 
@@ -240,10 +253,10 @@
240
253
  flex : 1 1 0%;
241
254
  display : flex;
242
255
  flex-flow : column nowrap;
243
- line-height : 1;
244
256
  gap : 0.2rem;
245
257
  overflow : hidden;
246
- align-items : flex-start;}
258
+ align-items : flex-start;
259
+ }
247
260
 
248
261
  .neo-file-upload-error-message {
249
262
  display : none;
@@ -0,0 +1,5 @@
1
+ .neo-prefixfield {
2
+ input.neo-prefixfield-input {
3
+ font-family: 'Andalé Mono', Lucida, Monaco, Courier Menlo, 'Courier New', Courier, monospace;
4
+ }
5
+ }
@@ -153,7 +153,6 @@ const DefaultConfig = {
153
153
  renderCountDeltas: false,
154
154
  /**
155
155
  * Add themes you want to use here. The first theme will get applied.
156
- * If config.useCssVars === true, other theme variables will get included as well
157
156
  * @default ['neo-theme-light', 'neo-theme-dark']
158
157
  * @memberOf! module:Neo
159
158
  * @name config.themes
@@ -168,14 +167,6 @@ const DefaultConfig = {
168
167
  * @type Boolean
169
168
  */
170
169
  unitTestMode: false,
171
- /**
172
- * Flag if CSS variable based stylesheets are in use (important for switching themes)
173
- * @default true
174
- * @memberOf! module:Neo
175
- * @name config.useCssVars
176
- * @type Boolean
177
- */
178
- useCssVars: true,
179
170
  /**
180
171
  * Experimental flag if an offscreen canvas worker should get created.
181
172
  * @default false
@@ -245,12 +236,12 @@ const DefaultConfig = {
245
236
  useVdomWorker: true,
246
237
  /**
247
238
  * buildScripts/injectPackageVersion.mjs will update this value
248
- * @default '6.0.0'
239
+ * @default '6.0.2'
249
240
  * @memberOf! module:Neo
250
241
  * @name config.version
251
242
  * @type String
252
243
  */
253
- version: '6.0.0'
244
+ version: '6.0.2'
254
245
  };
255
246
 
256
247
  Object.assign(DefaultConfig, {
package/src/Main.mjs CHANGED
@@ -50,6 +50,7 @@ class Main extends core.Base {
50
50
  'editRoute',
51
51
  'getByPath',
52
52
  'getWindowData',
53
+ 'importAddon',
53
54
  'redirectTo',
54
55
  'setNeoConfig',
55
56
  'setRoute',
@@ -192,9 +193,25 @@ class Main extends core.Base {
192
193
  };
193
194
  }
194
195
 
195
- // todo: https://developer.mozilla.org/en-US/docs/Web/Events/resize
196
- globalResizeListener(event) {
197
- console.log('globalResizeListener', event);
196
+ /**
197
+ * Import main thread addons at run-time from within the app worker
198
+ * @param {Object} data
199
+ * @param {String} data.name
200
+ * @returns {Boolean}
201
+ */
202
+ async importAddon(data) {
203
+ let name = data.name,
204
+ module;
205
+
206
+ if (name.startsWith('WS/')) {
207
+ module = await import(`../../../src/main/addon/${name.substring(3)}.mjs`)
208
+ } else {
209
+ module = await import(`./main/addon/${name}.mjs`)
210
+ }
211
+
212
+ this.addon[module.default.constructor.name] = module.default;
213
+
214
+ return true
198
215
  }
199
216
 
200
217
  /**
@@ -209,9 +226,6 @@ class Main extends core.Base {
209
226
 
210
227
  DomAccess.onDomContentLoaded();
211
228
 
212
- // not in use right now
213
- // window.addEventListener('resize', me.globalResizeListener.bind(me));
214
-
215
229
  // we need different publicPath values for the main thread inside the webpack based dist envs,
216
230
  // depending on the hierarchy level of the app entry point
217
231
  if (config.environment !== 'development') {
@@ -229,7 +243,7 @@ class Main extends core.Base {
229
243
 
230
244
  mainThreadAddons.forEach(addon => {
231
245
  if (addon.startsWith('WS/')) {
232
- imports.push(import(`../../../src/main/addon/${addon.substr(3)}.mjs`));
246
+ imports.push(import(`../../../src/main/addon/${addon.substring(3)}.mjs`));
233
247
  } else {
234
248
  imports.push(import(`./main/addon/${addon}.mjs`));
235
249
  }
@@ -245,7 +259,7 @@ class Main extends core.Base {
245
259
 
246
260
  WorkerManager.onWorkerConstructed({
247
261
  origin: 'main'
248
- });
262
+ })
249
263
  }
250
264
 
251
265
  /**
@@ -258,7 +272,7 @@ class Main extends core.Base {
258
272
  action : 'reply',
259
273
  replyId: data.id,
260
274
  success: true
261
- });
275
+ })
262
276
  }
263
277
 
264
278
  /**
@@ -170,7 +170,6 @@ class Splitter extends Component {
170
170
  newSize = data.clientX - data.offsetX - size - parentRect.left;
171
171
 
172
172
  if (resizeNext) {
173
- console.log(parentRect);
174
173
  newSize = parentRect.width - newSize - 2 * size
175
174
  } else {
176
175
  newSize += size
@@ -31,7 +31,7 @@ class SortZone extends BaseSortZone {
31
31
  * @param {Number} toIndex
32
32
  */
33
33
  moveTo(fromIndex, toIndex) {
34
- this.owner.up().moveTo(fromIndex, toIndex);
34
+ this.owner.up().moveTo(fromIndex, toIndex)
35
35
  }
36
36
 
37
37
  /**
@@ -47,7 +47,7 @@ class SortZone extends BaseSortZone {
47
47
 
48
48
  NeoArray.remove(cls, 'neo-no-animation');
49
49
  owner.cls = cls;
50
- }, 300);
50
+ }, 300)
51
51
  }
52
52
 
53
53
  /**
@@ -61,7 +61,7 @@ class SortZone extends BaseSortZone {
61
61
  NeoArray.add(cls, 'neo-no-animation');
62
62
  owner.cls = cls;
63
63
 
64
- super.onDragStart(data);
64
+ super.onDragStart(data)
65
65
  }
66
66
  }
67
67
 
@@ -122,6 +122,10 @@ class FileUpload extends Base {
122
122
  tag : 'input',
123
123
  type : 'file'
124
124
  },
125
+ {
126
+ cls : 'neo-file-upload-label',
127
+ tag : 'label'
128
+ },
125
129
  {
126
130
  cls : 'neo-file-upload-error-message'
127
131
  }
@@ -149,7 +153,7 @@ class FileUpload extends Base {
149
153
  UPLOADING : 'scanning',
150
154
 
151
155
  MALWARE_DETECTED : 'scan-failed',
152
- UN_DOWNLOADABLE : 'not-downloadable',
156
+ AVAILABLE : 'not-downloadable',
153
157
  DOWNLOADABLE : 'downloadable',
154
158
  DELETED : 'deleted'
155
159
  },
@@ -311,6 +315,7 @@ class FileUpload extends Base {
311
315
  error_ : null,
312
316
 
313
317
  // UI strings which can be overridden for other languages
318
+ chooseFile : 'Choose file',
314
319
  documentText : 'Document',
315
320
  pleaseUseTheseTypes : 'Please use these file types',
316
321
  fileSizeMoreThan : 'File size exceeds',
@@ -319,6 +324,7 @@ class FileUpload extends Base {
319
324
  documentStatusError : 'Document status service error',
320
325
  uploadFailed : 'Upload failed',
321
326
  scanning : 'Scanning',
327
+ uploading : 'Uploading...',
322
328
  malwareFoundInFile : 'Malware found in file',
323
329
  pleaseCheck : 'Please check the file and try again',
324
330
  successfullyUploaded : 'Successfully uploaded',
@@ -339,6 +345,18 @@ class FileUpload extends Base {
339
345
  ]);
340
346
  }
341
347
 
348
+ afterSetId(value, oldValue) {
349
+ const
350
+ labelEl = this.vdom.cn[4],
351
+ inputElId = `${this.id}-input`;
352
+
353
+ this.getInputEl().id = labelEl.for = inputElId;
354
+ labelEl.html = this.chooseFile;
355
+
356
+ // silent vdom update, the super call will trigger the engine
357
+ super.afterSetId?.(value, oldValue);
358
+ }
359
+
342
360
  /**
343
361
  * @returns {Object}
344
362
  */
@@ -428,7 +446,7 @@ class FileUpload extends Base {
428
446
 
429
447
  /**
430
448
  * This event fires before every HTTP request is sent to the server via any of the configured URLs.
431
- *
449
+ *
432
450
  * @event beforeRequest
433
451
  * @param {Object} event The event
434
452
  * @param {Object} event.headers An object containing the configured {@link #property-headers}
@@ -452,7 +470,7 @@ class FileUpload extends Base {
452
470
 
453
471
  (vdom.style || (vdom.style = {}))['--upload-progress'] = `${progress}turn`;
454
472
 
455
- vdom.cn[1].cn[1].innerHTML = `Uploading... (${Math.round(progress * 100)}%)`;
473
+ vdom.cn[1].cn[1].innerHTML = `${this.uploading}... (${Math.round(progress * 100)}%)`;
456
474
 
457
475
  this.uploadSize = loaded;
458
476
  this.update();
@@ -740,13 +758,13 @@ class FileUpload extends Base {
740
758
 
741
759
  afterSetError(text) {
742
760
  if (text) {
743
- this.vdom.cn[4].cn = [{
761
+ this.vdom.cn[5].cn = [{
744
762
  vtype : 'text',
745
763
  html : text
746
764
  }];
747
765
  }
748
766
  else {
749
- this.vdom.cn[4].cn = [];
767
+ this.vdom.cn[5].cn = [];
750
768
  }
751
769
 
752
770
  this.validate();
@@ -665,7 +665,7 @@ class Text extends Base {
665
665
 
666
666
  me.silentVdomUpdate = true;
667
667
 
668
- !me.clean && me.validate(false);
668
+ me.validate(false);
669
669
  me.changeInputElKey('required', value ? value : null);
670
670
  me.labelText = me.labelText; // apply the optional text if needed
671
671
 
@@ -118,8 +118,8 @@ class DomEvents extends Base {
118
118
 
119
119
  let me = this;
120
120
 
121
- document.addEventListener('selectionchange', me.onSelectionChange .bind(me));
122
121
  document.addEventListener('DOMContentLoaded', me.onDomContentLoaded.bind(me));
122
+ document.addEventListener('selectionchange', me.onSelectionChange .bind(me));
123
123
  window .addEventListener('hashchange', me.onHashChange .bind(me));
124
124
 
125
125
  if (Neo.config.useSharedWorkers) {
@@ -332,6 +332,11 @@ class DomEvents extends Base {
332
332
  return path
333
333
  }
334
334
 
335
+ /**
336
+ * @param {Object[]} path
337
+ * @param {HTMLElement} target
338
+ * @returns {Object[]}
339
+ */
335
340
  getSelectionPath(path, target) {
336
341
  if (target.parentNode && target.id.split('__').length > 1) {
337
342
  path = this.getSelectionPath(path, target.parentNode);
@@ -343,7 +348,7 @@ class DomEvents extends Base {
343
348
  }
344
349
 
345
350
  /**
346
- * @param {Object} node
351
+ * @param {HTMLElement} node
347
352
  * @returns {Object}
348
353
  */
349
354
  getTargetData(node) {
@@ -72,7 +72,7 @@ class Stylesheet extends Base {
72
72
  addGlobalCss() {
73
73
  let config = Neo.config,
74
74
  themes = config.themes,
75
- folders = config.useCssVars ? ['src', ...themes] : [themes[0]],
75
+ folders = ['src', ...themes],
76
76
  env = config.environment,
77
77
  path = env.startsWith('dist/') ? '' : config.appPath.includes('docs') ? `../dist/${env}/` : `../../dist/${env}/`,
78
78
  rootPath = config.basePath.substr(6);
@@ -87,7 +87,7 @@ class Stylesheet extends Base {
87
87
  this.createStyleSheet(
88
88
  null,
89
89
  null,
90
- `${rootPath}${path}css${config.useCssVars ? '' : '-no-vars'}/${folder}/Global.css`
90
+ `${rootPath}${path}css/${folder}/Global.css`
91
91
  );
92
92
  });
93
93
  }
@@ -112,19 +112,14 @@ class Stylesheet extends Base {
112
112
  className = className.split('.').join('/');
113
113
 
114
114
  data.folders.forEach(folder => {
115
- if (
116
- folder === 'src' && config.useCssVars || folder.includes('theme-') && (
117
- config.useCssVars && config.themes.includes(`neo-${folder}`) ||
118
- !config.useCssVars && config.themes[0] === `neo-${folder}`
119
- )
120
- ) {
115
+ if (folder === 'src' || folder.includes('theme-') && config.themes.includes(`neo-${folder}`)) {
121
116
  this.createStyleSheet(
122
117
  null,
123
118
  null,
124
- `${rootPath}${path}css${config.useCssVars ? '' : '-no-vars'}/${folder}/${className}.css`
125
- );
119
+ `${rootPath}${path}css/${folder}/${className}.css`
120
+ )
126
121
  }
127
- });
122
+ })
128
123
  }
129
124
 
130
125
  /**
@@ -31,6 +31,7 @@ const globalDomEvents = [
31
31
  'mouseleave',
32
32
  'mouseup',
33
33
  'scroll',
34
+ 'selectionchange',
34
35
  'wheel'
35
36
  ];
36
37
 
@@ -0,0 +1,304 @@
1
+ import Base from './Base.mjs';
2
+
3
+ /**
4
+ * @class Neo.plugin.PrefixField
5
+ * @extends Neo.plugin.Base
6
+ *
7
+ * @example
8
+ *
9
+ * {
10
+ * module : TextField,
11
+ * labelText: 'Credit Card',
12
+ * plugins : [{
13
+ * module : PrefixPlugin, // import PrefixPlugin from '../../src/plugin/PrefixField.mjs';
14
+ * flag : 'prefix', // textField.getPlugins({flag: 'prefix'})
15
+ * pattern: 'dd/mm/yyyy',
16
+ * slots : 'dmy', // characters allowed to replace
17
+ * accept : /\d/ // either '[A-Z]' or regex or undefined
18
+ * }]
19
+ * }
20
+ */
21
+ class PrefixField extends Base {
22
+ static config = {
23
+ /**
24
+ * @member {String} className='Neo.plugin.PrefixField'
25
+ * @protected
26
+ */
27
+ className: 'Neo.plugin.PrefixField',
28
+ /**
29
+ * @member {String} ntype='plugin-prefixfield'
30
+ * @protected
31
+ */
32
+ ntype: 'plugin-prefixfield',
33
+
34
+ /**
35
+ * Custom cls added to the inputEl
36
+ * @member {String} inputCls='neo-prefixfield-input'
37
+ */
38
+ inputCls: 'neo-prefixfield-input',
39
+ /**
40
+ * Custom cls added to the inputEl
41
+ * @member {String} inputCls='neo-prefixfield-input'
42
+ */
43
+ labelCls: 'neo-prefixfield-label',
44
+ /**
45
+ * Custom cls to add to the owner component
46
+ * @member {String} ownerCls='neo-prefixfield'
47
+ */
48
+ ownerCls: 'neo-prefixfield',
49
+
50
+ /**
51
+ * regex to calculate if entered value is acceptable
52
+ * Preset to numbers only
53
+ *
54
+ * @member {regex|null} accept
55
+ */
56
+ accept_: null,
57
+ /**
58
+ * @member {String} pattern=null
59
+ */
60
+ pattern_: null,
61
+ /**
62
+ * Only add a String. A Set will be automatically created
63
+ * @member {String|Set|null} slots=null
64
+ */
65
+ slots_: null
66
+ }
67
+
68
+ /**
69
+ * First accepted place to enter a value
70
+ * @member {Number} first
71
+ * @protected
72
+ */
73
+ first = null;
74
+ /**
75
+ * Array of numbers, which shows the previous entry point
76
+ * @member {Array[]} prev
77
+ * @protected
78
+ */
79
+ prev = null;
80
+ /**
81
+ * Position of the cursor inside input element
82
+ * @member {Object} selection
83
+ * @protected
84
+ */
85
+ selection = null;
86
+
87
+ /**
88
+ * State if selection should be updated
89
+ * @member {Boolean} ignoreSelection
90
+ * @protected
91
+ */
92
+ ignoreSelection = false;
93
+ /**
94
+ * State if last entry was the back button
95
+ * @member {Boolean} back
96
+ * @protected
97
+ */
98
+ back = false;
99
+
100
+ /**
101
+ * @param {Object} config
102
+ */
103
+ construct(config) {
104
+ let me = this;
105
+
106
+ super.construct(config);
107
+
108
+ me.addListeners();
109
+ me.addCss();
110
+ }
111
+
112
+ /**
113
+ * Add a custom cls to the owner component
114
+ */
115
+ addCss() {
116
+ const me = this,
117
+ owner = me.owner,
118
+ inputEl = owner.getInputEl(),
119
+ labelEl = owner.getLabelEl();
120
+
121
+ owner .addCls(me.ownerCls);
122
+ inputEl.cls.push(me.inputCls);
123
+ labelEl.cls.push(me.labelCls);
124
+ }
125
+
126
+ /**
127
+ * Add listeners
128
+ * @protected
129
+ */
130
+ addListeners() {
131
+ const me = this;
132
+ let listenerId;
133
+
134
+ me.owner.addDomListeners([
135
+ {keydown : me.onFieldKeyDown , scope: me},
136
+ {focusin : me.onFieldFocus , scope: me},
137
+ {focusout : me.onFieldBlur , scope: me},
138
+ {selectionchange: me.onFieldSelectionChange, scope: me}
139
+ ]);
140
+
141
+ listenerId = me.owner.on('mounted', (test) => {
142
+ Neo.currentWorker.insertThemeFiles(me.owner.appName, me.__proto__);
143
+
144
+ me.owner.un('mounted', listenerId);
145
+ listenerId = null;
146
+ });
147
+ }
148
+
149
+
150
+ /**
151
+ * After setting accept format output
152
+ * @param {String} value
153
+ * @param {String} oldValue
154
+ * @protected
155
+ */
156
+ afterSetAccept(value, oldValue) {
157
+ if (this.owner.value) this.format();
158
+ }
159
+
160
+ /**
161
+ * After setting pattern recalc other values and set placeholder
162
+ * @param {Set} value
163
+ * @param {Set} oldValue
164
+ * @protected
165
+ */
166
+ afterSetPattern(value, oldValue) {
167
+ this.owner.placeholderText = value;
168
+ this.recalcFirstAndPref();
169
+ }
170
+
171
+ /**
172
+ * After setting slots recalc other values
173
+ * @param {Set} value
174
+ * @param {Set} oldValue
175
+ * @protected
176
+ */
177
+ afterSetSlots(value, oldValue) {
178
+ this.recalcFirstAndPref();
179
+ }
180
+
181
+ /**
182
+ * Before the new value for slots will be set we create a Set from the string
183
+ * @param {String} value
184
+ * @return {Set}
185
+ * @protected
186
+ */
187
+ beforeSetSlots(value) {
188
+ return new Set(value || "_");
189
+ }
190
+
191
+ /**
192
+ * Remove unwanted entries and limit length
193
+ * @param {String} input
194
+ * @returns {any[]}
195
+ * @protected
196
+ */
197
+ clean(input) {
198
+ const me = this,
199
+ accept = new RegExp(this.accept || "\\d", "g");
200
+
201
+ input = input.match(accept) || [];
202
+ input = Array.from(me.pattern, c =>
203
+ input[0] === c || me.slots.has(c) ? input.shift() || c : c
204
+ );
205
+
206
+ return input.slice(0, me.pattern.length);
207
+ }
208
+
209
+ /**
210
+ * Calculate position and output correct String to Field
211
+ * @protected
212
+ */
213
+ format() {
214
+ const me = this,
215
+ el = me.owner,
216
+ selection = me.selection,
217
+ prev = me.prev,
218
+ clean = me.clean.bind(me);
219
+ let value = el.value || '';
220
+
221
+ const [i, j] = [selection.start, selection.end].map(i => {
222
+ i = me.clean(value.slice(0, i)).findIndex(c => me.slots.has(c));
223
+ return i < 0 ? prev[prev.length - 1] : me.back ? prev[i - 1] || me.first : i;
224
+ });
225
+
226
+ el.value = clean(value).join``;
227
+ this.ignoreSelection = true;
228
+
229
+ Neo.main.DomAccess.selectNode({id: el.getInputElId(), start: i, end: j});
230
+ this.ignoreSelection = false;
231
+
232
+ this.back = false;
233
+ }
234
+
235
+ /**
236
+ * Event
237
+ * @param {Object} data
238
+ * @returns {false|string}
239
+ * @protected
240
+ */
241
+ onFieldBlur(data) {
242
+ const pattern = this.pattern,
243
+ el = this.owner;
244
+
245
+ return el.value === pattern && (el.value = "");
246
+ }
247
+
248
+ /**
249
+ * Event
250
+ * @param {Object} data
251
+ * @protected
252
+ */
253
+ onFieldFocus(data) {
254
+ this.format();
255
+ }
256
+
257
+ /**
258
+ * Event
259
+ * @param {Object} data
260
+ * @protected
261
+ */
262
+ onFieldKeyDown(data) {
263
+ this.back = (data.key === "Backspace");
264
+ }
265
+
266
+ /**
267
+ * Event
268
+ * @param {Object} data
269
+ * @protected
270
+ */
271
+ onFieldSelectionChange(data) {
272
+ let sel = this.selection,
273
+ dSel = data.selection;
274
+
275
+ // Do not run, if ignore state or same start and end data
276
+ if (this.ignoreSelection || (dSel.start === sel.start && dSel.end === sel.end)) {
277
+ return;
278
+ }
279
+
280
+ this.selection = dSel;
281
+ this.format();
282
+ }
283
+
284
+ /**
285
+ * Calc values for first and prev
286
+ * @protected
287
+ */
288
+ recalcFirstAndPref() {
289
+ const me = this,
290
+ pattern = me.pattern,
291
+ slots = me.slots;
292
+
293
+ me.prev = (j => Array.from(pattern, (c, i) => slots.has(c) ? j = i + 1 : j))(0);
294
+ me.first = [...pattern].findIndex(c => slots.has(c));
295
+
296
+ me.selection = {start: me.first, end: me.first};
297
+
298
+ if (me.owner.value) me.format();
299
+ }
300
+ }
301
+
302
+ Neo.applyClassConfig(PrefixField);
303
+
304
+ export default PrefixField;
@@ -43,8 +43,8 @@ class Toolbar extends BaseToolbar {
43
43
  boundaryContainerId: me.id,
44
44
  owner : me,
45
45
  ...me.sortZoneConfig
46
- });
47
- });
46
+ })
47
+ })
48
48
  }
49
49
  }
50
50
 
@@ -61,10 +61,10 @@ class Toolbar extends BaseToolbar {
61
61
  me.items.forEach(item => {
62
62
  // silent updates
63
63
  item._useActiveTabIndicator = value;
64
- item.updateUseActiveTabIndicator(true);
64
+ item.updateUseActiveTabIndicator(true)
65
65
  });
66
66
 
67
- me.update();
67
+ me.update()
68
68
  }
69
69
  }
70
70
 
@@ -78,7 +78,7 @@ class Toolbar extends BaseToolbar {
78
78
  defaults.useActiveTabIndicator = me.useActiveTabIndicator;
79
79
  me.itemDefaults = defaults;
80
80
 
81
- super.createItems();
81
+ super.createItems()
82
82
  }
83
83
 
84
84
  /**
@@ -116,7 +116,7 @@ class Toolbar extends BaseToolbar {
116
116
  break;
117
117
  }
118
118
 
119
- return layoutConfig;
119
+ return layoutConfig
120
120
  }
121
121
 
122
122
  /**
@@ -131,10 +131,10 @@ class Toolbar extends BaseToolbar {
131
131
  if (fromIndex !== toIndex) {
132
132
  this.items.forEach((item, index) => {
133
133
  item.index = index;
134
- });
134
+ })
135
135
  }
136
136
 
137
- return returnValue;
137
+ return returnValue
138
138
  }
139
139
  }
140
140
 
@@ -313,7 +313,7 @@ class App extends Base {
313
313
  super.onRegisterNeoConfig(msg);
314
314
 
315
315
  let config = Neo.config,
316
- url = `resources/theme-map${config.useCssVars ? '' : '-no-vars'}.json`;
316
+ url = 'resources/theme-map.json';
317
317
 
318
318
  if (config.environment === 'development') {
319
319
  url = `../../${url}`
@@ -1,17 +0,0 @@
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 Docs NoCssVars</title>
7
- </head>
8
- <body>
9
- <script type="module">
10
- fetch('./neo-config-no-css-vars.json').then(response => response.json()).then(data => {
11
- self.Neo = {config: {}};
12
- Object.assign(Neo.config, data);
13
- import(data.mainPath);
14
- });
15
- </script>
16
- </body>
17
- </html>
@@ -1,8 +0,0 @@
1
- {
2
- "appPath" : "docs/app.mjs",
3
- "basePath" : "../",
4
- "environment" : "development",
5
- "mainPath" : "../src/Main.mjs",
6
- "mainThreadAddons": ["DragDrop", "HighlightJS", "Stylesheet"],
7
- "useCssVars" : false
8
- }