neo.mjs 8.7.0 → 8.8.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 (33) hide show
  1. package/apps/ServiceWorker.mjs +2 -2
  2. package/apps/portal/index.html +1 -1
  3. package/apps/portal/view/home/FooterContainer.mjs +1 -1
  4. package/examples/ServiceWorker.mjs +2 -2
  5. package/examples/grid/nestedRecordFields/EditUserDialog.mjs +154 -0
  6. package/examples/grid/nestedRecordFields/MainModel.mjs +54 -0
  7. package/examples/grid/nestedRecordFields/MainStore.mjs +54 -0
  8. package/examples/grid/nestedRecordFields/Viewport.mjs +152 -0
  9. package/examples/grid/nestedRecordFields/ViewportStateProvider.mjs +62 -0
  10. package/examples/grid/nestedRecordFields/app.mjs +6 -0
  11. package/examples/grid/nestedRecordFields/index.html +11 -0
  12. package/examples/grid/nestedRecordFields/neo-config.json +7 -0
  13. package/examples/table/nestedRecordFields/{MainContainer.mjs → Viewport.mjs} +50 -21
  14. package/examples/table/nestedRecordFields/{MainContainerStateProvider.mjs → ViewportStateProvider.mjs} +5 -5
  15. package/examples/table/nestedRecordFields/app.mjs +3 -3
  16. package/package.json +1 -1
  17. package/resources/scss/src/dialog/Base.scss +4 -0
  18. package/resources/scss/src/grid/View.scss +16 -0
  19. package/resources/scss/src/grid/header/Button.scss +3 -1
  20. package/resources/scss/src/table/View.scss +2 -2
  21. package/resources/scss/src/table/header/Button.scss +1 -2
  22. package/resources/scss/theme-dark/grid/View.scss +2 -0
  23. package/resources/scss/theme-dark/table/View.scss +2 -0
  24. package/resources/scss/theme-light/grid/View.scss +2 -0
  25. package/resources/scss/theme-light/table/View.scss +2 -0
  26. package/resources/scss/theme-neo-light/grid/View.scss +2 -0
  27. package/resources/scss/theme-neo-light/table/View.scss +2 -0
  28. package/src/DefaultConfig.mjs +2 -2
  29. package/src/Neo.mjs +48 -19
  30. package/src/data/RecordFactory.mjs +57 -31
  31. package/src/dialog/Base.mjs +1 -2
  32. package/src/grid/View.mjs +23 -5
  33. package/src/table/View.mjs +1 -1
@@ -5,16 +5,16 @@ import Store from '../../../src/data/Store.mjs';
5
5
  const dataSymbol = Symbol.for('data');
6
6
 
7
7
  /**
8
- * @class Neo.examples.table.nestedRecordFields.MainContainerStateProvider
8
+ * @class Neo.examples.table.nestedRecordFields.ViewportStateProvider
9
9
  * @extends Neo.state.Provider
10
10
  */
11
- class MainContainerStateProvider extends StateProvider {
11
+ class ViewportStateProvider extends StateProvider {
12
12
  static config = {
13
13
  /**
14
- * @member {String} className='Neo.examples.table.nestedRecordFields.MainContainerStateProvider'
14
+ * @member {String} className='Neo.examples.table.nestedRecordFields.ViewportStateProvider'
15
15
  * @protected
16
16
  */
17
- className: 'Neo.examples.table.nestedRecordFields.MainContainerStateProvider',
17
+ className: 'Neo.examples.table.nestedRecordFields.ViewportStateProvider',
18
18
  /**
19
19
  * @member {Object} stores
20
20
  */
@@ -59,4 +59,4 @@ class MainContainerStateProvider extends StateProvider {
59
59
  }
60
60
  }
61
61
 
62
- export default Neo.setupClass(MainContainerStateProvider);
62
+ export default Neo.setupClass(ViewportStateProvider);
@@ -1,6 +1,6 @@
1
- import MainContainer from './MainContainer.mjs';
1
+ import Viewport from './Viewport.mjs';
2
2
 
3
3
  export const onStart = () => Neo.app({
4
- mainView: MainContainer,
4
+ mainView: Viewport,
5
5
  name : 'Neo.examples.table.nestedRecordFields'
6
- });
6
+ })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "8.7.0",
3
+ "version": "8.8.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -77,6 +77,10 @@
77
77
  margin-right: 0.3em;
78
78
  padding : 0;
