neo.mjs 8.22.0 → 8.23.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.
package/README.md CHANGED
@@ -21,6 +21,11 @@ without compilations. This makes debugging easier and reduces development costs.
21
21
  In case you care about scalability, extensibility or solid architectures, congratulations,
22
22
  you have found the right spot.
23
23
 
24
+ <p align="center">
25
+ <a href="https://youtu.be/pYfM28Pz6_0"><img height="316px" width="400px" src="https://raw.githubusercontent.com/neomjs/pages/master/resources_pub/images/neo33s.png"></a>
26
+ <a href="https://youtu.be/aEA5333WiWY"><img height="316px" width="400px" src="https://raw.githubusercontent.com/neomjs/pages/master/resources_pub/images/neo-movie.png"></a>
27
+ </p>
28
+
24
29
  ## Content
25
30
  1. <a href="#introduction">Introduction</a>
26
31
  2. <a href="#use-cases">Potential Use Cases</a>
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.22.0'
23
+ * @member {String} version='8.23.0'
24
24
  */
25
- version: '8.22.0'
25
+ version: '8.23.0'
26
26
  }
27
27
 
28
28
  /**
@@ -16,7 +16,7 @@
16
16
  "@type": "Organization",
17
17
  "name": "Neo.mjs"
18
18
  },
19
- "datePublished": "2025-02-18",
19
+ "datePublished": "2025-02-21",
20
20
  "publisher": {
21
21
  "@type": "Organization",
22
22
  "name": "Neo.mjs"
@@ -107,7 +107,7 @@ class FooterContainer extends Container {
107
107
  }, {
108
108
  module: Component,
109
109
  cls : ['neo-version'],
110
- html : 'v8.22.0'
110
+ html : 'v8.23.0'
111
111
  }]
112
112
  }],
113
113
  /**
@@ -20,19 +20,19 @@ const themes = [
20
20
  class ConfigurationViewport extends Viewport {
21
21
  static config = {
22
22
  /**
23
- * @member {String} className='Neo.examples.ConfigurationContainer'
23
+ * @member {String} className='Neo.examples.ConfigurationViewport'
24
24
  * @protected
25
25
  */
26
- className: 'Neo.examples.ConfigurationContainer',
26
+ className: 'Neo.examples.ConfigurationViewport',
27
27
  /**
28
28
  * @member {String} ntype='configuration-viewport'
29
29
  * @protected
30
30
  */
31
31
  ntype: 'configuration-viewport',
32
32
  /**
33
- * @member {Object} layout={ntype:'hbox', align:'stretch'}
33
+ * @member {String[]} baseCls=['neo-examples-configuration-viewport','neo-viewport']
34
34
  */
35
- layout: {ntype: 'hbox', align: 'stretch'},
35
+ baseCls: ['neo-examples-configuration-viewport', 'neo-viewport'],
36
36
  /**
37
37
  * @member {Number} configItemLabelWidth=150
38
38
  */
@@ -45,6 +45,10 @@ class ConfigurationViewport extends Viewport {
45
45
  * @member {Number} configPanelFlex=1
46
46
  */
47
47
  configPanelFlex: 1,
48
+ /**
49
+ * @member {Number} configPanelMaxWidth=null
50
+ */
51
+ configPanelMaxWidth: null,
48
52
  /**
49
53
  * @member {Number} configPanelMinWidth=350
50
54
  */
@@ -56,7 +60,11 @@ class ConfigurationViewport extends Viewport {
56
60
  /**
57
61
  * @member {Number} exampleComponentFlex=1
58
62
  */
59
- exampleComponentFlex: 2
63
+ exampleComponentFlex: 2,
64
+ /**
65
+ * @member {Object} layout={ntype:'hbox', align:'stretch'}
66
+ */
67
+ layout: {ntype: 'hbox', align: 'stretch'}
60
68
  }
61
69
 
62
70
  /**
@@ -114,6 +122,7 @@ class ConfigurationViewport extends Viewport {
114
122
 
115
123
  me.items = [{
116
124
  module: Container,
125
+ cls : ['neo-example-container'],
117
126
  items : [me.exampleComponent],
118
127
  flex : me.exampleComponentFlex,
119
128
  layout: 'base',
@@ -123,11 +132,16 @@ class ConfigurationViewport extends Viewport {
123
132
  module: Panel,
124
133
  cls : ['neo-panel', 'neo-container', 'neo-configuration-panel'],
125
134
  flex : me.configPanelFlex,
126
- style : {margin: '20px', minWidth: me.configPanelMinWidth},
135
+
136
+ style: {
137
+ maxWidth: me.configPanelMaxWidth + 'px',
138
+ margin : '20px',
139
+ minWidth: me.configPanelMinWidth + 'px'
140
+ },
127
141
 
128
142
  headers: [{
143
+ cls : ['neo-configuration-header-toolbar'],
129
144
  dock : 'top',
130
- style: {borderLeft:0, borderRight:0, borderTop:0},
131
145
  items: [{
132
146
  ntype: 'label',
133
147
  text : 'Configuration'
@@ -145,7 +159,6 @@ class ConfigurationViewport extends Viewport {
145
159
  items: [{
146
160
  module: Container,
147
161
  layout: {ntype: 'vbox', align: null},
148
- style : {overflowY: 'auto', padding: '10px'},
149
162
  cls : ['neo-configuration-panel-body'],
150
163
  itemDefaults: {
151
164
  clearToOriginalValue: true,
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='8.22.0'
23
+ * @member {String} version='8.23.0'
24
24
  */
25
- version: '8.22.0'
25
+ version: '8.23.0'
26
26
  }
