neo.mjs 7.3.0 → 7.5.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.
Files changed (42) hide show
  1. package/README.md +12 -6
  2. package/apps/ServiceWorker.mjs +2 -2
  3. package/apps/portal/index.html +1 -1
  4. package/apps/portal/resources/data/examples_devmode.json +41 -14
  5. package/apps/portal/resources/data/examples_dist_dev.json +41 -14
  6. package/apps/portal/resources/data/examples_dist_prod.json +41 -14
  7. package/apps/portal/view/blog/List.mjs +18 -14
  8. package/apps/portal/view/examples/List.mjs +158 -42
  9. package/apps/portal/view/examples/TabContainer.mjs +4 -3
  10. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  11. package/examples/ServiceWorker.mjs +2 -2
  12. package/examples/table/container/MainStore.mjs +0 -6
  13. package/package.json +4 -2
  14. package/resources/scss/src/apps/portal/blog/List.scss +24 -1
  15. package/resources/scss/src/apps/portal/examples/List.scss +46 -2
  16. package/resources/scss/theme-neo-light/apps/portal/Viewport.scss +3 -0
  17. package/src/DefaultConfig.mjs +2 -2
  18. package/src/Main.mjs +8 -0
  19. package/src/component/Base.mjs +4 -3
  20. package/src/component/DateSelector.mjs +4 -5
  21. package/src/dialog/Base.mjs +3 -1
  22. package/src/form/field/Time.mjs +3 -2
  23. package/src/list/Base.mjs +15 -2
  24. package/src/main/DomAccess.mjs +7 -5
  25. package/src/main/DomEvents.mjs +2 -1
  26. package/src/selection/grid/CellColumnModel.mjs +1 -1
  27. package/src/selection/grid/CellColumnRowModel.mjs +2 -2
  28. package/src/selection/grid/CellModel.mjs +16 -23
  29. package/src/selection/grid/CellRowModel.mjs +1 -1
  30. package/src/selection/grid/ColumnModel.mjs +14 -8
  31. package/src/selection/grid/RowModel.mjs +15 -10
  32. package/src/selection/table/CellColumnModel.mjs +1 -1
  33. package/src/selection/table/CellColumnRowModel.mjs +1 -1
  34. package/src/selection/table/CellModel.mjs +16 -25
  35. package/src/selection/table/CellRowModel.mjs +1 -1
  36. package/src/selection/table/ColumnModel.mjs +16 -10
  37. package/src/selection/table/RowModel.mjs +14 -9
  38. package/src/table/View.mjs +2 -2
  39. package/src/worker/App.mjs +11 -0
  40. package/src/worker/ServiceBase.mjs +14 -5
  41. package/test/siesta/tests/ClassConfigsAndFields.mjs +7 -6
  42. package/test/siesta/tests/ClassSystem.mjs +1 -1
@@ -1,5 +1,5 @@
1
1
  import BaseList from '../../../../src/list/Base.mjs';
2
- import Examples from '../../store/Examples.mjs';
2
+ import VDomUtil from '../../../../src/util/VDom.mjs';
3
3
 
4
4
  /**
5
5
  * @class Portal.view.examples.List
@@ -35,17 +35,14 @@ class List extends BaseList {
35
35
  */
36
36
  environment: 'development',
37
37
  /**
38
- * @member {Neo.data.Store} store=Examples
38
+ * Specify how many example item images to preload when intersecting
39
+ * @member {Number} preloadImages=5
39
40
  */
40
- store: Examples,
41
+ preloadImages: 5,
41
42
  /**
42
- * @member {String|null} storeUrl_=null
43
+ * @member {String} sourceBaseUrl='https://github.com/neomjs/neo/tree/dev/'
43
44
  */
