neo.mjs 6.8.1 → 6.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/.github/PULL_REQUEST_TEMPLATE.md +1 -1
  2. package/apps/ServiceWorker.mjs +2 -2
  3. package/apps/covid/view/country/HistoricalDataTable.mjs +2 -2
  4. package/apps/covid/view/country/Table.mjs +2 -2
  5. package/apps/learnneo/app.mjs +6 -0
  6. package/apps/learnneo/index.html +11 -0
  7. package/apps/learnneo/model/Content.mjs +44 -0
  8. package/apps/learnneo/neo-config.json +6 -0
  9. package/apps/learnneo/store/Content.mjs +24 -0
  10. package/apps/learnneo/view/Viewport.mjs +34 -0
  11. package/apps/learnneo/view/ViewportController.mjs +19 -0
  12. package/apps/learnneo/view/home/ContentTreeList.mjs +41 -0
  13. package/apps/learnneo/view/home/MainContainer.mjs +50 -0
  14. package/apps/learnneo/view/home/MainContainerController.mjs +28 -0
  15. package/apps/sharedcovid/view/country/HistoricalDataTable.mjs +2 -2
  16. package/apps/sharedcovid/view/country/Table.mjs +2 -2
  17. package/examples/ServiceWorker.mjs +2 -2
  18. package/examples/form/field/select/MainContainer.mjs +6 -0
  19. package/examples/grid/covid/GridContainer.mjs +2 -2
  20. package/examples/table/covid/TableContainer.mjs +2 -2
  21. package/examples/todoList/version1/MainComponent.mjs +5 -5
  22. package/package.json +2 -2
  23. package/resources/data/learnneo/content.json +27 -0
  24. package/resources/scss/src/form/field/Date.scss +6 -1
  25. package/resources/scss/src/form/field/Picker.scss +0 -1
  26. package/src/DefaultConfig.mjs +2 -2
  27. package/src/dialog/Base.mjs +1 -4
  28. package/src/form/field/FileUpload.mjs +11 -0
  29. package/src/form/field/Picker.mjs +0 -17
  30. package/src/form/field/Text.mjs +29 -0
  31. package/src/form/field/trigger/Picker.mjs +7 -0
  32. package/src/main/DomAccess.mjs +4 -2
  33. package/src/table/View.mjs +2 -2
@@ -23,7 +23,7 @@ If yes, please describe the impact and migration path for existing applications:
23
23
 
24
24
  **The PR fulfills these requirements:**
25
25
 
26
- - [ ] It's submitted to the `dev` branch, _not_ the `master` branch
26
+ - [ ] It's submitted to the `dev` branch, _not_ the `main` branch
27
27
  - [ ] When resolving a specific issue, it's referenced in the PR's title (e.g. `fix #xxx[,#xxx]`, where "xxx" is the issue number)
28
28
 
29
29
  If adding a **new feature**, the PR's description includes:
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.8.1'
23
+ * @member {String} version='6.8.3'
24
24
  */
25
- version: '6.8.1'
25
+ version: '6.8.3'
26
26
  }
27
27
 
28
28
  /**
@@ -22,7 +22,7 @@ class HistoricalDataTable extends Container {
22
22
  * @member {Object} columnDefaults
23
23
  */
24
24
  columnDefaults: {
25
- align : 'right',
25
+ cellAlign : 'right',
26
26
  defaultSortDirection: 'DESC',
27
27
  renderer : Util.formatNumber
28
28
  },