27
27
 
28
28
  /**
@@ -0,0 +1,90 @@
1
+ import CheckBox from '../../../src/form/field/CheckBox.mjs';
2
+ import ConfigurationViewport from '../../ConfigurationViewport.mjs';
3
+ import MagicMoveText from '../../../src/component/MagicMoveText.mjs';
4
+ import NumberField from '../../../src/form/field/Number.mjs';
5
+ import TextField from '../../../src/form/field/Text.mjs';
6
+
7
+ /**
8
+ * @class Neo.examples.component.magicmovetext.MainContainer
9
+ * @extends Neo.examples.ConfigurationViewport
10
+ */
11
+ class MainContainer extends ConfigurationViewport {
12
+ static config = {
13
+ className : 'Neo.examples.component.magicmovetext.MainContainer',
14
+ configItemLabelWidth: 150,
15
+ configItemWidth : 250,
16
+ configPanelMaxWidth : 300,
17
+ configPanelMinWidth : 300,
18
+ layout : {ntype: 'hbox', align: 'stretch'}
19
+ }
20
+
21
+ createConfigurationComponents() {
22
+ let me = this,
23
+ {exampleComponent} = me;
24
+
25
+ return [{
26
+ module : CheckBox,
27
+ checked : exampleComponent.autoCycle,
28
+ labelText: 'autoCycle',
29
+ listeners: {change: me.onConfigChange.bind(me, 'autoCycle')}
30
+ }, {
31
+ module : NumberField,
32
+ clearable: false,
33
+ labelText: 'autoCycleInterval',
34
+ listeners: {change: me.onConfigChange.bind(me, 'autoCycleInterval')},
35
+ maxValue : 10000,
36
+ minValue : 0,
37
+ stepSize : 1000,
38
+ style : {marginTop: '10px'},
39
+ value : exampleComponent.autoCycleInterval
40
+ }, {
41
+ module : TextField,
42
+ labelText: 'colorFadeIn',
43
+ listeners: {change: me.onConfigChange.bind(me, 'colorFadeIn')},
44
+ value : exampleComponent.colorFadeIn
45
+ }, {
46
+ module : TextField,
47
+ labelText: 'colorFadeOut',
48
+ listeners: {change: me.onConfigChange.bind(me, 'colorFadeOut')},
49
+ value : exampleComponent.colorFadeOut
50
+ }, {
51
+ module : TextField,
52
+ labelText: 'colorMove',
53
+ listeners: {change: me.onConfigChange.bind(me, 'colorMove')},
54
+ value : exampleComponent.colorMove
55
+ }, {
56
+ module : TextField,
57
+ clearable: false,
58
+ labelText: 'fontFamily',
59
+ listeners: {change: me.onConfigChange.bind(me, 'fontFamily')},
60
+ value : exampleComponent.fontFamily,
61
+ width : 280
62
+ }, {
63
+ module : NumberField,
64
+ clearable: false,
65
+ labelText: 'transitionTime',
66
+ listeners: {change: me.onConfigChange.bind(me, 'transitionTime')},
67
+ maxValue : 900,
68
+ minValue : 50,
69
+ stepSize : 50,
70
+ value : exampleComponent.transitionTime
71
+ }]
72
+ }
73
+
74
+ createExampleComponent() {
75
+ return {
76
+ module: MagicMoveText,
77
+
78
+ cycleTexts: [
79
+ 'Magic Move',
80
+ 'Move characters like magic',
81
+ 'Animate between strings',
82
+ 'Just like that',
83
+ 'Simple to use',
84
+ 'Would you use it?'
85
+ ]
86
+ }
87
+ }
88
+ }
89
+
90
+ export default Neo.setupClass(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.component.magicmovetext'
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>Neo Magic Move - Text</title>
7
+ </head>
8
+ <body>
9
+ <script src="../../../src/MicroLoader.mjs" type="module"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "appPath" : "examples/component/magicmovetext/app.mjs",
3
+ "basePath" : "../../../",
4
+ "environment": "development",
5
+ "mainPath" : "./Main.mjs"
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.22.0",
3
+ "version": "8.23.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -61,7 +61,7 @@
61
61
  "monaco-editor": "0.50.0",
62
62
  "neo-jsdoc": "1.0.1",
63
63
  "neo-jsdoc-x": "1.0.5",
64
- "postcss": "^8.5.2",
64
+ "postcss": "^8.5.3",
65
65
  "sass": "^1.85.0",
66
66
  "siesta-lite": "5.5.2",
67
67
  "url": "^0.11.4",
@@ -0,0 +1,45 @@
1
+ .neo-magic-move-text {
2
+ --neo-height : 100px;
3
+ --neo-transition-time: 500ms;
4
+ --neo-width : 450px;
5
+
6
+ align-items : center;
7
+ background-color: #000;
8
+ color : #fff;
9
+ display : flex;
10
+ font-size : 30px;
11
+ font-weight : bold;
12
+ height : var(--neo-height);
13
+ justify-content : center;
14
+ width : var(--neo-width);
15
+
16
+ .neo-content {
17
+ height : 100%;
18
+ position: relative;
19
+ width : 100%;
20
+
21
+ > * {
22
+ position : absolute;
23
+ transition:
24
+ color 0.2s ease-out,
25
+ left var(--neo-transition-time) ease-out,
26
+ opacity var(--neo-transition-time) ease-out;
27
+ }
28
+ }
29
+
30
+ .neo-measure-element-wrapper {
31
+ align-items : center;
32
+ background-color: #000;
33
+ display : flex;
34
+ height : var(--neo-height);
35
+ justify-content: center;
36
+ left : -5000px;
37
+ position : absolute;
38
+ top : -5000px;
39
+ width : var(--neo-width);
40
+ }
41
+
42
+ .neo-measure-element {
43
+ position: relative;
44
+ }
45
+ }
@@ -0,0 +1,27 @@
1
+ .neo-examples-configuration-viewport {
2
+ transition: width .5s ease-in-out;
3
+
4
+ @media (max-width: 450px) {
5
+ overflow-y: auto;
6
+
7
+ .neo-example-container {
8
+ flex: none !important;
9
+ }
10
+
11
+ &.neo-flex-container {
12
+ &.neo-flex-direction-row {
13
+ flex-direction: column;
14
+ }
15
+ }
16
+ }
17
+
18
+ .neo-configuration-header-toolbar {
19
+ border : 0;
20
+ border-bottom: 1px solid var(--panel-border-color);
21
+ }
22
+
23
+ .neo-configuration-panel-body {
24
+ overflow-y: auto;
25
+ padding : 10px;
26
+ }
27
+ }
@@ -263,12 +263,12 @@ const DefaultConfig = {
263
263
  useVdomWorker: true,
264
264
  /**
265
265
  * buildScripts/injectPackageVersion.mjs will update this value
266
- * @default '8.22.0'
266
+ * @default '8.23.0'
267
267
  * @memberOf! module:Neo
268
268
  * @name config.version
269
269
  * @type String
270
270
  */
271
- version: '8.22.0'
271
+ version: '8.23.0'
272
272
  };
273
273
 
274
274
  Object.assign(DefaultConfig, {
@@ -0,0 +1,306 @@
1
+ import Component from '../component/Base.mjs';
2
+
3
+ /**
4
+ * Deeply inspired by https://github.com/yangshun 's video on LinkedIn
5
+ * as well as Apple's Keynote Magic Move effect
6
+ * @class Neo.component.MagicMoveText
7
+ * @extends Neo.component.Base
8
+ */
9
+ class MagicMoveText extends Component {
10
+ static config = {
11
+ /**
12
+ * @member {String} className='Neo.component.MagicMoveText'
13
+ * @protected
14
+ */
15
+ className: 'Neo.component.MagicMoveText',
16
+ /**
17
+ * @member {String} ntype='magic-move-text'
18
+ * @protected
19
+ */
20
+ ntype: 'magic-move-text',
21
+ /**
22
+ * @member {Boolean} autoCycle_=true
23
+ */
24
+ autoCycle_: true,
25
+ /**
26
+ * @member {Number} autoCycleInterval_=2000
27
+ */
28
+ autoCycleInterval_: 2000,
29
+ /**
30
+ * @member {String[]} baseCls=['neo-magic-move-text']
31
+ * @protected
32
+ */
33
+ baseCls: ['neo-magic-move-text'],
34
+ /**
35
+ * @member {String|null} colorMove=null
36
+ */
37
+ colorMove: null,
38
+ /**
39
+ * @member {String|null} colorFadeIn=null
40
+ */
41
+ colorFadeIn: null,
42
+ /**
43
+ * @member {String|null} colorFadeOut=null
44
+ */
45
+ colorFadeOut: null,
46
+ /**
47
+ * @member {String[]|null} cycleTexts=null
48
+ */
49
+ cycleTexts: null,
50
+ /**
51
+ * @member {String} fontFamily_='Helvetica Neue'
52
+ */
53
+ fontFamily_: 'Helvetica Neue',
54
+ /**
55
+ * @member {String} text_=null
56
+ */
57
+ text_: null,
58
+ /**
59
+ * Time in ms for the fadeIn, fadeOut and move character OPs
60
+ * @member {Number} transitionTime_=500
61
+ */
62
+ transitionTime_: 500,
63
+ /**
64
+ * @member {Object} _vdom
65
+ */
66
+ _vdom:
67
+ {style: {}, cn: [
68
+ {cls: ['neo-content'], cn: []},
69
+ {cls: ['neo-measure-element-wrapper'], removeDom: true, cn: [
70
+ {cls: ['neo-measure-element'], cn:[]}
71
+ ]}
72
+ ]}
73
+ }
74
+
75
+ /**
76
+ * @member {Object[]} chars=[]
77
+ */
78
+ chars = []
79
+ /**
80
+ * @member {Number} currentIndex=0
81
+ */
82
+ currentIndex = 0
83
+ /**
84
+ * @member {Number|null} intervalId=null
85
+ */
86
+ intervalId = null
87
+ /**
88
+ * @member {Object[]} previousChars=[]
89
+ */
90
+ previousChars = []
91
+ /**
92
+ * @member {Object} measureElement
93
+ */
94
+ get measureElement() {
95
+ return this.vdom.cn[1].cn[0]
96
+ }
97
+
98
+ /**
99
+ * Triggered after the autoCycle config got changed
100
+ * @param {Boolean} value
101
+ * @param {Boolean} oldValue
102
+ * @protected
103
+ */
104
+ afterSetAutoCycle(value, oldValue) {
105
+ this.mounted && this.startAutoCycle(value)
106
+ }
107
+
108
+ /**
109
+ * Triggered after the autoCycleInterval config got changed
110
+ * @param {Number} value
111
+ * @param {Number} oldValue
112
+ * @protected
113
+ */
114
+ afterSetAutoCycleInterval(value, oldValue) {
115
+ let me = this;
116
+
117
+ if (oldValue && me.mounted) {
118
+ me.startAutoCycle(false);
119
+ me.startAutoCycle()
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Triggered after the fontFamily config got changed
125
+ * @param {String} value
126
+ * @param {String} oldValue
127
+ * @protected
128
+ */
129
+ afterSetFontFamily(value, oldValue) {
130
+ this.vdom.style.fontFamily = value;
131
+ this.update()
132
+ }
133
+
134
+ /**
135
+ * Triggered after the mounted config got changed
136
+ * @param {Boolean} value
137
+ * @param {Boolean} oldValue
138
+ * @protected
139
+ */
140
+ afterSetMounted(value, oldValue) {
141
+ super.afterSetMounted(value, oldValue);
142
+ this.autoCycle && this.startAutoCycle(value)
143
+ }
144
+
145
+ /**
146
+ * Triggered after the text config got changed
147
+ * @param {String} value
148
+ * @param {String} oldValue
149
+ * @returns {Promise<void>}
150
+ * @protected
151
+ */
152
+ async afterSetText(value, oldValue) {
153
+ let me = this,
154
+ {measureElement} = me;
155
+
156
+ if (oldValue) {
157
+ me.previousChars = me.chars
158
+ }
159
+
160
+ if (value) {
161
+ me.chars = [];
162
+ measureElement.cn = [];
163
+
164
+ value?.split('').forEach(char => {
165
+ me.chars.push({name: char});
166
+
167
+ if (char === ' ') {
168
+ char = '&nbsp;'
169
+ }
170
+
171
+ measureElement.cn.push({tag: 'span', html: char})
172
+ });
173
+
174
+ if (me.mounted) {
175
+ await me.measureChars()
176
+ }
177
+
178
+ await me.updateChars()
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Triggered after the transitionTime config got changed
184
+ * @param {Number} value
185
+ * @param {Number} oldValue
186
+ * @protected
187
+ */
188
+ afterSetTransitionTime(value, oldValue) {
189
+ this.vdom.style['--neo-transition-time'] = value + 'ms';
190
+ this.update()
191
+ }
192
+
193
+ /**
194
+ * @returns {Promise<void>}
195
+ */
196
+ async measureChars() {
197
+ let me = this,
198
+ {measureElement} = me,
199
+ parentRect, rects;
200
+
201
+ delete me.vdom.cn[1].removeDom;
202
+
203
+ await me.promiseUpdate();
204
+ await me.timeout(20);
205
+
206
+ rects = await me.getDomRect([me.vdom.cn[1].id, ...measureElement.cn.map(node => node.id)]);
207
+ parentRect = rects.shift();
208
+
209
+ rects.forEach((rect, index) => {
210
+ me.chars[index].left = `${rect.left - parentRect.left}px`;
211
+ me.chars[index].top = `${rect.top - parentRect.top }px`;
212
+ });
213
+
214
+ me.vdom.cn[1].removeDom = true;
215
+ await me.promiseUpdate()
216
+ }
217
+
218
+ /**
219
+ * @param {Boolean} start=true
220
+ */
221
+ startAutoCycle(start=true) {
222
+ let me = this;
223
+
224
+ if (start) {
225
+ me.intervalId = setInterval(() => {
226
+ me.text = me.cycleTexts[me.currentIndex];
227
+ me.currentIndex = (me.currentIndex + 1) % me.cycleTexts.length
228
+ }, me.autoCycleInterval)
229
+
230
+ me.text && me.measureChars()
231
+ } else {
232
+ clearInterval(me.intervalId)
233
+ }
234
+ }
235
+
236
+ /**
237
+ * @returns {Promise<void>}
238
+ */
239
+ async updateChars() {
240
+ let me = this,
241
+ {chars, previousChars} = me,
242
+ charsContainer = me.vdom.cn[0].cn,
243
+ letters = chars.map(char => char.name),
244
+ char, charNode, index;
245
+
246
+ previousChars.forEach((previousChar, previousIndex) => {
247
+ index = letters.indexOf(previousChar.name);
248
+
249
+ if (index > -1) {
250
+ charNode = charsContainer[previousIndex];
251
+
252
+ charNode.style.color = me.colorMove;
253
+ charNode.style.left = chars[index].left;
254
+ letters[index] = null
255
+ } else {
256
+ charNode = charsContainer[previousIndex];
257
+
258
+ charNode.flag = 'remove'
259
+ }
260
+ });
261
+
262
+ letters.forEach((letter, index) => {
263
+ if (letter !== null) {
264
+ char = chars[index];
265
+
266
+ charsContainer.push({
267
+ html : char.name,
268
+ style: {color: me.colorFadeIn, left: char.left, opacity: 0, top: char.top}
269
+ })
270
+ }
271
+ });
272
+
273
+ await me.promiseUpdate();
274
+
275
+ charsContainer.forEach(charNode => {
276
+ if (charNode.flag === 'remove') {
277
+ charNode.style.color = me.colorFadeOut;
278
+ charNode.style.opacity = 0
279
+ } else {
280
+ charNode.style.opacity = 1
281
+ }
282
+ });
283
+
284
+ await me.promiseUpdate();
285
+ await me.timeout(me.transitionTime);
286
+
287
+ charsContainer.sort((a, b) => parseFloat(a.style.left) - parseFloat(b.style.left));
288
+
289
+ index = charsContainer.length - 1;
290
+
291
+ for (; index >= 0; index--) {
292
+ charNode = charsContainer[index];
293
+
294
+ delete charNode.flag;
295
+ delete charNode.style.color;
296
+
297
+ if (charNode.style.opacity === 0) {
298
+ charsContainer.splice(index, 1)
299
+ }
300
+ }
301
+
302
+ await me.promiseUpdate()
303
+ }
304
+ }
305
+
306
+ export default Neo.setupClass(MagicMoveText);
@@ -330,6 +330,18 @@ class Store extends Base {
330
330
  return this.keyProperty || this.model.keyProperty
331
331
  }
332
332
 
333
+ /**
334
+ * Convenience shortcut to check for int based keyProperties
335
+ * @returns {String|null} lowercase value of the model field type
336
+ */
337
+ getKeyType() {
338
+ let me = this,
339
+ {model} = me,
340
+ keyField = model?.getField(me.getKeyProperty());
341
+
342
+ return keyField?.type?.toLowerCase() || null
343
+ }
344
+
333
345
  /**
334
346
  * @param {Object} opts={}
335
347
  * @param {Object} opts.data
@@ -522,7 +522,7 @@ class GridContainer extends BaseContainer {
522
522
  if (!me.initialResizeEvent) {
523
523
  await me.passSizeToView(true);
524
524
 
525
- me.view.updateVisibleColumns();
525
+ me.view.updateMountedAndVisibleColumns();
526
526
 
527
527
  await me.headerToolbar.passSizeToView()
528
528
  } else {
package/src/grid/View.mjs CHANGED
@@ -81,6 +81,12 @@ class GridView extends Component {
81
81
  * @member {Object} keys
82
82
  */
83
83
  keys: {},
84
+ /**
85
+ * Stores the indexes of the first & last mounted columns, including bufferColumnRange
86
+ * @member {Number[]} mountedColumns_=[0,0]
87
+ * @protected
88
+ */
89
+ mountedColumns_: [0, 0],
84
90
  /**
85
91
  * Stores the indexes of the first & last mounted rows, including bufferRowRange
86
92
  * @member {Number[]} mountedRows=[0,0]
@@ -118,10 +124,10 @@ class GridView extends Component {
118
124
  store_: null,
119
125
  /**
120
126
  * Stores the indexes of the first & last painted columns
121
- * @member {Number[]} visibleColumns_=[0,0]
127
+ * @member {Number[]} visibleColumns=[0,0]
122
128
  * @protected
123
129
  */
124
- visibleColumns_: [0, 0],
130
+ visibleColumns: [0, 0],
125
131
  /**
126
132
  * Stores the indexes of the first & last visible rows, excluding bufferRowRange
127
133
  * @member {Number[]} visibleRows=[0,0]
@@ -229,9 +235,7 @@ class GridView extends Component {
229
235
  * @protected
230
236
  */
231
237
  afterSetAvailableRows(value, oldValue) {
232
- if (value > 0) {
233
- this.createViewData()
234
- }
238
+ value > 0 && this.createViewData()
235
239
  }
236
240
 
237
241
  /**
@@ -277,9 +281,7 @@ class GridView extends Component {
277
281
  * @protected
278
282
  */
279
283
  afterSetContainerWidth(value, oldValue) {
280
- if (value > 0) {
281
- this.updateVisibleColumns()
282
- }
284
+ value > 0 && this.updateMountedAndVisibleColumns()
283
285
  }
284
286
 
285
287
  /**
@@ -305,6 +307,16 @@ class GridView extends Component {
305
307
  this.toggleCls('neo-is-scrolling', value)
306
308
  }
307
309
 
310
+ /**
311
+ * Triggered after the mountedColumns config got changed
312
+ * @param {Number[]} value
313
+ * @param {Number[]} oldValue
314
+ * @protected
315
+ */
316
+ afterSetMountedColumns(value, oldValue) {
317
+ oldValue !== undefined && this.createViewData()
318
+ }
319
+
308
320
  /**
309
321
  * Triggered after the rowHeight config got changed
310
322
  * @param {Number} value
@@ -327,7 +339,7 @@ class GridView extends Component {
327
339
  newStartIndex;
328
340
 
329
341
  if (value.x !== oldValue?.x) {
330
- me.updateVisibleColumns()
342
+ me.updateMountedAndVisibleColumns()
331
343
  }
332
344
 
333
345
  if (value.y !== oldValue?.y) {
@@ -362,18 +374,6 @@ class GridView extends Component {
362
374
  oldValue !== undefined && this.createViewData()
363
375
  }
364
376
 
365
- /**
366
- * Triggered after the visibleColumns config got changed
367
- * @param {Number[]} value
368
- * @param {Number[]} oldValue
369
- * @protected
370
- */
371
- afterSetVisibleColumns(value, oldValue) {
372
- if (oldValue !== undefined) {
373
- this.createViewData()
374
- }
375
- }
376
-
377
377
  /**
378
378
  * @param {Object} data
379
379
  * @param {String} [data.cellId]
@@ -516,12 +516,12 @@ class GridView extends Component {
516
516
  }
517
517
 
518
518
  let me = this,
519
- {bufferColumnRange, selectedRows, visibleColumns} = me,
519
+ {mountedColumns, selectedRows} = me,
520
520
  gridContainer = me.parent,
521
521
  columns = gridContainer.headerToolbar.items,
522
522
  id = me.getRowId(record, rowIndex),
523
523
  rowCls = me.getRowClass(record, rowIndex),
524
- config, column, columnPosition, endIndex, gridRow, i, startIndex;
524
+ config, column, columnPosition, gridRow, i;
525
525
 
526
526
  if (rowIndex % 2 !== 0) {
527
527
  rowCls.push('neo-even')
@@ -549,10 +549,7 @@ class GridView extends Component {
549
549
  }
550
550
  };
551
551
 
552
- endIndex = Math.min(columns.length - 1, visibleColumns[1] + bufferColumnRange);
553
- startIndex = Math.max(0, visibleColumns[0] - bufferColumnRange);
554
-
555
- for (i=startIndex; i <= endIndex; i++) {
552
+ for (i=mountedColumns[0]; i <= mountedColumns[1]; i++) {
556
553
  column = columns[i];
557
554
  config = me.applyRendererOutput({column, columnIndex: i, record, rowIndex});
558
555
 
@@ -593,7 +590,7 @@ class GridView extends Component {
593
590
  me.availableRows < 1 ||
594
591
  me._containerWidth < 1 || // we are not checking me.containerWidth, since we want to ignore the config symbol
595
592
  me.columnPositions.getCount() < 1 ||
596
- me.visibleColumns[1] < 1
593
+ me.mountedColumns[1] < 1
597
594
  ) {
598
595
  return
599
596
  }
@@ -729,6 +726,7 @@ class GridView extends Component {
729
726
 
730
727
  /**
731
728
  * Get the matching record by passing a row id, a cell id or an id inside a grid cell.
729
+ * Limited to mounted rows (must be inside the vdom).
732
730
  * @param {String} nodeId
733
731
  * @returns {Object|null}
734
732
  */
@@ -743,7 +741,7 @@ class GridView extends Component {
743
741
 
744
742
  parentNodes = VDomUtil.getParentNodes(me.vdom, nodeId);
745
743
 
746
- for (node of parentNodes) {
744
+ for (node of parentNodes || []) {
747
745
  record = me.getRecordByRowId(node.id);
748
746
 
749
747
  if (record) {
@@ -754,6 +752,22 @@ class GridView extends Component {
754
752
  return null
755
753
  }
756
754
 
755
+ /**
756
+ * @param {String} cellId
757
+ * @returns {Record}
758
+ */
759
+ getRecordByCellId(cellId) {
760
+ let recordId = cellId.split('__')[1],
761
+ {store} = this,
762
+ keyType = store.getKeyType();
763
+
764
+ if (keyType === 'int' || keyType === 'integer') {
765
+ recordId = parseInt(recordId)
766
+ }
767
+
768
+ return store.get(recordId)
769
+ }
770
+
757
771
  /**
758
772
  * @param {String} rowId
759
773
  * @returns {Record}
@@ -761,9 +775,7 @@ class GridView extends Component {
761
775
  getRecordByRowId(rowId) {
762
776
  let recordId = rowId.split('__')[2],
763
777
  {store} = this,
764
- {model} = store,
765
- keyField = model?.getField(store.getKeyProperty()),
766
- keyType = keyField?.type?.toLowerCase();
778
+ keyType = store.getKeyType();
767
779
 
768
780
  if (keyType === 'int' || keyType === 'integer') {
769
781
  recordId = parseInt(recordId)
@@ -1020,16 +1032,45 @@ class GridView extends Component {
1020
1032
  }
1021
1033
 
1022
1034
  /**
1023
- * @param {Boolean} silent=false
1035
+ *
1024
1036
  */
1025
- updateScrollHeight(silent=false) {
1026
- let me = this,
1027
- countRecords = me.store.getCount(),
1028
- {rowHeight} = me;
1037
+ updateMountedAndVisibleColumns() {
1038
+ let me = this,
1039
+ {bufferColumnRange, columnPositions, visibleColumns} = me,
1040
+ {x} = me.scrollPosition,
1041
+ i = 0,
1042
+ len = columnPositions.getCount(),
1043
+ endIndex = len - 1,
1044
+ column, startIndex;
1029
1045
 
1030
- if (countRecords > 0 && rowHeight > 0) {
1031
- me.vdom.cn[0].height = `${(countRecords + 1) * rowHeight}px`;
1032
- !silent && me.update()
1046
+ if (len < 1) {
1047
+ return
1048
+ }
1049
+
1050
+ for (; i < len; i++) {
1051
+ column = columnPositions.getAt(i);
1052
+
1053
+ if (x >= column.x && x <= column.x + column.width) {
1054
+ startIndex = i
1055
+ }
1056
+
1057
+ if (me.containerWidth + x < column.x) {
1058
+ endIndex = i - 1;
1059
+ break
1060
+ }
1061
+ }
1062
+
1063
+ if (
1064
+ Math.abs(startIndex - me.visibleColumns[0]) >= me.bufferColumnRange ||
1065
+ me.visibleColumns[1] < 1 // initial call
1066
+ ) {
1067
+ visibleColumns[0] = startIndex;
1068
+ visibleColumns[1] = endIndex;
1069
+
1070
+ endIndex = Math.min(len - 1, visibleColumns[1] + bufferColumnRange);
1071
+ startIndex = Math.max(0, visibleColumns[0] - bufferColumnRange);
1072
+
1073
+ me.mountedColumns = [startIndex, endIndex]
1033
1074
  }
1034
1075
  }
1035
1076
 
@@ -1053,39 +1094,16 @@ class GridView extends Component {
1053
1094
  }
1054
1095
 
1055
1096
  /**
1056
- *
1097
+ * @param {Boolean} silent=false
1057
1098
  */
1058
- updateVisibleColumns() {
1059
- let me = this,
1060
- {columnPositions} = me,
1061
- {x} = me.scrollPosition,
1062
- i = 0,
1063
- len = columnPositions.getCount(),
1064
- endIndex = len - 1,
1065
- column, startIndex;
1066
-
1067
- if (len < 1) {
1068
- return
1069
- }
1070
-
1071
- for (; i < len; i++) {
1072
- column = columnPositions.getAt(i);
1073
-
1074
- if (x >= column.x && x <= column.x + column.width) {
1075
- startIndex = i
1076
- }
1077
-
1078
- if (me.containerWidth + x < column.x) {
1079
- endIndex = i - 1;
1080
- break
1081
- }
1082
- }
1099
+ updateScrollHeight(silent=false) {
1100
+ let me = this,
1101
+ countRecords = me.store.getCount(),
1102
+ {rowHeight} = me;
1083
1103
 
1084
- if (
1085
- Math.abs(startIndex - me.visibleColumns[0]) >= me.bufferColumnRange ||
1086
- me.visibleColumns[1] < 1 // initial call
1087
- ) {
1088
- me.visibleColumns = [startIndex, endIndex]
1104
+ if (countRecords > 0 && rowHeight > 0) {
1105
+ me.vdom.cn[0].height = `${(countRecords + 1) * rowHeight}px`;
1106
+ !silent && me.update()
1089
1107
  }
1090
1108
  }
1091
1109
  }
@@ -229,7 +229,7 @@ class Toolbar extends BaseToolbar {
229
229
  availableWidth: lastItem.x + lastItem.width - rects[0].x
230
230
  });
231
231
 
232
- !silent && view.updateVisibleColumns()
232
+ !silent && view.updateMountedAndVisibleColumns()
233
233
  }
234
234
  }
235
235
 
@@ -112,10 +112,10 @@ class CellModel extends BaseModel {
112
112
  {view} = me,
113
113
  {store} = view,
114
114
  currentIndex = 0,
115
- dataField, newIndex;
115
+ dataField, newIndex, record;
116
116
 
117
117
  if (me.hasSelection()) {
118
- currentIndex = store.indexOf(view.getRecord(me.items[0]));
118
+ currentIndex = store.indexOf(view.getRecordByCellId(me.items[0]));
119
119
  dataField = view.getDataField(me.items[0])
120
120
  } else {
121
121
  dataField = me.dataFields[0]
@@ -127,7 +127,11 @@ class CellModel extends BaseModel {
127
127
  newIndex += store.getCount()
128
128
  }
129
129
 
130
- me.select(view.getCellId(store.getAt(newIndex), dataField))
130
+ record = store.getAt(newIndex);
131
+
132
+ me.select(view.getCellId(record, dataField));
133
+
134
+ view.scrollByRows(currentIndex, step)
131
135
  }
132
136
 
133
137
  /**
@@ -94,9 +94,9 @@ class RowModel extends BaseModel {
94
94
  rowId = view.getRowId(record);
95
95
 
96
96
  if (rowId) {
97
- view.scrollByRows(currentIndex, step);
98
-
99
97
  me.select(rowId);
98
+
99
+ view.scrollByRows(currentIndex, step);
100
100
  view.fire('select', {record})
101
101
  }
102
102
  }
@@ -1,25 +0,0 @@
1
- .neo-configuration-panel {
2
- transition: width .5s ease-in-out;
3
-
4
- .neo-details-label {
5
- background-color: #323232;
6
- color : #ddd;
7
- font-size : 13px;
8
- margin : 10px;
9
- padding : 10px;
10
- white-space : normal;
11
- }
12
-
13
- .neo-toolbar {
14
- border : 0;
15
- border-bottom: 1px solid var(--panel-border-color);
16
- }
17
- }
18
-
19
- .neo-configuration-panel-body {
20
- overflow-y: scroll;
21
- }
22
-
23
- .neo-examples-tab-component {
24
- background-color: var(--examples-tab-component-background-color);
25
- }