44
- storeUrl_: null,
45
- /**
46
- * @member {String} sourceBaseUrl='https://github.com/neo.mjs/neo/tree/dev/'
47
- */
48
- sourceBaseUrl: 'https://github.com/neo.mjs/neo/tree/dev/',
45
+ sourceBaseUrl: 'https://github.com/neomjs/neo/tree/dev/',
49
46
  /**
50
47
  * @member {Boolean} useWrapperNode=true
51
48
  */
@@ -60,31 +57,9 @@ class List extends BaseList {
60
57
  }
61
58
 
62
59
  /**
63
- * Triggered before the store config gets changed.
64
- * @param {Object|Neo.data.Store} value
65
- * @param {Object|Neo.data.Store} oldValue
66
- * @returns {Neo.data.Store}
67
- * @protected
60
+ * @member {String} imageBasePath
68
61
  */
69
- beforeSetStore(value, oldValue) {
70
- if (value) {
71
- if (value.isClass) {
72
- value = {
73
- module: value,
74
- url : this.storeUrl
75
- };
76
- } else if (Neo.isObject(value)) {
77
- value.url = this.storeUrl;
78
- }
79
- }
80
-
81
- return super.beforeSetStore(value, oldValue);
82
- }
83
-
84
- /**
85
- * @param {Object} record
86
- */
87
- createItemContent(record) {
62
+ get imageBasePath() {
88
63
  let basePath;
89
64
 
90
65
  if (Neo.config.isGitHubPages) {
@@ -97,15 +72,50 @@ class List extends BaseList {
97
72
  basePath = 'https://raw.githubusercontent.com/neomjs/pages/main/resources_pub/website/examples'
98
73
  }
99
74
 
75
+ return basePath
76
+ }
77
+
78
+ /**
79
+ * @param {Object} config
80
+ */
81
+ construct(config) {
82
+ super.construct(config);
83
+
84
+ let me = this;
85
+
86
+ me.addDomListeners({
87
+ intersect: me.onIntersect,
88
+ scope : me
89
+ })
90
+ }
91
+
92
+ /**
93
+ * Triggered after the mounted config got changed
94
+ * @param {Boolean} value
95
+ * @param {Boolean} oldValue
96
+ * @protected
97
+ */
98
+ afterSetMounted(value, oldValue) {
99
+ super.afterSetMounted(value, oldValue);
100
+ value && this.registerIntersectionObserver()
101
+ }
102
+
103
+ /**
104
+ * @param {Object} record
105
+ */
106
+ createItemContent(record) {
107
+ let me = this;
108
+
100
109
  return [
101
- {cls: ['content', 'neo-relative'], removeDom: record.hidden, cn: [
102
- {cls: ['neo-full-size', 'preview-image'], style: {
103
- backgroundImage: `url('${basePath}/${record.image}'), linear-gradient(#777, #333)`}
104
- },
110
+ {cls: ['content', 'neo-relative'], data: {recordId: record.id}, removeDom: me.isHiddenItem(record), cn: [
111
+ {cls: ['neo-multi-window'], data: {neoTooltip: 'Multi Window Demo'}, removeDom: !record.sharedWorkers, cn: [
112
+ {cls: ['far', 'fa-window-restore']}
113
+ ]},
114
+ {cls: ['neo-full-size', 'preview-image'], flag: `image-${record.id}`},
105
115
  {cls: ['neo-absolute', 'neo-item-bottom-position'], cn: [
106
- {...this.createLink(record)},
116
+ {...me.createLink(record)},
107
117
  {cls: ['neo-top-20'], cn: [
108
- {tag: 'a', cls: ['fab fa-github', 'neo-github-image'], href: this.sourceBaseUrl + record.sourceUrl, target: '_blank'},
118
+ {...me.createSourceLink(record)},
109
119
  {cls: ['neo-inner-content'], cn: [
110
120
  {cls: ['neo-inner-details'], html: record.browsers.join(', ')},
111
121
  {cls: ['neo-inner-details'], html: record.environments.join(', ')}
@@ -122,22 +132,50 @@ class List extends BaseList {
122
132
  * @returns {Object}
123
133
  */
124
134
  createLink(record) {
125
- let vdom = {
135
+ let externalLink = record.url.startsWith('http'),
136
+
137
+ vdom = {
126
138
  tag : 'a',
127
139
  cls : ['neo-title'],
128
140
  cn : [{html: record.name.replace(List.nameRegEx, "$1")}],
129
- href: this.baseUrl + record.url
141
+ href: record.url
130
142
  };
131
143
 
144
+ // We can use a shorter syntax for pointing examples to neomjs.com, but not all examples have to be there.
145
+ if (!externalLink) {
146
+ vdom.href = this.baseUrl + record.url
147
+ }
148
+
132
149
  // Do not open multi-window examples inside a new browser window, in case the environment is the same.
133
150
  // E.g. opening the multi-window covid app & the portal app inside the same app worker is problematic.
134
- if (!record.sharedWorkers || this.environment !== Neo.config.environment) {
151
+ if (!record.sharedWorkers || this.environment !== Neo.config.environment || externalLink) {
135
152
  vdom.target = '_blank'
136
153
  }
137
154
 
138
155
  return vdom
139
156
  }
140
157
 
158
+ /**
159
+ *
160
+ * @param {Object} record
161
+ * @returns {Object}
162
+ */
163
+ createSourceLink(record) {
164
+ let vdom = {
165
+ tag : 'a',
166
+ cls : ['fab fa-github', 'neo-github-image'],
167
+ href : record.sourceUrl,
168
+ target: '_blank'
169
+ };
170
+
171
+ // We can use a shorter syntax for pointing examples to neomjs/neo repo, but not all examples have to be there.
172
+ if (!record.sourceUrl.startsWith('http')) {
173
+ vdom.href = this.sourceBaseUrl + record.sourceUrl
174
+ }
175
+
176
+ return vdom
177
+ }
178
+
141
179
  /**
142
180
  * @returns {Object}
143
181
  */
@@ -151,6 +189,84 @@ class List extends BaseList {
151
189
  getVnodeRoot() {
152
190
  return this.vnode.childNodes[0]
153
191
  }
192
+
193
+ /**
194
+ * @param {Object} record
195
+ * @returns {Boolean}
196
+ */
197
+ isHiddenItem(record) {
198
+ if (record.hidden) {
199
+ return true
200
+ }
201
+
202
+ // We only want to show the portal app for the non-current environment.
203
+ // => A link to itself feels pointless
204
+ return record.sourceUrl === 'apps/portal' && this.environment === Neo.config.environment
205
+ }
206
+
207
+ /**
208
+ * @param {Object} data
209
+ */
210
+ onIntersect(data) {
211
+ let me = this,
212
+ {imageBasePath, store} = me,
213
+ record = store.get(parseInt(data.data.recordId)),
214
+ i = store.indexOf(record),
215
+ len = Math.min(i + me.preloadImages, store.getCount()),
216
+ needsUpdate = false,
217
+ node;
218
+
219
+ for (; i < len; i++) {
220
+ node = VDomUtil.getByFlag(me.vdom, `image-${record.id}`);
221
+
222
+ if (!node.style) {
223
+ needsUpdate = true;
224
+
225
+ node.style = {
226
+ backgroundImage: [
227
+ `url('${imageBasePath}/${record.image}'),`,
228
+ 'linear-gradient(',
229
+ 'var(--portal-examples-list-gradient-start),',
230
+ 'var(--portal-examples-list-gradient-end)',
231
+ ')'
232
+ ].join('')
233
+ }
234
+ }
235
+ }
236
+
237
+ needsUpdate && me.update()
238
+ }
239
+
240
+ /**
241
+ *
242
+ */
243
+ async registerIntersectionObserver() {
244
+ let me = this,
245
+ opts = {id: me.id, observe: ['.content'], windowId: me.windowId},
246
+ i = 0,
247
+ len = me.intersectionObserverReconnects,
248
+ data;
249
+
250
+ await me.timeout(150);
251
+
252
+ data = await Neo.main.addon.IntersectionObserver.register({
253
+ ...opts,
254
+ callback: 'isVisible',
255
+ root : `#${me.parentId}`
256
+ });
257
+
258
+ if (data.countTargets < 1) {
259
+ for (; i < len; i++) {
260
+ await me.timeout(100);
261
+
262
+ data = await Neo.main.addon.IntersectionObserver.observe(opts);
263
+
264
+ if (data.countTargets > 0) {
265
+ break
266
+ }
267
+ }
268
+ }
269
+ }
154
270
  }
155
271
 
156
272
  export default Neo.setupClass(List);
@@ -1,4 +1,5 @@
1
1
  import Container from '../../../../src/tab/Container.mjs';
2
+ import ExampleStore from '../../store/Examples.mjs';
2
3
  import List from './List.mjs';
3
4
  import TabContainerController from './TabContainerController.mjs';
4
5
 
@@ -42,7 +43,7 @@ class TabContainer extends Container {
42
43
  */
43
44
  items: [{
44
45
  reference : 'examples-devmode-list',
45
- storeUrl : '../../apps/portal/resources/data/examples_devmode.json',
46
+ store : {module: ExampleStore, url: '../../apps/portal/resources/data/examples_devmode.json'},
46
47
  tabButtonConfig: {
47
48
  iconCls: 'fa fa-chess-knight',
48
49
  route : '/examples/devmode',
@@ -51,7 +52,7 @@ class TabContainer extends Container {
51
52
  }, {
52
53
  environment : 'dist/development',
53
54
  reference : 'examples-dist-dev-list',
54
- storeUrl : '../../apps/portal/resources/data/examples_dist_dev.json',
55
+ store : {module: ExampleStore, url: '../../apps/portal/resources/data/examples_dist_dev.json'},
55
56
  tabButtonConfig: {
56
57
  iconCls: 'fa fa-chess-queen',
57
58
  route : '/examples/dist_dev',
@@ -60,7 +61,7 @@ class TabContainer extends Container {
60
61
  }, {
61
62
  environment : 'dist/production',
62
63
  reference : 'examples-dist-prod-list',
63
- storeUrl : '../../apps/portal/resources/data/examples_dist_prod.json',
64
+ store : {module: ExampleStore, url: '../../apps/portal/resources/data/examples_dist_prod.json'},
64
65
  tabButtonConfig: {
65
66
  iconCls: 'fa fa-chess-king',
66
67
  route : '/examples/dist_prod',
@@ -111,7 +111,7 @@ class FooterContainer extends Container {
111
111
  }, {
112
112
  module: Component,
113
113
  cls : ['neo-version'],
114
- html : 'v7.3.0'
114
+ html : 'v7.5.0'
115
115
  }]
116
116
  }],
117
117
  /**
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='7.3.0'
23
+ * @member {String} version='7.5.0'
24
24
  */
25
- version: '7.3.0'
25
+ version: '7.5.0'
26
26
  }
27
27
 
28
28
  /**
@@ -41,12 +41,6 @@ class MainStore extends Store {
41
41
  firstname: 'Bastian',
42
42
  githubId : 'bhaustein',
43
43
  lastname : 'Haustein'
44
- }, {
45
- colspan : {firstname: 3},
46
- country : 'Germany',
47
- firstname: 'Colspan 3',
48
- githubId : 'random',
49
- lastname : ''
50
44
  }]
51
45
  }
52
46
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "7.3.0",
3
+ "version": "7.5.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -31,6 +31,8 @@
31
31
  "javascript",
32
32
  "frontend",
33
33
  "framework",
34
+ "offscreencanvas",
35
+ "sharedworker",
34
36
  "webworker",
35
37
  "ecmascript",
36
38
  "css",
@@ -57,7 +59,7 @@
57
59
  "monaco-editor": "0.50.0",
58
60
  "neo-jsdoc": "1.0.1",
59
61
  "neo-jsdoc-x": "1.0.5",
60
- "postcss": "^8.4.45",
62
+ "postcss": "^8.4.47",
61
63
  "sass": "^1.78.0",
62
64
  "siesta-lite": "5.5.2",
63
65
  "url": "^0.11.4",
@@ -1,4 +1,12 @@
1
+ @property --portal-blog-list-gradient-start {
2
+ syntax : "<color>";
3
+ initial-value: #777;
4
+ inherits : false;
5
+ }
6
+
1
7
  .portal-blog-list.neo-list {
8
+ --portal-blog-list-gradient-end: #333;
9
+
2
10
  transition: padding var(--portal-transition-duration) ease-out;
3
11
 
4
12
  .neo-list-item {
@@ -13,6 +21,20 @@
13
21
  white-space : normal;
14
22
  width : 100%;
15
23
 
24
+ &:focus {
25
+ outline: none;
26
+
27
+ .preview-image {
28
+ --portal-blog-list-gradient-start: darkgreen;
29
+ }
30
+ }
31
+
32
+ &:hover {
33
+ .preview-image {
34
+ --portal-blog-list-gradient-start: #3E63DD;
35
+ }
36
+ }
37
+
16
38
  .content {
17
39
  flex: 1;
18
40
  }
@@ -75,7 +97,7 @@
75
97
  transition : color 250ms ease-out;
76
98
 
77
99
  &:hover {
78
- color: #5d83a7
100
+ color: #8BA6FF
79
101
  }
80
102
  }
81
103
 
@@ -96,6 +118,7 @@
96
118
  background-position : 50% 50%;
97
119
  background-size : cover;
98
120
  height : 100%;
121
+ transition : --portal-blog-list-gradient-start var(--portal-transition-duration) ease-out;
99
122
  }
100
123
  }
101
124
 
@@ -1,4 +1,12 @@
1
+ @property --portal-examples-list-gradient-start {
2
+ syntax : "<color>";
3
+ initial-value: #777;
4
+ inherits : false;
5
+ }
6
+
1
7
  .portal-examples-list.neo-list {
8
+ --portal-examples-list-gradient-end: #333;
9
+
2
10
  transition: padding var(--portal-transition-duration) ease-out;
3
11
 
4
12
  .neo-list-item {
@@ -12,6 +20,20 @@
12
20
  white-space : normal;
13
21
  width : 100%;
14
22
 
23
+ &:focus {
24
+ outline: none;
25
+
26
+ .preview-image {
27
+ --portal-examples-list-gradient-start: darkgreen;
28
+ }
29
+ }
30
+
31
+ &:hover {
32
+ .preview-image {
33
+ --portal-examples-list-gradient-start: #3E63DD;
34
+ }
35
+ }
36
+
15
37
  .content {
16
38
  flex: 1;
17
39
  }
@@ -29,8 +51,12 @@
29
51
  color : #ddd;
30
52
  text-decoration : none;
31
53
  transition-duration : var(--portal-transition-duration);
32
- transition-property : font-size, height, width;
54
+ transition-property : color, font-size, height, width;
33
55
  transition-timing-function: ease-out;
56
+
57
+ &:hover {
58
+ color: #8BA6FF
59
+ }
34
60
  }
35
61
 
36
62
  .neo-inner-content {
@@ -51,20 +77,37 @@
51
77
  transition: margin var(--portal-transition-duration) ease-out;
52
78
  }
53
79
 
80
+ .neo-multi-window {
81
+ align-items : center;
82
+ background-color: #fff;
83
+ border-radius : 50%;
84
+ box-shadow : 0 5px 10px rgba(0, 0, 0, 0.3);
85
+ color : #3E63DD;
86
+ display : flex;
87
+ height : 2em;
88
+ justify-content : center;
89
+ position : absolute;
90
+ right : 1em;
91
+ top : 1em;
92
+ width : 2em;
93
+ }
94
+
54
95
  .neo-relative {
55
96
  position: relative;
56
97
  }
57
98
 
58
99
  .neo-title {
59
100
  color : #fff;
101
+ display : flex;
60
102
  font-weight : 600;
61
103
  line-height : 1;
62
104
  text-decoration: none;
63
105
  text-shadow : 1px 1px 1px #000, 1px 1px 1px #fff;
64
106
  transition : color 250ms ease-out;
107
+ width : fit-content;
65
108
 
66
109
  &:hover {
67
- color: #5d83a7
110
+ color: #8BA6FF
68
111
  }
69
112
  }
70
113
 
@@ -78,6 +121,7 @@
78
121
  background-blend-mode: overlay;
79
122
  background-size : cover;
80
123
  height : 100%;
124
+ transition : --portal-examples-list-gradient-start var(--portal-transition-duration) ease-out;
81
125
  }
82
126
  }
83
127
 
@@ -0,0 +1,3 @@
1
+ :root .neo-theme-neo-light { // .neo-viewport
2
+ --portal-transition-duration: 300ms;
3
+ }
@@ -262,12 +262,12 @@ const DefaultConfig = {
262
262
  useVdomWorker: true,
263
263
  /**
264
264
  * buildScripts/injectPackageVersion.mjs will update this value
265
- * @default '7.3.0'
265
+ * @default '7.5.0'
266
266
  * @memberOf! module:Neo
267
267
  * @name config.version
268
268
  * @type String
269
269
  */
270
- version: '7.3.0'
270
+ version: '7.5.0'
271
271
  };
272
272
 
273
273
  Object.assign(DefaultConfig, {
package/src/Main.mjs CHANGED
@@ -53,6 +53,7 @@ class Main extends core.Base {
53
53
  'importAddon',
54
54
  'log',
55
55
  'redirectTo',
56
+ 'reloadWindow',
56
57
  'setNeoConfig',
57
58
  'setRoute',
58
59
  'windowClose',
@@ -410,6 +411,13 @@ class Main extends core.Base {
410
411
  this.addon[addon.constructor.name] = addon
411
412
  }
412
413
 
414
+ /**
415
+ * @param {Object} data
416
+ */
417
+ reloadWindow(data) {
418
+ location.reload()
419
+ }
420
+
413
421
  /**
414
422
  * Triggers the different DOM operation queues
415
423
  * @protected
@@ -829,7 +829,7 @@ class Base extends CoreBase {
829
829
  me.alignTo();
830
830
 
831
831
  // Focus will be pushed into the first input field or other focusable item
832
- Neo.main.DomAccess.focus({id, children: true, windowId})
832
+ me.focus(id, true)
833
833
  }
834
834
 
835
835
  me.fire('mounted', me.id)
@@ -1516,9 +1516,10 @@ class Base extends CoreBase {
1516
1516
  /**
1517
1517
  * Calls focus() on the top level DOM node of this component or on a given node via id
1518
1518
  * @param {String} id=this.id
1519
+ * @param {Boolean} children=false
1519
1520
  */
1520
- focus(id=this.id) {
1521
- Neo.main.DomAccess.focus({id, windowId: this.windowId})
1521
+ focus(id=this.id, children=false) {
1522
+ Neo.main.DomAccess.focus({children, id, windowId: this.windowId})
1522
1523
  }
1523
1524
 
1524
1525
  /**
@@ -166,7 +166,8 @@ class DateSelector extends Component {
166
166
  * @protected
167
167
  */
168
168
  afterSetCurrentDate(value, oldValue) {
169
- let me = this,
169
+ let me = this,
170
+ {id, windowId} = me,
170
171
  dayIncrement, method, methodParams, monthIncrement, yearIncrement;
171
172
 
172
173
  if (me.mounted) {
@@ -181,14 +182,12 @@ class DateSelector extends Component {
181
182
  method = 'changeYear';
182
183
  methodParams = [yearIncrement]
183
184
  } else if (dayIncrement !== 0) {
184
- me.selectionModel.select(me.id + '__' + DateUtil.convertToyyyymmdd(value))
185
+ me.selectionModel.select(id + '__' + DateUtil.convertToyyyymmdd(value))
185
186
  }
186
187
 
187
188
  if (method) {
188
189
  if (me.containsFocus) {
189
- Neo.main.DomAccess.focus({
190
- id: me.id
191
- }).then(data => {
190
+ Neo.main.DomAccess.focus({id, windowId}).then(data => {
192
191
  me[method](...methodParams)
193
192
  })
194
193
  } else {
@@ -668,7 +668,9 @@ class Base extends Panel {
668
668
 
669
669
  // we need a reset, otherwise we do not get a change event for the next onDragStart() call
670
670
  me.dragZone.boundaryContainerId = null;
671
- me.isDragging = false
671
+ me.isDragging = false;
672
+
673
+ me.focus(me.id, true)
672
674
  })
673
675
  }
674
676
  }
@@ -294,8 +294,9 @@ class Time extends Picker {
294
294
  let me = this;
295
295
 
296
296
  Neo.main.DomAccess.focus({
297
- appName: me.appName,
298
- id : me.getInputElId()
297
+ appName : me.appName,
298
+ id : me.getInputElId(),
299
+ windowId: me.windowId
299
300
  }).then(() => {
300
301
  callback?.apply(me)
301
302
  })
package/src/list/Base.mjs CHANGED
@@ -465,7 +465,7 @@ class Base extends Component {
465
465
  itemId = me.getItemId(record[me.getKeyProperty()]),
466
466
  {selectionModel} = me,
467
467
  isSelected = !me.disableSelection && selectionModel?.isSelected(itemId),
468
- item;
468
+ item, removeDom;
469
469
 
470
470
  isHeader && cls.push('neo-list-header');
471
471
 
@@ -492,7 +492,7 @@ class Base extends Component {
492
492
  item.tabIndex = -1
493
493
  }
494
494
 
495
- if (record.hidden) {
495
+ if (record.hidden || itemContent.removeDom) {
496
496
  item.removeDom = true
497
497
  }
498
498
 
@@ -507,6 +507,19 @@ class Base extends Component {
507
507
 
508
508
  case 'Array': {
509
509
  item.cn = itemContent;
510
+
511
+ removeDom = true;
512
+
513
+ itemContent.forEach(item => {
514
+ if (!item.removeDom) {
515
+ removeDom = false
516
+ }
517
+ })
518
+
519
+ if (removeDom) {
520
+ item.removeDom = true
521
+ }
522
+
510
523
  break
511
524
  }
512
525
 
@@ -392,15 +392,17 @@ class DomAccess extends Base {
392
392
  /**
393
393
  * Calls focus() on a node for a given dom node id
394
394
  * @param {Object} data
395
+ * @param {Boolean} data.children
396
+ * @param {String} data.id
395
397
  * @returns {Object} obj.id => the passed id
396
398
  */
397
- focus(data) {
398
- let node = this.getElement(data.id);
399
+ focus({children, id}) {
400
+ let node = this.getElement(id);
399
401
 
400
402
  if (node) {
401
403
  // The children property means focus inner elements if possible.
402
- if (!DomUtils.isFocusable(node) && data.children) {
403
- // query for the first focusable decendent
404
+ if (!DomUtils.isFocusable(node) && children) {
405
+ // query for the first focusable descendent
404
406
  node = DomUtils.query(node, DomUtils.isFocusable)
405
407
  }
406
408
 
@@ -413,7 +415,7 @@ class DomAccess extends Base {
413
415
  }
414
416
  }
415
417
 
416
- return {id: data.id}
418
+ return {id}
417
419
  }
418
420
 
419
421
  /**