@@ -30,7 +30,7 @@ class HistoricalDataTable extends Container {
30
30
  * @member {Object[]} columns
31
31
  */
32
32
  columns: [{
33
- align : 'left',
33
+ cellAlign: 'left',
34
34
  dataField: 'date',
35
35
  dock : 'left',
36
36
  text : 'Date',
@@ -28,7 +28,7 @@ class Table extends Container {
28
28
  * @member {Object} columnDefaults
29
29
  */
30
30
  columnDefaults: {
31
- align : 'right',
31
+ cellAlign : 'right',
32
32
  defaultSortDirection: 'DESC',
33
33
  renderer : Util.formatNumber
34
34
  },
@@ -44,7 +44,7 @@ class Table extends Container {
44
44
  renderer : Util.indexRenderer,
45
45
  width : 40
46
46
  }, {
47
- align : 'left',
47
+ cellAlign : 'left',
48
48
  dataField : 'country',
49
49
  defaultSortDirection: 'ASC',
50
50
  dock : 'left',
@@ -0,0 +1,6 @@
1
+ import Viewport from './view/Viewport.mjs';
2
+
3
+ export const onStart = () => Neo.app({
4
+ mainView: Viewport,
5
+ name : 'LearnNeo'
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>LearnNeo</title>
7
+ </head>
8
+ <body>
9
+ <script src="../../src/MicroLoader.mjs" type="module"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,44 @@
1
+ import Model from '../../../src/data/Model.mjs';
2
+
3
+ /**
4
+ * @class LearnNeo.model.Content
5
+ * @extends Neo.data.Model
6
+ */
7
+ class Content extends Model {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='LearnNeo.model.Content'
11
+ * @protected
12
+ */
13
+ className: 'LearnNeo.model.Content',
14
+ /**
15
+ * @member {Object[]} fields
16
+ */
17
+ fields: [{
18
+ name: 'className',
19
+ type: 'String'
20
+ }, {
21
+ name: 'collapsed',
22
+ type: 'Boolean'
23
+ }, {
24
+ name: 'id',
25
+ type: 'Integer'
26
+ }, {
27
+ name: 'isLeaf',
28
+ type: 'Boolean'
29
+ }, {
30
+ name: 'name',
31
+ type: 'String'
32
+ }, {
33
+ name: 'parentId',
34
+ type: 'Integer'
35
+ }, {
36
+ name: 'path',
37
+ type: 'String'
38
+ }]
39
+ }
40
+ }
41
+
42
+ Neo.applyClassConfig(Content);
43
+
44
+ export default Content;
@@ -0,0 +1,6 @@
1
+ {
2
+ "appPath": "apps/learnneo/app.mjs",
3
+ "basePath": "../../",
4
+ "environment": "development",
5
+ "mainPath": "./Main.mjs"
6
+ }
@@ -0,0 +1,24 @@
1
+ import ContentModel from '../model/Content.mjs';
2
+ import Store from '../../../src/data/Store.mjs';
3
+
4
+ /**
5
+ * @class LearnNeo.store.Content
6
+ * @extends Neo.data.Store
7
+ */
8
+ class Content extends Store {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='LearnNeo.store.Content'
12
+ * @protected
13
+ */
14
+ className: 'LearnNeo.store.Content',
15
+ /**
16
+ * @member {Neo.data.Model} model=ContentModel
17
+ */
18
+ model: ContentModel
19
+ }
20
+ }
21
+
22
+ Neo.applyClassConfig(Content);
23
+
24
+ export default Content;
@@ -0,0 +1,34 @@
1
+ import BaseViewport from '../../../src/container/Viewport.mjs';
2
+ import ViewportController from './ViewportController.mjs';
3
+
4
+ /**
5
+ * @class LearnNeo.view.Viewport
6
+ * @extends Neo.container.Viewport
7
+ */
8
+ class Viewport extends BaseViewport {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='LearnNeo.view.Viewport'
12
+ * @protected
13
+ */
14
+ className: 'LearnNeo.view.Viewport',
15
+ /**
16
+ * @member {Neo.controller.Component} controller=ViewportController
17
+ */
18
+ controller: ViewportController,
19
+ /**
20
+ * @member {Object[]} items
21
+ */
22
+ items: [{
23
+ module: () => import('./home/MainContainer.mjs')
24
+ }],
25
+ /**
26
+ * @member {Object} layout={ntype:'card'}
27
+ */
28
+ layout: {ntype: 'card'}
29
+ }
30
+ }
31
+
32
+ Neo.applyClassConfig(Viewport);
33
+
34
+ export default Viewport;
@@ -0,0 +1,19 @@
1
+ import Component from '../../../src/controller/Component.mjs';
2
+
3
+ /**
4
+ * @class LearnNeo.view.ViewportController
5
+ * @extends Neo.controller.Component
6
+ */
7
+ class ViewportController extends Component {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='LearnNeo.view.ViewportController'
11
+ * @protected
12
+ */
13
+ className: 'LearnNeo.view.ViewportController'
14
+ }
15
+ }
16
+
17
+ Neo.applyClassConfig(ViewportController);
18
+
19
+ export default ViewportController;
@@ -0,0 +1,41 @@
1
+ import ContentStore from '../../store/Content.mjs'
2
+ import TreeList from '../../../../src/tree/List.mjs';
3
+
4
+ /**
5
+ * @class LearnNeo.view.home.ContentTreeList
6
+ * @extends Neo.container.Base
7
+ */
8
+ class ContentTreeList extends TreeList {
9
+ static config = {
10
+ /**
11
+ * @member {String} className='LearnNeo.view.home.ContentTreeList'
12
+ * @protected
13
+ */
14
+ className: 'LearnNeo.view.home.ContentTreeList',
15
+ /**
16
+ * @member {Neo.data.Store} store=ContentStore
17
+ */
18
+ store: ContentStore
19
+ }
20
+
21
+ /**
22
+ * todo: createItems() should get triggered onStoreLoad()
23
+ */
24
+ onConstructed() {
25
+ super.onConstructed();
26
+
27
+ let me = this;
28
+
29
+ Neo.Xhr.promiseJson({
30
+ url: '../../../resources/data/learnneo/content.json'
31
+ }).then(data => {
32
+ me.store.data = data.json.data;
33
+ me.createItems(null, me.getListItemsRoot(), 0);
34
+ me.update();
35
+ })
36
+ }
37
+ }
38
+
39
+ Neo.applyClassConfig(ContentTreeList);
40
+
41
+ export default ContentTreeList;
@@ -0,0 +1,50 @@
1
+ import Container from '../../../../src/container/Base.mjs';
2
+ import ContentTreeList from './ContentTreeList.mjs';
3
+ import MainContainerController from './MainContainerController.mjs';
4
+ import Splitter from '../../../../src/component/Splitter.mjs';
5
+
6
+ /**
7
+ * @class LearnNeo.view.home.MainContainer
8
+ * @extends Neo.container.Base
9
+ */
10
+ class MainContainer extends Container {
11
+ static config = {
12
+ /**
13
+ * @member {String} className='LearnNeo.view.home.MainContainer'
14
+ * @protected
15
+ */
16
+ className: 'LearnNeo.view.home.MainContainer',
17
+ /**
18
+ * @member {Neo.controller.Component} controller=MainContainerController
19
+ */
20
+ controller: MainContainerController,
21
+ /**
22
+ * @member {Object[]} items
23
+ */
24
+ items: [{
25
+ module: Container,
26
+ layout: 'fit',
27
+ width : 300,
28
+
29
+ items: [{
30
+ module : ContentTreeList,
31
+ listeners: {leafItemClick: 'onContentListLeafClick'}
32
+ }]
33
+ }, {
34
+ module : Splitter,
35
+ resizeTarget: 'previous'
36
+ }, {
37
+ module : Container,
38
+ layout : {ntype: 'card', activeIndex: null},
39
+ reference: 'content-container'
40
+ }],
41
+ /**
42
+ * @member {Object} layout={ntype:'hbox',align:'stretch'}
43
+ */
44
+ layout: {ntype: 'hbox', align: 'stretch'}
45
+ }
46
+ }
47
+
48
+ Neo.applyClassConfig(MainContainer);
49
+
50
+ export default MainContainer;
@@ -0,0 +1,28 @@
1
+ import Component from '../../../../src/controller/Component.mjs';
2
+
3
+ /**
4
+ * @class LearnNeo.view.home.MainContainerController
5
+ * @extends Neo.controller.Component
6
+ */
7
+ class MainContainerController extends Component {
8
+ static config = {
9
+ /**
10
+ * @member {String} className='LearnNeo.view.home.MainContainerController'
11
+ * @protected
12
+ */
13
+ className: 'LearnNeo.view.home.MainContainerController'
14
+ }
15
+
16
+ /**
17
+ * @param {Object} record
18
+ */
19
+ onContentListLeafClick(record) {
20
+ let contentContainer = this.getReference('content-container');
21
+
22
+ console.log('onContentListLeafClick', {contentContainer, record});
23
+ }
24
+ }
25
+
26
+ Neo.applyClassConfig(MainContainerController);
27
+
28
+ export default MainContainerController;
@@ -22,7 +22,7 @@ class HistoricalDataTable extends Container {
22
22
  * @member {Object} columnDefaults
23
23
  */
24
24
  columnDefaults: {
25
- align : 'right',
25
+ cellAlign : 'right',
26
26
  defaultSortDirection: 'DESC',
27
27
  renderer : Util.formatNumber
28
28
  },
@@ -30,7 +30,7 @@ class HistoricalDataTable extends Container {
30
30
  * @member {Object[]} columns
31
31
  */
32
32
  columns: [{
33
- align : 'left',
33
+ cellAlign: 'left',
34
34
  dataField: 'date',
35
35
  dock : 'left',
36
36
  text : 'Date',
@@ -28,7 +28,7 @@ class Table extends Container {
28
28
  * @member {Object} columnDefaults
29
29
  */
30
30
  columnDefaults: {
31
- align : 'right',
31
+ cellAlign : 'right',
32
32
  defaultSortDirection: 'DESC',
33
33
  renderer : Util.formatNumber
34
34
  },
@@ -44,7 +44,7 @@ class Table extends Container {
44
44
  renderer : Util.indexRenderer,
45
45
  width : 40
46
46
  }, {
47
- align : 'left',
47
+ cellAlign : 'left',
48
48
  dataField : 'country',
49
49
  defaultSortDirection: 'ASC',
50
50
  dock : 'left',
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='6.8.1'
23
+ * @member {String} version='6.8.3'
24
24
  */
25
- version: '6.8.1'
25
+ version: '6.8.3'
26
26
  }
27
27
 
28
28
  /**
@@ -111,6 +111,12 @@ class MainContainer extends ConfigurationViewport {
111
111
  labelText: 'placeholderText',
112
112
  listeners: {change: me.onConfigChange.bind(me, 'placeholderText')},
113
113
  value : me.exampleComponent.placeholderText
114
+ }, {
115
+ module : CheckBox,
116
+ checked : me.exampleComponent.readOnly,
117
+ labelText: 'readOnly',
118
+ listeners: {change: me.onConfigChange.bind(me, 'readOnly')},
119
+ style : {marginTop: '10px'}
114
120
  }, {
115
121
  module : CheckBox,
116
122
  checked : me.exampleComponent.typeAhead,
@@ -23,7 +23,7 @@ class GridContainer extends BaseGridContainer {
23
23
  * @member {Object} columnDefaults
24
24
  */
25
25
  columnDefaults: {
26
- align : 'right',
26
+ cellAlign : 'right',
27
27
  defaultSortDirection: 'DESC',
28
28
  renderer : Util.formatNumber,
29
29
  width : 100
@@ -40,7 +40,7 @@ class GridContainer extends BaseGridContainer {
40
40
  renderer: Util.indexRenderer,
41
41
  width : 40
42
42
  }, {
43
- align : 'left',
43
+ cellAlign : 'left',
44
44
  defaultSortDirection: 'ASC',
45
45
  dock : 'left',
46
46
  field : 'country',
@@ -23,7 +23,7 @@ class TableContainer extends BaseTableContainer {
23
23
  * @member {Object} columnDefaults
24
24
  */
25
25
  columnDefaults: {
26
- align : 'right',
26
+ cellAlign : 'right',
27
27
  defaultSortDirection: 'DESC',
28
28
  renderer : Util.formatNumber
29
29
  },
@@ -39,7 +39,7 @@ class TableContainer extends BaseTableContainer {
39
39
  renderer : Util.indexRenderer,
40
40
  width : 40
41
41
  }, {
42
- align : 'left',
42
+ cellAlign : 'left',
43
43
  dataField : 'country',
44
44
  defaultSortDirection: 'ASC',
45
45
  dock : 'left',
@@ -17,9 +17,9 @@ class MainComponent extends Component {
17
17
  width : 300,
18
18
 
19
19
  /**
20
- * @member {Object[]} data
20
+ * @member {Object[]} items
21
21
  */
22
- data: [
22
+ items: [
23
23
  {id: 1, done: true, text: 'Todo Item 1'},
24
24
  {id: 2, done: false, text: 'Todo Item 2'},
25
25
  {id: 3, done: false, text: 'Todo Item 3'}
@@ -51,14 +51,14 @@ class MainComponent extends Component {
51
51
  {input: me.onInputFieldChange, delegate: 'todo-input'}
52
52
  ]);
53
53
 
54
- me.createItems(me.data || []);
54
+ me.createItems(me.items || []);
55
55
  }
56
56
 
57
- createItems(data) {
57
+ createItems(items) {
58
58
  let me = this,
59
59
  cls;
60
60
 
61
- data.forEach(item => {
61
+ items.forEach(item => {
62
62
  cls = ['todo-item'];
63
63
 
64
64
  if (item.done) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "6.8.1",
3
+ "version": "6.8.3",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -56,7 +56,7 @@
56
56
  "neo-jsdoc": "1.0.1",
57
57
  "neo-jsdoc-x": "1.0.5",
58
58
  "postcss": "^8.4.31",
59
- "sass": "^1.68.0",
59
+ "sass": "^1.69.1",
60
60
  "showdown": "^2.1.0",
61
61
  "webpack": "^5.88.2",
62
62
  "webpack-cli": "^5.1.4",
@@ -0,0 +1,27 @@
1
+ {
2
+ "data": [{
3
+ "id" : 1,
4
+ "isLeaf" : false,
5
+ "name" : "Hello",
6
+ "parentId": null,
7
+ "path" : null
8
+ }, {
9
+ "id" : 2,
10
+ "isLeaf" : true,
11
+ "name" : "Max",
12
+ "parentId": 1,
13
+ "path" : null
14
+ }, {
15
+ "id" : 3,
16
+ "isLeaf" : false,
17
+ "name" : "Hello",
18
+ "parentId": null,
19
+ "path" : null
20
+ }, {
21
+ "id" : 4,
22
+ "isLeaf" : true,
23
+ "name" : "Torsten",
24
+ "parentId": 3,
25
+ "path" : null
26
+ }]
27
+ }
@@ -16,10 +16,15 @@
16
16
  }
17
17
  }
18
18
 
19
+ // a hack since FF does no longer allow to hide the calendar icon post v109
19
20
  @-moz-document url-prefix() {
20
21
  .neo-datefield {
22
+ .neo-field-trigger {
23
+ background-color: var(--textfield-input-background-color);
24
+ }
25
+
21
26
  .neo-textfield-input {
22
- clip-path: inset(0 2em 0 0);
27
+ margin-right: -2.2em !important;
23
28
  }
24
29
  }
25
30
  }
@@ -4,7 +4,6 @@
4
4
 
5
5
  .neo-textfield-input {
6
6
  cursor : pointer;
7
- pointer-events: none;
8
7
  user-select : none;
9
8
  }
10
9
  }
@@ -236,12 +236,12 @@ const DefaultConfig = {
236
236
  useVdomWorker: true,
237
237
  /**
238
238
  * buildScripts/injectPackageVersion.mjs will update this value
239
- * @default '6.8.1'
239
+ * @default '6.8.3'
240
240
  * @memberOf! module:Neo
241
241
  * @name config.version
242
242
  * @type String
243
243
  */
244
- version: '6.8.1'
244
+ version: '6.8.3'
245
245
  };
246
246
 
247
247
  Object.assign(DefaultConfig, {
@@ -376,10 +376,7 @@ class Base extends Panel {
376
376
  me.closeOrHide(false);
377
377
 
378
378
  if (me.closeAction === 'hide') {
379
- await Neo.applyDeltas(appName, [
380
- {id, cls: {remove: ['animated-hiding-showing']}},
381
- {id, action: 'removeNode'}
382
- ])
379
+ await Neo.applyDeltas(appName, {id, action: 'removeNode'})
383
380
  }
384
381
  }
385
382
 
@@ -691,13 +691,17 @@ class FileUpload extends Base {
691
691
 
692
692
  delete vdom.inert;
693
693
 
694
+ let isChangeEventNeeded;
695
+
694
696
  switch (value) {
695
697
  case 'ready':
696
698
  anchor.tag = 'div';
697
699
  anchor.href = '';
700
+ isChangeEventNeeded = true;
698
701
  break;
699
702
  case 'upload-failed':
700
703
  status.innerHTML = `${me.uploadFailed}${isNaN(me.progress) ? '' : `... (${Math.round(me.progress * 100)}%)`}`;
704
+ isChangeEventNeeded = true;
701
705
  break;
702
706
  case 'processing':
703
707
  status.innerHTML = `${me.scanning}... (${me.formatSize(me.uploadSize)})`;
@@ -706,6 +710,7 @@ class FileUpload extends Base {
706
710
  case 'scan-failed':
707
711
  status.innerHTML = `${me.malwareFoundInFile}. \u2022 ${me.fileSize}`;
708
712
  me.error = me.pleaseCheck;
713
+ isChangeEventNeeded = true;
709
714
  break;
710
715
  case 'downloadable':
711
716
  anchor.tag = 'a';
@@ -713,14 +718,20 @@ class FileUpload extends Base {
713
718
  [me.documentIdParameter] : me.documentId
714
719
  });
715
720
  status.innerHTML = me.fileSize;
721
+ isChangeEventNeeded = true;
716
722
  break;
717
723
  case 'not-downloadable':
718
724
  status.innerHTML = me.document ? me.fileSize : `${me.successfullyUploaded} \u2022 ${me.fileSize}`;
725
+ isChangeEventNeeded = true;
719
726
  break;
720
727
  case 'deleted':
721
728
  status.innerHTML = me.fileWasDeleted;
729
+ isChangeEventNeeded = true;
722
730
  }
723
731
 
732
+ if (isChangeEventNeeded) {
733
+ me.fireChangeEvent(me.file)
734
+ }
724
735
  me.validate();
725
736
  me.update();
726
737
 
@@ -32,10 +32,6 @@ class Picker extends Text {
32
32
  * @protected
33
33
  */
34
34
  clientRects: null,
35
- /**
36
- * @member {Boolean} editable_=true
37
- */
38
- editable_: true,
39
35
  /**
40
36
  * Additional used keys for the selection model
41
37
  * @member {Object} keys
@@ -110,19 +106,6 @@ class Picker extends Text {
110
106
  })
111
107
  }
112
108
 
113
- /**
114
- * Triggered after the editable config got changed
115
- * @param {Boolean} value
116
- * @param {Boolean} oldValue
117
- * @protected
118
- */
119
- afterSetEditable(value, oldValue) {
120
- let cls = this.cls;
121
-
122
- NeoArray.toggle(cls, 'neo-not-editable', !value);
123
- this.cls = cls
124
- }
125
-
126
109
  /**
127
110
  * Triggered after the mounted config got changed
128
111
  * @param {Boolean} value
@@ -75,6 +75,15 @@ class Text extends Base {
75
75
  * @member {String[]|null} disabledChars_=null
76
76
  */
77
77
  disabledChars_: null,
78
+ /**
79
+ * Setting `editable` to `false` means that the input field will be read-only
80
+ * but the field is still workable and may have its value changed by user interaction.
81
+ *
82
+ * For example picker fields such as `Date` and `Select` may still have their
83
+ * values changed by selecting from the picker using keyboard or pointer.
84
+ * @member {Boolean} editable_=true
85
+ */
86
+ editable_: true,
78
87
  /**
79
88
  * Configure the value of empty fields. null or an empty string is recommended.
80
89
  * @member {String|null} emptyValue=null
@@ -184,6 +193,10 @@ class Text extends Base {
184
193
  */
185
194
  placeholderText_: null,
186
195
  /**
196
+ * Setting `readOnly` means that the field may not be changed by user interaction.
197
+ *
198
+ * The input field will be read-only and other ways of changing the field's value
199
+ * (such as by operating pickers) will be disabled.
187
200
  * @member {Boolean} readOnly_=false
188
201
  */
189
202
  readOnly_: false,
@@ -342,6 +355,22 @@ class Text extends Base {
342
355
  }
343
356
  }
344
357
 
358
+ /**
359
+ * Triggered after the editable config got changed
360
+ * @param {Boolean} value
361
+ * @param {Boolean} oldValue
362
+ * @protected
363
+ */
364
+ afterSetEditable(value, oldValue) {
365
+ const
366
+ me = this,
367
+ { cls } = me;
368
+
369
+ NeoArray.toggle(cls, 'neo-not-editable', !value);
370
+ me.cls = cls
371
+ me.changeInputElKey('readonly', value ? false : true);
372
+ }
373
+
345
374
  /**
346
375
  * Triggered after the error config got changed
347
376
  * @param {String|null} value
@@ -35,6 +35,13 @@ class Picker extends Base {
35
35
  onTriggerClick(data) {
36
36
  this.field.onPickerTriggerClick();
37
37
  }
38
+
39
+ /**
40
+ * @returns {Boolean} true in case the trigger should be hidden
41
+ */
42
+ getHiddenState() {
43
+ return !this.field.editable;
44
+ }
38
45
  }
39
46
 
40
47
  Neo.applyClassConfig(Picker);
@@ -37,15 +37,17 @@ const
37
37
  isTabbable = e => {
38
38
  const
39
39
  { nodeName } = e,
40
- style = getComputedStyle(e);
40
+ style = getComputedStyle(e),
41
+ tabIndex = e.getAttribute('tabIndex');
41
42
 
43
+ // Hidden elements not tabbable
42
44
  if (style.getPropertyValue('display') === 'none' || style.getPropertyValue('visibility') === 'hidden') {
43
45
  return false;
44
46
  }
45
47
 
46
48
  return focusableTags[nodeName] ||
47
49
  ((nodeName === 'A' || nodeName === 'LINK') && !!e.href) ||
48
- e.getAttribute('tabIndex') != null ||
50
+ (tabIndex != null && Number(tabIndex) >= 0) ||
49
51
  e.contentEditable === 'true';
50
52
  };
51
53
 
@@ -98,8 +98,8 @@ class View extends Component {
98
98
  rendererOutput = ''
99
99
  }
100
100
 
101
- if (column.align !== 'left') {
102
- cellCls.push('neo-' + column.align)
101
+ if (column.cellAlign !== 'left') {
102
+ cellCls.push('neo-' + column.cellAlign)
103
103
  }
104
104
 
105
105
  if (!cellId) {