79
79
 
80
+ &:active {
81
+ border: none !important;
82
+ }
83
+
80
84
  &:last-child {
81
85
  margin-right: 0;
82
86
  }
@@ -22,6 +22,22 @@
22
22
  height : 100%;
23
23
  overflow-x: visible;
24
24
 
25
+ .neo-grid-cell {
26
+ &.neo-is-modified {
27
+ &:after {
28
+ border-color: transparent var(--grid-cell-ismodified-color) transparent transparent;
29
+ border-style: solid;
30
+ border-width: 0 var(--grid-cell-ismodified-size) var(--grid-cell-ismodified-size) 0;
31
+ content : '';
32
+ height : 0;
33
+ position : absolute;
34
+ right : 0;
35
+ top : 0;
36
+ width : 0;
37
+ }
38
+ }
39
+ }
40
+
25
41
  &.neo-is-scrolling * {
26
42
  pointer-events: none !important;
27
43
  }
@@ -2,6 +2,7 @@
2
2
  align-items : center;
3
3
  background-color: var(--grid-header-button-background-color);
4
4
  background-image: var(--grid-header-button-background-image);
5
+ border-radius : 0;
5
6
  border-width : 0;
6
7
  color : var(--grid-header-button-color);
7
8
  cursor : pointer;
@@ -13,7 +14,6 @@
13
14
  height : 29px; // Webkit => Safari can not handle 100%
14
15
  justify-content : flex-end;
15
16
  margin : 0;
16
- padding : 7px 10px 6px;
17
17
  white-space : nowrap;
18
18
 
19
19
  &:not(:last-child) {
@@ -69,6 +69,8 @@
69
69
  }
70
70
 
71
71
  .neo-button-text {
72
+ color : var(--grid-header-button-color);
72
73
  pointer-events: none;
74
+ text-transform: none;
73
75
  }
74
76
  }
@@ -13,9 +13,9 @@
13
13
 
