neo.mjs 4.0.49 → 4.0.52

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.
@@ -0,0 +1,372 @@
1
+ #!/usr/bin/env node
2
+
3
+ import chalk from 'chalk';
4
+ import { Command } from 'commander/esm.mjs';
5
+ import envinfo from 'envinfo';
6
+ import fs from 'fs-extra';
7
+ import inquirer from 'inquirer';
8
+ import os from 'os';
9
+ import path from 'path';
10
+ import {fileURLToPath} from 'url';
11
+
12
+ const
13
+ __dirname = fileURLToPath(new URL('../', import.meta.url)),
14
+ cwd = process.cwd(),
15
+ requireJson = path => JSON.parse(fs.readFileSync((path))),
16
+ packageJson = requireJson(path.join(__dirname, 'package.json')),
17
+ insideNeo = packageJson.name === 'neo.mjs',
18
+ program = new Command(),
19
+ programName = `${packageJson.name} create-class`,
20
+ questions = [],
21
+ /**
22
+ * Maintain a list of dir-names recognized as source root directories.
23
+ * When not using dot notation with a class-name, the program assumes
24
+ * that we want to create the class inside the cwd. The proper namespace
25
+ * is then looked up by traversing the directory path up to the first
26
+ * folder that matches an entry in "sourceRootDirs". The owning
27
+ * folder (parent of cwd, child of sourceRootDirs[n]) will then be used as the
28
+ * namespace for this created class.
29
+ * Can be overwritten with the -s option.
30
+ * @type {string[]}
31
+ */
32
+ sourceRootDirs = ['apps'];
33
+
34
+ program
35
+ .name(programName)
36
+ .version(packageJson.version)
37
+ .option('-i, --info', 'print environment debug info')
38
+ .option('-d, --drop', 'drops class in the currently selected folder')
39
+ .option('-s, --source <value>', `name of the folder containing the project. Defaults to any of ${sourceRootDirs.join(',')}`)
40
+ .option('-b, --baseClass <value>')
41
+ .option('-c, --className <value>')
42
+ .allowUnknownOption()
43
+ .on('--help', () => {
44
+ console.log('\nIn case you have any issues, please create a ticket here:');
45
+ console.log(chalk.cyan(process.env.npm_package_bugs_url));
46
+ })
47
+ .parse(process.argv);
48
+
49
+ const programOpts = program.opts();
50
+
51
+ if (programOpts.info) {
52
+ console.log(chalk.bold('\nEnvironment Info:'));
53
+ console.log(`\n current version of ${packageJson.name}: ${packageJson.version}`);
54
+ console.log(` running from ${cwd}`);
55
+
56
+ envinfo
57
+ .run({
58
+ System : ['OS', 'CPU'],
59
+ Binaries : ['Node', 'npm', 'Yarn'],
60
+ Browsers : ['Chrome', 'Edge', 'Firefox', 'Safari'],
61
+ npmPackages: ['neo.mjs']
62
+ }, {
63
+ duplicates : true,
64
+ showNotFound: true
65
+ })
66
+ .then(console.log);
67
+ } else {
68
+ console.log(chalk.green(programName));
69
+
70
+ if (programOpts.drop) {
71
+ // change source folder if the user wants to
72
+ if (programOpts.source) {
73
+ while (sourceRootDirs.length) {
74
+ sourceRootDirs.pop();
75
+ }
76
+ sourceRootDirs.push(programOpts.source);
77
+ }
78
+
79
+ if (!programOpts.className || !programOpts.baseClass) {
80
+ console.error(chalk.red('-d is non interactive. Please provide name base class, and optionally the source parent for the class to create'));
81
+ console.info(chalk.bgCyan('Usage: createClass -d -c <className> -b <baseClass> [-s sourceParent]'));
82
+ process.exit(1);
83
+ }
84
+
85
+ if (programOpts.className.indexOf('.') !== -1) {
86
+ console.error(chalk.red('No .dot-notation avcailable when -d option is selected.'));
87
+ console.info(chalk.bgCyan('Usage: createClass -d -c <className> -b <baseClass> [-s sourceParent]'));
88
+ process.exit(1);
89
+ }
90
+ }
91
+
92
+ if (!programOpts.className) {
93
+ questions.push({
94
+ type : 'input',
95
+ name : 'className',
96
+ message: 'Please choose the namespace for your class:',
97
+ default: 'Covid.view.HeaderContainerController'
98
+ });
99
+ }
100
+
101
+ if (!programOpts.baseClass) {
102
+ questions.push({
103
+ type : 'list',
104
+ name : 'baseClass',
105
+ message: 'Please pick the base class, which you want to extend:',
106
+ choices: ['component.Base', 'container.Base', 'controller.Component', 'core.Base'],
107
+ default: 'container.Base'
108
+ });
109
+ }
110
+
111
+ inquirer.prompt(questions).then(answers => {
112
+ let baseClass = programOpts.baseClass || answers.baseClass,
113
+ className = programOpts.className || answers.className,
114
+ isDrop = programOpts.drop,
115
+ startDate = new Date(),
116
+ classFolder, file, folderDelta, index, ns, root, rootLowerCase, viewFile;
117
+
118
+ if (className.endsWith('.mjs')) {
119
+ className = className.slice(0, -4);
120
+ }
121
+
122
+ if (!isDrop) {
123
+ ns = className.split('.');
124
+ file = ns.pop();
125
+ root = ns.shift();
126
+ rootLowerCase = root.toLowerCase();
127
+ }
128
+
129
+ if (root === 'Neo') {
130
+ console.log('todo: create the file inside the src folder');
131
+ } else {
132
+ if (isDrop === true) {
133
+ ns = [];
134
+
135
+ let pathInfo = path.parse(cwd),
136
+ sep = path.sep,
137
+ baseName, loc = baseName = '',
138
+ tmpNs;
139
+
140
+ sourceRootDirs.some(dir => {
141
+ loc = cwd;
142
+ tmpNs = [];
143
+
144
+ while (pathInfo.root !== loc) {
145
+ baseName = path.resolve(loc, './').split(sep).pop();
146
+
147
+ if (baseName === dir) {
148
+ ns = tmpNs.reverse();
149
+ classFolder = path.resolve(loc, ns.join(sep));
150
+ file = className;
151
+ className = ns.concat(className).join('.');
152
+ loc = path.resolve(loc, ns.join(sep));
153
+ return true;
154
+ }
155
+
156
+ tmpNs.push(baseName);
157
+ loc = path.resolve(loc, '../');
158
+ }
159
+ });
160
+
161
+ if (!ns.length) {
162
+ console.error(chalk.red(
163
+ 'Could not determine namespace for application. Did you provide the ' +
164
+ `correct source parent with -s? (was: ${sourceRootDirs.join(',')}`));
165
+ process.exit(1);
166
+ }
167
+
168
+ console.info(
169
+ chalk.yellow(`Creating ${chalk.bgGreen(className)} extending ${chalk.bgGreen(baseClass)} in ${loc}${sep}${file}.mjs`)
170
+ );
171
+
172
+ let delta_l = path.normalize(__dirname),
173
+ delta_r = path.normalize(loc);
174
+
175
+ if (delta_r.indexOf(delta_l) !== 0) {
176
+ console.error(chalk.red(`Could not determine ${loc} being a child of ${__dirname}`));
177
+ process.exit(1);
178
+ }
179
+
180
+ let delta = delta_r.replace(delta_l, ''),
181
+ parts = delta.split(sep);
182
+
183
+ folderDelta = parts.length;
184
+ }
185
+
186
+ if (isDrop !== true) {
187
+ if (fs.existsSync(path.resolve(__dirname, 'apps', rootLowerCase))) {
188
+ classFolder = path.resolve(__dirname, 'apps', rootLowerCase, ns.join('/'));
189
+ } else {
190
+ console.log('\nNon existing neo app name:', chalk.red(root));
191
+ process.exit(1);
192
+ }
193
+ }
194
+
195
+ if (folderDelta === undefined) {
196
+ folderDelta = ns.length + 2;
197
+ }
198
+
199
+ fs.mkdirpSync(classFolder);
200
+
201
+ fs.writeFileSync(path.join(classFolder, file + '.mjs'), createContent({baseClass, className, file, folderDelta, ns, root}));
202
+
203
+ if (baseClass === 'controller.Component') {
204
+ index = file.indexOf('Controller');
205
+
206
+ if (index > 0) {
207
+ viewFile = path.join(classFolder, file.substr(0, index) + '.mjs');
208
+
209
+ if (fs.existsSync(viewFile)) {
210
+ adjustView({file, viewFile});
211
+ }
212
+ }
213
+ }
214
+ }
215
+
216
+ const processTime = (Math.round((new Date - startDate) * 100) / 100000).toFixed(2);
217
+ console.log(`\nTotal time for ${programName}: ${processTime}s`);
218
+
219
+ process.exit();
220
+ });
221
+
222
+ /**
223
+ * Adds a comma to the last element of the contentArray
224
+ * @param {String[]} contentArray
225
+ * @returns {String[]}
226
+ */
227
+ function addComma(contentArray) {
228
+ contentArray[contentArray.length - 1] += ',';
229
+ return contentArray;
230
+ }
231
+
232
+ /**
233
+ * Adjusts the views related to controller.Component or model.Component
234
+ * @param {Object} opts
235
+ * @param {String} opts.file
236
+ * @param {String} opts.viewFile
237
+ */
238
+ function adjustView(opts) {
239
+ let file = opts.file,
240
+ viewFile = opts.viewFile,
241
+ content = fs.readFileSync(viewFile).toString().split(os.EOL),
242
+ fromMaxPosition = 0,
243
+ i = 0,
244
+ len = content.length,
245
+ adjustSpaces, codeLine, fromPosition, importLength, importName, j, spaces;
246
+
247
+ // find the index where we want to insert our import statement
248
+ for (; i < len; i++) {
249
+ codeLine = content[i];
250
+
251
+ if (codeLine === '') {
252
+ break;
253
+ }
254
+
255
+ importName = codeLine.substr(7);
256
+ importName = importName.substr(0, importName.indexOf(' '));
257
+ importLength = importName.length;
258
+
259
+ if (importName > file) {
260
+ break;
261
+ }
262
+ }
263
+
264
+ content.splice(i, 0, `import ${file} from './${file}.mjs';`);
265
+
266
+ // find the longest import module name
267
+ for (i=0; i < len; i++) {
268
+ codeLine = content[i];
269
+
270
+ if (codeLine === '') {
271
+ break;
272
+ }
273
+
274
+ fromMaxPosition = Math.max(fromMaxPosition, codeLine.indexOf('from'));
275
+ }
276
+
277
+ // adjust the block-formatting for imports
278
+ for (i=0; i < len; i++) {
279
+ codeLine = content[i];
280
+
281
+ if (codeLine === '') {
282
+ break;
283
+ }
284
+
285
+ fromPosition = codeLine.indexOf('from');
286
+ adjustSpaces = fromMaxPosition - fromPosition;
287
+
288
+ if (adjustSpaces > 0) {
289
+ spaces = '';
290
+
291
+ for (j=0; j < adjustSpaces; j++) {
292
+ spaces += ' ';
293
+ }
294
+
295
+ content[i] = codeLine.substr(0, fromPosition) + spaces + codeLine.substr(fromPosition);
296
+ }
297
+ }
298
+
299
+ fs.writeFileSync(viewFile, content.join(os.EOL));
300
+
301
+ console.log(i, opts.file);
302
+ console.log(content);
303
+ }
304
+
305
+ /**
306
+ * Creates the content of the neo-class .mjs file
307
+ * @param {Object} opts
308
+ * @param {String} opts.baseClass
309
+ * @param {String} opts.className
310
+ * @param {String} opts.file
311
+ * @param {String} opts.folderDelta
312
+ * @param {String} opts.ns
313
+ * @param {String} opts.root
314
+ * @returns {String}
315
+ */
316
+ function createContent(opts) {
317
+ let baseClass = opts.baseClass,
318
+ baseClassNs = baseClass.split('.'),
319
+ baseFileName = baseClassNs.pop(),
320
+ className = opts.className,
321
+ file = opts.file,
322
+ i = 0,
323
+ importDelta = '';
324
+
325
+ for (; i < opts.folderDelta; i++) {
326
+ importDelta += '../';
327
+ }
328
+
329
+ let classContent = [
330
+ `import ${baseFileName} from '${importDelta}${(insideNeo ? '' : 'node_modules/neo.mjs/')}src/${baseClassNs.join('/')}/${baseFileName}.mjs';`,
331
+ "",
332
+ "/**",
333
+ ` * @class ${className}`,
334
+ ` * @extends Neo.${baseClass}`,
335
+ " */",
336
+ `class ${file} extends ${baseFileName} {`,
337
+ " static getConfig() {return {",
338
+ " /*",
339
+ ` * @member {String} className='${className}'`,
340
+ " * @protected",
341
+ " */",
342
+ ` className: '${className}'`
343
+ ];
344
+
345
+ baseClass === 'container.Base' && addComma(classContent).push(
346
+ " /*",
347
+ " * @member {Object[]} items",
348
+ " */",
349
+ " items: []"
350
+ );
351
+
352
+ baseClass === 'component.Base' && addComma(classContent).push(
353
+ " /*",
354
+ " * @member {Object} _vdom",
355
+ " */",
356
+ " _vdom:",
357
+ " {}"
358
+ );
359
+
360
+ classContent.push(
361
+ " }}",
362
+ "}",
363
+ "",
364
+ `Neo.applyClassConfig(${file});`,
365
+ "",
366
+ `export default ${file};`,
367
+ ""
368
+ );
369
+
370
+ return classContent.join(os.EOL);
371
+ }
372
+ }
@@ -97,6 +97,22 @@ class MainContainer extends ConfigurationViewport {
97
97
  minValue : 50,
98
98
  stepSize : 5,
99
99
  value : me.exampleComponent.labelWidth
100
+ }, {
101
+ module : NumberField,
102
+ labelText: 'maxLength',
103
+ listeners: {change: me.onConfigChange.bind(me, 'maxLength')},
104
+ maxValue : 50,
105
+ minValue : 1,
106
+ stepSize : 1,
107
+ value : me.exampleComponent.maxLength
108
+ }, {
109
+ module : NumberField,
110
+ labelText: 'minLength',
111
+ listeners: {change: me.onConfigChange.bind(me, 'minLength')},
112
+ maxValue : 50,
113
+ minValue : 1,
114
+ stepSize : 1,
115
+ value : me.exampleComponent.minLength
100
116
  }, {
101
117
  module : TextField,
102
118
  clearable: true,
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "4.0.49",
3
+ "version": "4.0.52",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/neomjs/neo.git"
9
9
  },
10
+ "bin": {
11
+ "neo-cc": "./buildScripts/createClass.mjs"
12
+ },
10
13
  "scripts": {
11
14
  "build-all": "node ./buildScripts/buildAll.mjs -f -n",
12
15
  "build-all-questions": "node ./buildScripts/buildAll.mjs -f",
@@ -14,6 +17,7 @@
14
17
  "build-themes": "node ./buildScripts/buildThemes.mjs -f",
15
18
  "build-threads": "node ./buildScripts/webpack/buildThreads.mjs -f",
16
19
  "create-app": "node ./buildScripts/createApp.mjs",
20
+ "create-class": "node ./buildScripts/createClass.mjs",
17
21
  "generate-docs-json": "node ./buildScripts/docs/jsdocx.mjs",
18
22
  "server-start": "webpack serve -c ./buildScripts/webpack/webpack.server.config.mjs --open",
19
23
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -50,7 +54,7 @@
50
54
  "neo-jsdoc": "^1.0.1",
51
55
  "neo-jsdoc-x": "^1.0.4",
52
56
  "postcss": "^8.4.14",
53
- "sass": "^1.52.3",
57
+ "sass": "^1.53.0",
54
58
  "webpack": "^5.73.0",
55
59
  "webpack-cli": "^4.10.0",
56
60
  "webpack-dev-server": "4.9.2",
@@ -58,7 +62,8 @@
58
62
  "webpack-node-externals": "^3.0.0"
59
63
  },