14
14
  &.neo-is-modified {
15
15
  &:after {
16
- border-color: transparent orange transparent transparent;
16
+ border-color: transparent var(--table-cell-ismodified-color) transparent transparent;
17
17
  border-style: solid;
18
- border-width: 0 10px 10px 0;
18
+ border-width: 0 var(--table-cell-ismodified-size) var(--table-cell-ismodified-size) 0;
19
19
  content : '';
20
20
  height : 0;
21
21
  position : absolute;
@@ -1,6 +1,7 @@
1
1
  .neo-table-header-button {
2
2
  align-items : center;
3
3
  background-color: var(--table-header-button-background-color);
4
+ background-image: var(--table-header-button-background-image);
4
5
  border-width : 0;
5
6
  color : var(--table-header-button-color);
6
7
  cursor : pointer;
@@ -15,8 +16,6 @@
15
16
  white-space : nowrap;
16
17
  width : 100% !important;
17
18
 
18
- background-image: var(--table-header-button-background-image);
19
-
20
19
  &.neo-drag-over {
21
20
  background-image: linear-gradient(green, darkgreen);;
22
21
  }
@@ -1,5 +1,7 @@
1
1
  :root .neo-theme-dark { // .neo-grid-view
2
2
  --grid-cell-background-color-hover : #54595c;
3
+ --grid-cell-ismodified-color : orange;
4
+ --grid-cell-ismodified-size : 10px;
3
5
  --grid-cellmodel-selected-cell-background-color : #64B5F6;
4
6
  --grid-cellmodel-selected-cell-color : #2b2b2b;
5
7
  --grid-cellmodel-selected-column-cell-background-color: #4f558a;
@@ -1,5 +1,7 @@
1
1
  :root .neo-theme-dark { // .neo-table-view
2
2
  --table-cell-background-color-hover : #54595c;
3
+ --table-cell-ismodified-color : orange;
4
+ --table-cell-ismodified-size : 10px;
3
5
  --table-cellmodel-selected-cell-background-color : #64B5F6;
4
6
  --table-cellmodel-selected-cell-color : #2b2b2b;
5
7
  --table-cellmodel-selected-column-cell-background-color: #4f558a;
@@ -2,6 +2,8 @@
2
2
 
3
3
  :root .neo-theme-light { // .neo-grid-view
4
4
  --grid-cell-background-color-hover : #{color.adjust(#33343d, $lightness: 70%)};
5
+ --grid-cell-ismodified-color : orange;
6
+ --grid-cell-ismodified-size : 10px;
5
7
  --grid-cellmodel-selected-cell-background-color : #{color.adjust(#64B5F6, $lightness: 22%)};
6
8
  --grid-cellmodel-selected-cell-color : #2b2b2b;
7
9
  --grid-cellmodel-selected-column-cell-background-color: #{color.adjust(#4f558a, $lightness: 52%)};
@@ -2,6 +2,8 @@
2
2
 
3
3
  :root .neo-theme-light { // .neo-table-view
4
4
  --table-cell-background-color-hover : #{color.adjust(#33343d, $lightness: 70%)};
5
+ --table-cell-ismodified-color : orange;
6
+ --table-cell-ismodified-size : 10px;
5
7
  --table-cellmodel-selected-cell-background-color : #{color.adjust(#64B5F6, $lightness: 22%)};
6
8
  --table-cellmodel-selected-cell-color : #2b2b2b;
7
9
  --table-cellmodel-selected-column-cell-background-color: #{color.adjust(#4f558a, $lightness: 52%)};
@@ -2,6 +2,8 @@
2
2
 
3
3
  :root .neo-theme-neo-light { // .neo-grid-view
4
4
  --grid-cell-background-color-hover : #{color.adjust(#33343d, $lightness: 70%)};
5
+ --grid-cell-ismodified-color : orange;
6
+ --grid-cell-ismodified-size : 10px;
5
7
  --grid-cellmodel-selected-cell-background-color : #{color.adjust(#64B5F6, $lightness: 22%)};
6
8
  --grid-cellmodel-selected-cell-color : #2b2b2b;
7
9
  --grid-cellmodel-selected-column-cell-background-color: #{color.adjust(#4f558a, $lightness: 52%)};
@@ -2,6 +2,8 @@
2
2
 
3
3
  :root .neo-theme-neo-light { // .neo-table-view
4
4
  --table-cell-background-color-hover : #{color.adjust(#33343d, $lightness: 70%)};
5
+ --table-cell-ismodified-color : orange;
6
+ --table-cell-ismodified-size : 10px;
5
7
  --table-cellmodel-selected-cell-background-color : #{color.adjust(#64B5F6, $lightness: 22%)};
6
8
  --table-cellmodel-selected-cell-color : #2b2b2b;
7
9
  --table-cellmodel-selected-column-cell-background-color: #{color.adjust(#4f558a, $lightness: 52%)};
@@ -262,12 +262,12 @@ const DefaultConfig = {
262
262
  useVdomWorker: true,
263
263
  /**
264
264
  * buildScripts/injectPackageVersion.mjs will update this value
265
- * @default '8.7.0'
265
+ * @default '8.8.0'
266
266
  * @memberOf! module:Neo
267
267
  * @name config.version
268
268
  * @type String
269
269
  */
270
- version: '8.7.0'
270
+ version: '8.8.0'
271
271
  };
272
272
 
273
273
  Object.assign(DefaultConfig, {
package/src/Neo.mjs CHANGED
@@ -66,10 +66,10 @@ Neo = globalThis.Neo = Object.assign({
66
66
  *
67
67
  * // e.g. Neo.core.Util.isObject => Neo.isObject
68
68
  * @memberOf module:Neo
69
- * @param {Neo|Neo.core.Base} target The target class or singleton Instance or Neo
70
- * @param {Neo.core.Base} namespace The class containing the methods
71
- * @param {Object} config
72
- * @param {Boolean} [bind] set this to true in case you want to bind methods to the "from" namespace
69
+ * @param {Neo|Neo.core.Base} target The target class or singleton Instance or Neo
70
+ * @param {Neo.core.Base} namespace The class containing the methods
71
+ * @param {Object} config
72
+ * @param {Boolean} [bind] set this to true in case you want to bind methods to the "from" namespace
73
73
  * @returns {Object} target
74
74
  */
75
75
  applyFromNs(target, namespace, config, bind) {
@@ -104,7 +104,7 @@ Neo = globalThis.Neo = Object.assign({
104
104
  /**
105
105
  * Copies all keys of defaults into target, in case they don't already exist
106
106
  * @memberOf module:Neo
107
- * @param {Object} target The target object
107
+ * @param {Object} target The target object
108
108
  * @param {Object} defaults The object containing the keys you want to copy
109
109
  * @returns {Object} target
110
110
  */
@@ -120,6 +120,35 @@ Neo = globalThis.Neo = Object.assign({
120
120
  return target
121
121
  },
122
122
 
123
+ /**
124
+ * Assigns a new value to a given nested objects path.
125
+ * It will create the path structure or parts of it, in case it does not exist.
126
+ * @example
127
+ * Neo.assignToNs('annotations.selected', false, record)
128
+ *
129
+ * @memberOf module:Neo
130
+ * @param {String[]|String} path The path string containing dots or an Array of the string parts
131
+ * @param {*} value The new value to assign to the leaf node
132
+ * @param {Object} scope=globalThis Set a different starting point as globalThis
133
+ * @param {Boolean} force=true false will only assign default values (assign if old value === undefined)
134
+ */
135
+ assignToNs(path, value, scope=globalThis, force=true) {
136
+ path = Array.isArray(path) ? path : path.split('.');
137
+
138
+ let key;
139
+
140
+ if (path.length > 1) {
141
+ key = path.pop();
142
+ scope = Neo.ns(path, true, scope)
143
+ } else {
144
+ key = path
145
+ }
146
+
147
+ if (force || scope[key] === undefined) {
148
+ scope[key] = value
149
+ }
150
+ },
151
+
123
152
  /**
124
153
  * Converts kebab-case strings into camel-case
125
154
  * @memberOf module:Neo
@@ -143,7 +172,7 @@ Neo = globalThis.Neo = Object.assign({
143
172
  /**
144
173
  * @memberOf module:Neo
145
174
  * @param {Object|Array|*} obj
146
- * @param {Boolean} deep=false Set this to true in case you want to clone nested objects as well
175
+ * @param {Boolean} deep=false Set this to true in case you want to clone nested objects as well
147
176
  * @param {Boolean} ignoreNeoInstances=false returns existing instances if set to true
148
177
  * @returns {Object|Array|*} the cloned input
149
178
  */
@@ -214,7 +243,7 @@ Neo = globalThis.Neo = Object.assign({
214
243
  * });
215
244
  * @memberOf module:Neo
216
245
  * @param {String|Object|Neo.core.Base} className
217
- * @param {Object} [config]
246
+ * @param {Object} [config]
218
247
  * @returns {Neo.core.Base|null} The new class instance
219
248
  * @tutorial 02_ClassSystem
220
249
  */
@@ -263,7 +292,7 @@ Neo = globalThis.Neo = Object.assign({
263
292
  * Checks if there is a set method for a given property key inside the prototype chain
264
293
  * @memberOf module:Neo
265
294
  * @param {Neo.core.Base} proto The top level prototype of a class
266
- * @param {String} key the property key to test
295
+ * @param {String} key The property key to test
267
296
  * @returns {Boolean}
268
297
  */
269
298
  hasPropertySetter(proto, key) {
@@ -319,9 +348,9 @@ Neo = globalThis.Neo = Object.assign({
319
348
  * // return globalThis.Neo.button.Base;
320
349
  *
321
350
  * @memberOf module:Neo
322
- * @param {Array|String} names The class name string containing dots or an Array of the string parts
323
- * @param {Boolean} create=false Set create to true to create empty objects for non-existing parts
324
- * @param {Object} [scope] Set a different starting point as globalThis
351
+ * @param {String[]|String} names The class name string containing dots or an Array of the string parts
352
+ * @param {Boolean} create=false Set create to true to create empty objects for non-existing parts
353
+ * @param {Object} [scope] Set a different starting point as globalThis
325
354
  * @returns {Object} reference to the toplevel namespace
326
355
  */
327
356
  ns(names, create=false, scope) {
@@ -341,9 +370,9 @@ Neo = globalThis.Neo = Object.assign({
341
370
  /**
342
371
  * Extended version of Neo.ns() which supports mapping into arrays.
343
372
  * @memberOf module:Neo
344
- * @param {Array|String} names The class name string containing dots or an Array of the string parts
345
- * @param {Boolean} create=false Set create to true to create empty objects for non-existing parts
346
- * @param {Object} [scope] Set a different starting point as globalThis
373
+ * @param {Array|String} names The class name string containing dots or an Array of the string parts
374
+ * @param {Boolean} create=false Set create to true to create empty objects for non-existing parts
375
+ * @param {Object} [scope] Set a different starting point as globalThis
347
376
  * @returns {Object} reference to the toplevel namespace
348
377
  */
349
378
  nsWithArrays(names, create=false, scope) {
@@ -383,7 +412,7 @@ Neo = globalThis.Neo = Object.assign({
383
412
  * });
384
413
  * @memberOf module:Neo
385
414
  * @param {String|Object} ntype
386
- * @param {Object} [config]
415
+ * @param {Object} [config]
387
416
  * @returns {Neo.core.Base}
388
417
  * @see {@link module:Neo.create create}
389
418
  */
@@ -575,7 +604,7 @@ const ignoreMixin = [
575
604
 
576
605
  /**
577
606
  * @param {Neo.core.Base} cls
578
- * @param {Array} mixins
607
+ * @param {Array} mixins
579
608
  * @private
580
609
  */
581
610
  function applyMixins(cls, mixins) {
@@ -614,7 +643,7 @@ function applyMixins(cls, mixins) {
614
643
  /**
615
644
  * Creates get / set methods for class configs ending with an underscore
616
645
  * @param {Neo.core.Base} proto
617
- * @param {String} key
646
+ * @param {String} key
618
647
  * @private
619
648
  * @tutorial 02_ClassSystem
620
649
  */
@@ -708,8 +737,8 @@ function autoGenerateGetSet(proto, key) {
708
737
 
709
738
  /**
710
739
  * @param {Boolean} create
711
- * @param {Object} current
712
- * @param {Object} prev
740
+ * @param {Object} current
741
+ * @param {Object} prev
713
742
  * @returns {Object|undefined}
714
743
  */
715
744
  function createArrayNs(create, current, prev) {
@@ -4,6 +4,7 @@ import Model from './Model.mjs';
4
4
 
5
5
  const
6
6
  dataSymbol = Symbol.for('data'),
7
+ isModifiedSymbol = Symbol.for('isModified'),
7
8
  originalDataSymbol = Symbol.for('originalData');
8
9
 
9
10
  let instance;
@@ -31,21 +32,42 @@ class RecordFactory extends Base {
31
32
  singleton: true
32
33
  }
33
34
 
35
+ /**
36
+ * Assigns model based default values to a data object
37
+ * @param {Object} data
38
+ * @param {Neo.data.Model} model
39
+ * @returns {Object}
40
+ */
41
+ assignDefaultValues(data, model) {
42
+ model.fieldsMap.forEach((field, fieldName) => {
43
+ if (Object.hasOwn(field, 'defaultValue')) {
44
+ // We could always use Neo.assignToNs() => the check is just for improving the performance
45
+ if (model.hasNestedFields) {
46
+ Neo.assignToNs(fieldName, field.defaultValue, data, false)
47
+ } else if (data[fieldName] === undefined) {
48
+ data[fieldName] = field.defaultValue
49
+ }
50
+ }
51
+ });
52
+
53
+ return data
54
+ }
55
+
34
56
  /**
35
57
  * @param {Object} data
36
58
  * @param {Object} data.field
37
- * @param {Neo.data.RecordFactory} data.me
38
59
  * @param {Neo.data.Model} data.model
39
60
  * @param {String} data.path=''
61
+ * @param {Object} data.proto
40
62
  */
41
- createField({field, me, model, path=''}) {
63
+ createField({field, model, path='', proto}) {
42
64
  let fieldName = field.name,
43
65
  fieldPath = path === '' ? fieldName : `${path}.${fieldName}`,
44
66
  properties;
45
67
 
46
68
  if (field.fields) {
47
69
  field.fields.forEach(childField => {
48
- this.createField({field: childField, me, model, path: fieldPath})
70
+ this.createField({field: childField, model, path: fieldPath, proto})
49
71
  })
50
72
  } else {
51
73
  properties = {
@@ -68,7 +90,9 @@ class RecordFactory extends Base {
68
90
  if (!Neo.isEqual(value, oldValue)) {
69
91
  instance.setRecordData({fieldName: fieldPath, model, record: me, value});
70
92
 
71
- me._isModified = me.isModified;
93
+ if (!model.trackModifiedFields) {
94
+ me[isModifiedSymbol] = true
95
+ }
72
96
 
73
97
  instance.onRecordChange({
74
98
  fields: [{name: fieldPath, oldValue, value}],
@@ -80,7 +104,7 @@ class RecordFactory extends Base {
80
104
  }
81
105
  };
82
106
 
83
- Object.defineProperties(me, properties)
107
+ Object.defineProperties(proto, properties)
84
108
  }
85
109
  }
86
110
 
@@ -124,10 +148,10 @@ class RecordFactory extends Base {
124
148
  let me = this;
125
149
 
126
150
  if (model.trackModifiedFields) {
127
- return Neo.isEqual(me[dataSymbol], me[originalDataSymbol])
151
+ return !Neo.isEqual(me[dataSymbol], me[originalDataSymbol])
128
152
  }
129
153
 
130
- return me._isModified
154
+ return me[isModifiedSymbol]
131
155
  }
132
156
 
133
157
  /**
@@ -136,13 +160,15 @@ class RecordFactory extends Base {
136
160
  constructor(config) {
137
161
  let me = this;
138
162
 
163
+ config = instance.assignDefaultValues(config, model);
164
+
139
165
  if (model.trackModifiedFields) {
140
166
  me[originalDataSymbol] = {};
141
167
  me.setOriginal(config)
142
168
  }
143
169
 
144
170
  me.setSilent(config); // We do not want to fire change events when constructing
145
- me._isModified = false
171
+ me[isModifiedSymbol] = false
146
172
  }
147
173
 
148
174
  /**
@@ -153,7 +179,7 @@ class RecordFactory extends Base {
153
179
  let me = this;
154
180
 
155
181
  // Check if the field getter does exist
156
- if (!me.__proto__.hasOwnProperty(fieldName)) {
182
+ if (!Object.hasOwn(me.__proto__, fieldName)) {
157
183
  Logger.logError('The record does not contain the field', fieldName, me)
158
184
  }
159
185
 
@@ -177,6 +203,15 @@ class RecordFactory extends Base {
177
203
  return null
178
204
  }
179
205
 
206
+ /**
207
+ * Bulk-update multiple record fields at once
208
+ * @param {Object} fields
209
+ */
210
+ reset(fields) {
211
+ this.setOriginal(fields);
212
+ this.set(fields)
213
+ }
214
+
180
215
  /**
181
216
  * Bulk-update multiple record fields at once
182
217
  * @param {Object} fields
@@ -214,7 +249,7 @@ class RecordFactory extends Base {
214
249
 
215
250
  if (Array.isArray(model.fields)) {
216
251
  model.fields.forEach(field => {
217
- instance.createField({field, me: cls.prototype, model})
252
+ instance.createField({field, model, proto: cls.prototype})
218
253
  })
219
254
  }
220
255
 
@@ -228,23 +263,6 @@ class RecordFactory extends Base {
228
263
  }
229
264
  }
230
265
 
231
- /**
232
- * @param {Object} record
233
- * @returns {Boolean} true in case a change was found
234
- */
235
- isModified(record) {
236
- return record.isModified
237
- }
238
-
239
- /**
240
- * @param {Object} record
241
- * @param {String} fieldName
242
- * @returns {Boolean|null} null in case the model does not use trackModifiedFields, true in case a change was found
243
- */
244
- isModifiedField(record, fieldName) {
245
- return record.isModifiedField(fieldName)
246
- }
247
-
248
266
  /**
249
267
  * Tests if a given object is an instance of a class created by this factory
250
268
  * @param {Object} record
@@ -353,6 +371,10 @@ class RecordFactory extends Base {
353
371
  * @protected
354
372
  */
355
373
  setRecordData({fieldName, model, record, useOriginalData=false, value}) {
374
+ if (useOriginalData && !model.trackModifiedFields) {
375
+ return
376
+ }
377
+
356
378
  let scope = useOriginalData ? originalDataSymbol : dataSymbol;
357
379
 
358
380
  if (model.hasNestedFields && fieldName.includes('.')) {
@@ -378,9 +400,13 @@ class RecordFactory extends Base {
378
400
  * @param {Boolean} data.useOriginalData=false true will apply changes to the originalData symbol
379
401
  */
380
402
  setRecordFields({changedFields=[], fields, model, record, silent=false, useOriginalData=false}) {
381
- let {fieldsMap} = model,
403
+ let {fieldsMap, trackModifiedFields} = model,
382
404
  fieldExists, oldValue;
383
405
 
406
+ if (!trackModifiedFields && useOriginalData) {
407
+ return
408
+ }
409
+
384
410
  Object.entries(fields).forEach(([key, value]) => {
385
411
  fieldExists = fieldsMap.has(key);
386
412
 
@@ -402,8 +428,8 @@ class RecordFactory extends Base {
402
428
  if (!Neo.isEqual(oldValue, value)) {
403
429
  instance.setRecordData({fieldName: key, model, record, useOriginalData, value});
404
430
 
405
- if (!useOriginalData) {
406
- record._isModified = true
431
+ if (!trackModifiedFields && !useOriginalData) {
432
+ record[isModifiedSymbol] = true
407
433
  }
408
434
 
409
435
  changedFields.push({name: key, oldValue, value})
@@ -411,7 +437,7 @@ class RecordFactory extends Base {
411
437
  }
412
438
  });
413
439
 
414
- if (!silent && Object.keys(changedFields).length > 0) {
440
+ if (!silent && !useOriginalData && Object.keys(changedFields).length > 0) {
415
441
  Neo.get(model.storeId)?.onRecordChange({fields: changedFields, model, record})
416
442
  }
417
443
  }
@@ -413,8 +413,7 @@ class Dialog extends Panel {
413
413
 
414
414
  // rendered outside the visible area
415
415
  await me.render(true);
416
-
417
- await me.timeout(30);
416
+ await me.timeout(150);
418
417
 
419
418
  let [dialogRect, bodyRect] = await me.getDomRect([me.id, 'document.body']);
420
419
 
package/src/grid/View.mjs CHANGED
@@ -72,6 +72,10 @@ class GridView extends Component {
72
72
  * @protected
73
73
  */
74
74
  columnPositions_: [],
75
+ /**
76
+ * @member {Boolean} highlightModifiedCells_=false
77
+ */
78
+ highlightModifiedCells_: false,
75
79
  /**
76
80
  * @member {Boolean} isScrolling_=false
77
81
  */
@@ -432,6 +436,12 @@ class GridView extends Component {
432
436
  cellCls.push('neo-' + column.cellAlign)
433
437
  }
434
438
 
439
+ if (me.highlightModifiedCells) {
440
+ if (record.isModifiedField(dataField)) {
441
+ cellCls.push('neo-is-modified')
442
+ }
443
+ }
444
+
435
445
  if (!cellId) {
436
446
  cellId = me.getCellId(record, column.dataField)
437
447
  }
@@ -803,9 +813,9 @@ class GridView extends Component {
803
813
  fieldNames = fields.map(field => field.name),
804
814
  needsUpdate = false,
805
815
  {gridContainer} = me,
806
- {selectionModel} = gridContainer,
816
+ {selectionModel} = gridContainer.view,
807
817
  {vdom} = me,
808
- cellId, cellNode, column, index, scope;
818
+ cellId, cellNode, cellStyle, cellVdom, column, index;
809
819
 
810
820
  if (fieldNames.includes(me.colspanField)) {
811
821
  index = me.store.indexOf(record);
@@ -821,14 +831,22 @@ class GridView extends Component {
821
831
  cellId = me.getCellId(record, field.name);
822
832
  cellNode = VDomUtil.find(vdom, cellId);
823
833
 
824
- // the vdom might not exist yet => nothing to do in this case
834
+ // The vdom might not exist yet => nothing to do in this case
825
835
  if (cellNode?.vdom) {
836
+ cellStyle = cellNode.vdom.style;
826
837
  column = me.getColumn(field.name);
827
838
  index = cellNode.index;
839
+ cellVdom = me.applyRendererOutput({cellId, column, gridContainer, index, record});
828
840
  needsUpdate = true;
829
- scope = column.rendererScope || gridContainer;
830
841
 
831
- cellNode.parentNode.cn[index] = me.applyRendererOutput({cellId, column, gridContainer, index, record})
842
+ // The cell-positioning logic happens outside applyRendererOutput()
843
+ // We need to preserve these styles
844
+ Object.assign(cellVdom.style, {
845
+ left : cellStyle.left,
846
+ width: cellStyle.width
847
+ });
848
+
849
+ cellNode.parentNode.cn[index] = cellVdom
832
850
  }
833
851
  }
834
852
  })
@@ -154,7 +154,7 @@ class View extends Component {
154
154
  cellCls.push('neo-' + column.cellAlign)
155
155
  }
156
156
 
157
- if (me.highlightModifiedCells && me.store.model.trackModifiedFields) {
157
+ if (me.highlightModifiedCells) {
158
158
  if (record.isModifiedField(dataField)) {
159
159
  cellCls.push('neo-is-modified')
160
160
  }