60
64
  "devDependencies": {
61
- "siesta-lite": "^5.5.2"
65
+ "siesta-lite": "^5.5.2",
66
+ "url": "^0.11.0"
62
67
  },
63
68
  "funding": {
64
69
  "type": "GitHub Sponsors",
@@ -28,9 +28,9 @@ class RecordFactory extends Base {
28
28
  */
29
29
  ovPrefix: 'ov_',
30
30
  /**
31
- * @member {String} recordNamespace='Neo.data.record.'
31
+ * @member {String} recordNamespace='Neo.data.record'
32
32
  */
33
- recordNamespace: 'Neo.data.record.'
33
+ recordNamespace: 'Neo.data.record'
34
34
  }}
35
35
 
36
36
  /**
@@ -39,7 +39,7 @@ class RecordFactory extends Base {
39
39
  * @returns {Object}
40
40
  */
41
41
  createRecord(model, config) {
42
- let recordClass = Neo.ns(this.recordNamespace + model.className);
42
+ let recordClass = Neo.ns(`${this.recordNamespace}.${model.className}.${model.id}`);
43
43
 
44
44
  if (!recordClass) {
45
45
  recordClass = this.createRecordClass(model);
@@ -54,7 +54,7 @@ class RecordFactory extends Base {
54
54
  */
55
55
  createRecordClass(model) {
56
56
  if (model instanceof Model) {
57
- let className = this.recordNamespace + model.className,
57
+ let className = `${this.recordNamespace}.${model.className}.${model.id}`,
58
58
  ns = Neo.ns(className),
59
59
  key, nsArray;
60
60
 
@@ -76,7 +76,7 @@ class RecordFactory extends Base {
76
76
 
77
77
  if (Array.isArray(model.fields)) {
78
78
  model.fields.forEach(field => {
79
- let parsedValue = instance.parseRecordValue(field, config[field.name], config),
79
+ let parsedValue = instance.parseRecordValue(me, field, config[field.name], config),
80
80
  symbol = Symbol.for(field.name);
81
81
 
82
82
  properties = {
@@ -97,9 +97,9 @@ class RecordFactory extends Base {
97
97
  let me = this,
98
98
  oldValue = me[symbol];
99
99
 
100
- if (!Neo.isEqual(value, oldValue)) {
101
- value = instance.parseRecordValue(field, value, null);
100
+ value = instance.parseRecordValue(me, field, value);
102
101
 
102
+ if (!Neo.isEqual(value, oldValue)) {
103
103
  me[symbol] = value;
104
104
 
105
105
  me._isModified = true;
@@ -221,14 +221,18 @@ class RecordFactory extends Base {
221
221
 
222
222
  /**
223
223
  * todo: parse value for more field types
224
+ * @param {Object} record
224
225
  * @param {Object} field
225
226
  * @param {*} value
226
- * @param {Object} recordConfig
227
+ * @param {Object} recordConfig=null
227
228
  * @returns {*}
228
229
  */
229
- parseRecordValue(field, value, recordConfig) {
230
- let mapping = field.mapping,
231
- type = field.type?.toLowerCase();
230
+ parseRecordValue(record, field, value, recordConfig=null) {
231
+ let mapping = field.mapping,
232
+ maxLength = field.maxLength,
233
+ minLength = field.minLength,
234
+ oldValue = recordConfig?.[field.name] || record[field.name],
235
+ type = field.type?.toLowerCase();
232
236
 
233
237
  // only trigger mappings for initial values
234
238
  // dynamic changes of a field will not pass the recordConfig
@@ -240,7 +244,21 @@ class RecordFactory extends Base {
240
244
  value = ns[key];
241
245
  }
242
246
 
243
- if (type === 'date') {
247
+ if (Object.hasOwn(field, maxLength)) {
248
+ if (value?.toString() > maxLength) {
249
+ console.warn(`Setting record field: ${field} value: ${value} conflicts with the maxLength: ${maxLength}`);
250
+ return oldValue;
251
+ }
252
+ }
253
+
254
+ if (Object.hasOwn(field, minLength)) {
255
+ if (value?.toString() < minLength) {
256
+ console.warn(`Setting record field: ${field} value: ${value} conflicts with the minLength: ${minLength}`);
257
+ return oldValue;
258
+ }
259
+ }
260
+
261
+ if (type === 'date' && Neo.typeOf(value) !== 'Date') {
244
262
  return new Date(value);
245
263
  }
246
264
 
@@ -259,6 +277,7 @@ class RecordFactory extends Base {
259
277
 
260
278
  Object.entries(fields).forEach(([key, value]) => {
261
279
  oldValue = record[key];
280
+ value = instance.parseRecordValue(record, model.getField(key), value);
262
281
 
263
282
  if (!Neo.isEqual(oldValue, value)) {
264
283
  record[Symbol.for(key)] = value; // silent update
@@ -174,6 +174,28 @@ class Number extends Text {
174
174
  return this.beforeSetEnumValue(value, oldValue, 'triggerPosition');
175
175
  }
176
176
 
177
+ /**
178
+ * @returns {Boolean}
179
+ */
180
+ isValid() {
181
+ let me = this,
182
+ value = me.value;
183
+
184
+ if (Neo.isNumber(me.maxValue) && value > me.maxValue) {
185
+ return false;
186
+ }
187
+
188
+ if (Neo.isNumber(me.minValue) && value < me.minValue) {
189
+ return false;
190
+ }
191
+
192
+ if (value % me.stepSize !== 0) {
193
+ return false;
194
+ }
195
+
196
+ return super.isValid();
197
+ }
198
+
177
199
  /**
178
200
  *
179
201
  */
@@ -215,12 +237,13 @@ class Number extends Text {
215
237
  */
216
238
  onSpinButtonDownClick() {
217
239
  let me = this,
218
- oldValue = me.value || (me.maxValue + me.stepSize),
219
- value = Math.max(me.minValue, oldValue - me.stepSize);
240
+ stepSize = me.stepSize,
241
+ oldValue = Neo.isNumber(me.value) ? me.value : me.minValue,
242
+ value = (oldValue - stepSize) < me.minValue ? me.maxValue : (oldValue - stepSize);
220
243
 
221
244
  if (me.excludedValues) {
222
245
  while(me.excludedValues.includes(value)) {
223
- value = Math.max(me.minValue, value - me.stepSize);
246
+ value = Math.max(me.minValue, value - stepSize);
224
247
  }
225
248
  }
226
249
 
@@ -234,12 +257,13 @@ class Number extends Text {
234
257
  */
235
258
  onSpinButtonUpClick() {
236
259
  let me = this,
237
- oldValue = me.value || (me.minValue - me.stepSize),
238
- value = Math.min(me.maxValue, oldValue + me.stepSize);
260
+ stepSize = me.stepSize,
261
+ oldValue = Neo.isNumber(me.value) ? me.value : me.maxValue,
262
+ value = (oldValue + stepSize) > me.maxValue ? me.minValue : (oldValue + stepSize);
239
263
 
240
264
  if (me.excludedValues) {
241
265
  while(me.excludedValues.includes(value)) {
242
- value = Math.min(me.maxValue, value + me.stepSize);
266
+ value = Math.min(me.maxValue, value + stepSize);
243
267
  }
244
268
  }
245
269
 
@@ -93,6 +93,16 @@ class Text extends Base {
93
93
  * @member {Number|String} labelWidth_=150
94
94
  */
95
95
  labelWidth_: 150,
96
+ /**
97
+ * The maximum amount of chars which you can enter into this field
98
+ * @member {Number|null} maxLength_=null
99
+ */
100
+ maxLength_: null,
101
+ /**
102
+ * The minimum amount of chars which you can enter into this field
103
+ * @member {Number|null} minLength_=null
104
+ */
105
+ minLength_: null,
96
106
  /**
97
107
  * @member {String|null} placeholderText_=null
98
108
  */
@@ -358,6 +368,26 @@ class Text extends Base {
358
368
  }
359
369
  }
360
370
 
371
+ /**
372
+ * Triggered after the maxLength config got changed
373
+ * @param {Number|null} value
374
+ * @param {Number|null} oldValue
375
+ * @protected
376
+ */
377
+ afterSetMaxLength(value, oldValue) {
378
+ this.changeInputElKey('maxlength', value);
379
+ }
380
+
381
+ /**
382
+ * Triggered after the minLength config got changed
383
+ * @param {Number|null} value
384
+ * @param {Number|null} oldValue
385
+ * @protected
386
+ */
387
+ afterSetMinLength(value, oldValue) {
388
+ this.changeInputElKey('minlength', value);
389
+ }
390
+
361
391
  /**
362
392
  * Triggered after the mounted config got changed
363
393
  * @param {Boolean} value
@@ -776,9 +806,19 @@ class Text extends Base {
776
806
  * @returns {Boolean}
777
807
  */
778
808
  isValid() {
779
- let me = this;
809
+ let me = this,
810
+ value = me.value,
811
+ valueLength = value?.toString().length;
812
+
813
+ if (me.required && (!value || valueLength < 1)) {
814
+ return false;
815
+ }
816
+
817
+ if (Neo.isNumber(me.maxLength) && valueLength > me.maxLength) {
818
+ return false;
819
+ }
780
820
 
781
- if (me.required && (!me.value || me.value?.length < 1)) {
821
+ if (Neo.isNumber(me.minLength) && valueLength < me.minLength) {
782
822
  return false;
783
823
  }
784
824
 
@@ -186,11 +186,15 @@ class DeltaUpdates extends Base {
186
186
  }
187
187
  } else if (key === 'id') {
188
188
  node[Neo.config.useDomIds ? 'id' : 'data-neo-id'] = val;
189
- }else if (key === 'spellcheck' && val === 'false') {
189
+ } else if (key === 'spellcheck' && val === 'false') {
190
190
  // see https://github.com/neomjs/neo/issues/1922
191
191
  node[key] = false;
192
192
  } else {
193
- node[key] = val;
193
+ if (key === 'value') {
194
+ node[key] = val;
195
+ } else {
196
+ node.setAttribute(key, val);
197
+ }
194
198
  }
195
199
  });
196
200
  break;
@@ -247,9 +247,9 @@ class Container extends BaseContainer {
247
247
  }
248
248
 
249
249
  if (value) {
250
- let me = this;
250
+ let me = this,
251
251
 
252
- const listeners = {
252
+ listeners = {
253
253
  filter : me.onStoreFilter,
254
254
  load : me.onStoreLoad,
255
255
  recordChange: me.onStoreRecordChange,
@@ -258,13 +258,10 @@ class Container extends BaseContainer {
258
258
 
259
259
  if (value instanceof Store) {
260
260
  value.on(listeners);
261
-
262
- if (value.getCount() > 0) {
263
- me.onStoreLoad(value.items);
264
- }
261
+ value.getCount() > 0 && me.onStoreLoad(value.items);
265
262
  } else {
266
263
  value = ClassSystemUtil.beforeSetInstance(value, Store, {
267
- listeners: listeners
264
+ listeners
268
265
  });
269
266
  }
270
267