neo.mjs 10.0.2 → 10.1.1
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/.github/RELEASE_NOTES/v10.1.0.md +17 -0
- package/.github/RELEASE_NOTES/v10.1.1.md +13 -0
- package/ServiceWorker.mjs +2 -2
- package/apps/portal/index.html +1 -1
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/package.json +1 -1
- package/src/DefaultConfig.mjs +2 -2
- package/src/collection/Base.mjs +17 -12
- package/src/grid/Body.mjs +1 -1
- package/src/main/DeltaUpdates.mjs +0 -4
- package/src/selection/Model.mjs +12 -2
- package/src/util/vdom/TreeBuilder.mjs +1 -1
- package/src/vdom/Helper.mjs +2 -1
- package/test/siesta/tests/CollectionBase.mjs +46 -0
@@ -0,0 +1,17 @@
|
|
1
|
+
**Neo.mjs v10.1.0 Release Notes**
|
2
|
+
|
3
|
+
This release includes important fixes and minor improvements, primarily focusing on VDOM optimization, collection performance, and selection model improvements.
|
4
|
+
|
5
|
+
**Key Updates:**
|
6
|
+
|
7
|
+
* **VDOM Optimization & `neo-ignore` Refinement (Addresses #7114):**
|
8
|
+
The internal handling of the `neo-ignore` flag has been significantly improved. Previously, `neo-ignore` could interfere with the VDOM's structural integrity, potentially leading to rendering anomalies, **especially in complex layouts like grids, where elements might overlap**. Now, a dedicated `neoIgnore: true` flag is added to VDOM nodes, preserving the original `componentId` and `id`. This ensures the VDOM engine can correctly identify and skip sub-trees during diffing, leading to more robust and efficient asymmetric VDOM updates and resolving visual rendering issues **such as those observed in grid components**.
|
9
|
+
|
10
|
+
* **Collection Performance Enhancements (Addresses #7115):**
|
11
|
+
Several methods within `src/collection/Base.mjs` (e.g., `clear`, `findBy`, `pop`, `remove`) have been optimized by replacing `this.getCount()` method calls with direct access to `this.count`. This change streamlines collection operations, leading to improved performance.
|
12
|
+
|
13
|
+
* **Enhanced Selection Model (Addresses #7116):**
|
14
|
+
The `src/selection/Model.mjs` has been updated to provide more comprehensive data during selection changes. A new `getController()` method has been added, and the `selectionChange` event now includes the `records` array, offering richer context for selection handling and better integration with view controllers.
|
15
|
+
|
16
|
+
* **Minor Fixes & Improvements (Addresses #7117):**
|
17
|
+
This release also includes minor cleanups and documentation improvements, such as an updated JSDoc comment in `src/grid/Body.mjs` and the removal of a debug `console.log` statement in `src/main/DeltaUpdates.mjs`.
|
@@ -0,0 +1,13 @@
|
|
1
|
+
# Neo.mjs v10.1.1 Release Notes
|
2
|
+
|
3
|
+
This is a patch release that addresses a critical regression bug affecting drag-and-drop operations in grids.
|
4
|
+
|
5
|
+
## Bug Fixes
|
6
|
+
|
7
|
+
### Grid Column Drag & Drop
|
8
|
+
|
9
|
+
- Fixed a bug in `Neo.collection.Base` where the `move()` method would fail to correctly swap adjacent items. This was caused by an unsafe, nested `splice()` operation that could lead to unpredictable behavior.
|
10
|
+
- The direct impact of this bug was the failure of drag-and-drop for grid column reordering, which is a significant regression.
|
11
|
+
- The `move()` method has been refactored to use a safer, two-step approach, ensuring the stability of all collection-based move operations.
|
12
|
+
|
13
|
+
This fix restores the expected drag-and-drop functionality for grid columns.
|
package/ServiceWorker.mjs
CHANGED
package/apps/portal/index.html
CHANGED
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name" : "neo.mjs",
|
3
|
-
"version" : "10.
|
3
|
+
"version" : "10.1.1",
|
4
4
|
"description" : "Neo.mjs: The multi-threaded UI framework for building ultra-fast, desktop-like web applications with uncompromised responsiveness, inherent security, and a transpilation-free dev mode.",
|
5
5
|
"type" : "module",
|
6
6
|
"repository" : {
|
package/src/DefaultConfig.mjs
CHANGED
@@ -299,12 +299,12 @@ const DefaultConfig = {
|
|
299
299
|
useVdomWorker: true,
|
300
300
|
/**
|
301
301
|
* buildScripts/injectPackageVersion.mjs will update this value
|
302
|
-
* @default '10.
|
302
|
+
* @default '10.1.1'
|
303
303
|
* @memberOf! module:Neo
|
304
304
|
* @name config.version
|
305
305
|
* @type String
|
306
306
|
*/
|
307
|
-
version: '10.
|
307
|
+
version: '10.1.1'
|
308
308
|
};
|
309
309
|
|
310
310
|
Object.assign(DefaultConfig, {
|
package/src/collection/Base.mjs
CHANGED
@@ -412,7 +412,7 @@ class Collection extends Base {
|
|
412
412
|
* Removes all items and clears the map
|
413
413
|
*/
|
414
414
|
clear() {
|
415
|
-
this.splice(0, this.
|
415
|
+
this.splice(0, this.count)
|
416
416
|
}
|
417
417
|
|
418
418
|
/**
|
@@ -429,7 +429,7 @@ class Collection extends Base {
|
|
429
429
|
clearSilent() {
|
430
430
|
let me = this;
|
431
431
|
|
432
|
-
me._items.splice(0, me.
|
432
|
+
me._items.splice(0, me.count);
|
433
433
|
me.map.clear()
|
434
434
|
}
|
435
435
|
|
@@ -817,10 +817,10 @@ class Collection extends Base {
|
|
817
817
|
* @param {Object} fn.item The current collection item
|
818
818
|
* @param {Object} scope=this The scope in which the passed function gets executed
|
819
819
|
* @param {Number} start=0 The start index
|
820
|
-
* @param {Number} end=this.
|
820
|
+
* @param {Number} end=this.count The end index (up to, last value excluded)
|
821
821
|
* @returns {Array} Returns an empty Array in case no items are found
|
822
822
|
*/
|
823
|
-
findBy(fn, scope=this, start=0, end=this.
|
823
|
+
findBy(fn, scope=this, start=0, end=this.count) {
|
824
824
|
let me = this,
|
825
825
|
items = [],
|
826
826
|
i = start;
|
@@ -1041,7 +1041,7 @@ class Collection extends Base {
|
|
1041
1041
|
* @returns {Object}
|
1042
1042
|
*/
|
1043
1043
|
last() {
|
1044
|
-
return this._items[this.
|
1044
|
+
return this._items[this.count -1]
|
1045
1045
|
}
|
1046
1046
|
|
1047
1047
|
/**
|
@@ -1051,16 +1051,21 @@ class Collection extends Base {
|
|
1051
1051
|
*/
|
1052
1052
|
move(fromIndex, toIndex) {
|
1053
1053
|
if (fromIndex === toIndex) {
|
1054
|
-
return
|
1054
|
+
return;
|
1055
1055
|
}
|
1056
1056
|
|
1057
|
-
let
|
1057
|
+
let items = this._items;
|
1058
1058
|
|
1059
1059
|
if (fromIndex >= items.length) {
|
1060
1060
|
fromIndex = items.length - 1
|
1061
1061
|
}
|
1062
1062
|
|
1063
|
-
|
1063
|
+
// The splice operations are intentionally separated.
|
1064
|
+
// Using the common one-liner `items.splice(toIndex, 0, items.splice(fromIndex, 1)[0])`
|
1065
|
+
// can lead to unpredictable side effects, as the inner splice can alter the array
|
1066
|
+
// before the outer splice's index is resolved. This two-step approach is safer.
|
1067
|
+
const item = items.splice(fromIndex, 1)[0];
|
1068
|
+
items.splice(toIndex, 0, item)
|
1064
1069
|
}
|
1065
1070
|
|
1066
1071
|
/**
|
@@ -1102,7 +1107,7 @@ class Collection extends Base {
|
|
1102
1107
|
* @returns {Object} The removed element from the collection; undefined if the collection is empty.
|
1103
1108
|
*/
|
1104
1109
|
pop() {
|
1105
|
-
let mutation = this.splice(this.
|
1110
|
+
let mutation = this.splice(this.count -1, 1);
|
1106
1111
|
return mutation.removedItems[0]
|
1107
1112
|
}
|
1108
1113
|
|
@@ -1122,7 +1127,7 @@ class Collection extends Base {
|
|
1122
1127
|
*/
|
1123
1128
|
remove(key) {
|
1124
1129
|
this.splice(0, Array.isArray(key) ? key : [key]);
|
1125
|
-
return this.
|
1130
|
+
return this.count
|
1126
1131
|
}
|
1127
1132
|
|
1128
1133
|
/**
|
@@ -1132,7 +1137,7 @@ class Collection extends Base {
|
|
1132
1137
|
*/
|
1133
1138
|
removeAt(index) {
|
1134
1139
|
this.splice(index, 1);
|
1135
|
-
return this.
|
1140
|
+
return this.count
|
1136
1141
|
}
|
1137
1142
|
|
1138
1143
|
/**
|
@@ -1310,7 +1315,7 @@ class Collection extends Base {
|
|
1310
1315
|
*/
|
1311
1316
|
unshift(item) {
|
1312
1317
|
this.splice(0, 0, item);
|
1313
|
-
return this.
|
1318
|
+
return this.count
|
1314
1319
|
}
|
1315
1320
|
}
|
1316
1321
|
|
package/src/grid/Body.mjs
CHANGED
@@ -954,7 +954,7 @@ class GridBody extends Component {
|
|
954
954
|
/**
|
955
955
|
* @param {Object} data
|
956
956
|
* @param {Object[]} data.fields Each field object contains the keys: name, oldValue, value
|
957
|
-
* @param {Neo.data.Model} data.model
|
957
|
+
* @param {Neo.data.Model} data.model The model instance of the changed record
|
958
958
|
* @param {Object} data.record
|
959
959
|
*/
|
960
960
|
onStoreRecordChange({fields, record}) {
|
@@ -370,10 +370,6 @@ class DeltaUpdates extends Base {
|
|
370
370
|
let me = this,
|
371
371
|
node = DomAccess.getElementOrBody(delta.id);
|
372
372
|
|
373
|
-
if (!node) {
|
374
|
-
console.log('node not found', delta.id);
|
375
|
-
}
|
376
|
-
|
377
373
|
if (node) {
|
378
374
|
Object.entries(delta).forEach(([prop, value]) => {
|
379
375
|
switch (prop) {
|
package/src/selection/Model.mjs
CHANGED
@@ -157,6 +157,14 @@ class Model extends Base {
|
|
157
157
|
super.destroy(...args)
|
158
158
|
}
|
159
159
|
|
160
|
+
/**
|
161
|
+
* Important for mapping listeners to view controllers
|
162
|
+
* @returns {Neo.controller.Component|null}
|
163
|
+
*/
|
164
|
+
getController() {
|
165
|
+
return this.view.getController()
|
166
|
+
}
|
167
|
+
|
160
168
|
/**
|
161
169
|
* @returns {Array} this.items
|
162
170
|
*/
|
@@ -226,8 +234,9 @@ class Model extends Base {
|
|
226
234
|
items = [items]
|
227
235
|
}
|
228
236
|
|
229
|
-
let me
|
230
|
-
{view}
|
237
|
+
let me = this,
|
238
|
+
{view} = me,
|
239
|
+
records = [...items]; // Potential records
|
231
240
|
|
232
241
|
// We hold vdom ids for now, so all incoming selections must be converted.
|
233
242
|
items = items.map(item => item.isRecord ? view.getItemId(item) : Neo.isObject(item) ? item.id : item);
|
@@ -256,6 +265,7 @@ class Model extends Base {
|
|
256
265
|
view.onSelect?.(items);
|
257
266
|
|
258
267
|
me.fire('selectionChange', {
|
268
|
+
records,
|
259
269
|
selection: itemCollection
|
260
270
|
})
|
261
271
|
}
|
@@ -51,7 +51,7 @@ class TreeBuilder extends Base {
|
|
51
51
|
if (currentItem.componentId) {
|
52
52
|
// Prune the branch only if we are at the boundary AND the child is not part of a merged update
|
53
53
|
if (depth === 1 && !mergedChildIds?.has(currentItem.componentId)) {
|
54
|
-
output[childKey].push({
|
54
|
+
output[childKey].push({...currentItem, neoIgnore: true});
|
55
55
|
return // Stop processing this branch
|
56
56
|
}
|
57
57
|
// Expand the branch if it's part of a merged update, or if the depth requires it
|
package/src/vdom/Helper.mjs
CHANGED
@@ -255,7 +255,8 @@ class Helper extends Base {
|
|
255
255
|
// Case 2: Both nodes are placeholders for the same component
|
256
256
|
(childNode.componentId && childNode.componentId === oldChildNode.componentId)
|
257
257
|
)) {
|
258
|
-
if (childNode.
|
258
|
+
if (childNode.neoIgnore) {
|
259
|
+
delete childNode.neoIgnore;
|
259
260
|
continue
|
260
261
|
}
|
261
262
|
|
@@ -275,4 +275,50 @@ StartTest(t => {
|
|
275
275
|
{id: 'e'}
|
276
276
|
], 'collection.getRange()');
|
277
277
|
});
|
278
|
+
|
279
|
+
t.it('Move collection items', t => {
|
280
|
+
let moveCollection = Neo.create(Collection, {
|
281
|
+
items: [
|
282
|
+
{id: 'a'},
|
283
|
+
{id: 'b'},
|
284
|
+
{id: 'c'},
|
285
|
+
{id: 'd'},
|
286
|
+
{id: 'e'}
|
287
|
+
]
|
288
|
+
});
|
289
|
+
|
290
|
+
t.isDeeplyStrict(moveCollection.getRange(), [{id: 'a'}, {id: 'b'}, {id: 'c'}, {id: 'd'}, {id: 'e'}], 'Initial order');
|
291
|
+
|
292
|
+
// Move item forward
|
293
|
+
moveCollection.move(1, 2);
|
294
|
+
t.isDeeplyStrict(moveCollection.getRange(), [{id: 'a'}, {id: 'c'}, {id: 'b'}, {id: 'd'}, {id: 'e'}], 'Move item forward (1 -> 2)');
|
295
|
+
|
296
|
+
// Swap adjacent items (backward)
|
297
|
+
moveCollection.move(2, 1);
|
298
|
+
t.isDeeplyStrict(moveCollection.getRange(), [{id: 'a'}, {id: 'b'}, {id: 'c'}, {id: 'd'}, {id: 'e'}], 'Swap adjacent items back (2 -> 1)');
|
299
|
+
|
300
|
+
// Move item backward
|
301
|
+
moveCollection.move(3, 1);
|
302
|
+
t.isDeeplyStrict(moveCollection.getRange(), [{id: 'a'}, {id: 'd'}, {id: 'b'}, {id: 'c'}, {id: 'e'}], 'Move item backward (3 -> 1)');
|
303
|
+
|
304
|
+
// Move item forward
|
305
|
+
moveCollection.move(1, 3);
|
306
|
+
t.isDeeplyStrict(moveCollection.getRange(), [{id: 'a'}, {id: 'b'}, {id: 'c'}, {id: 'd'}, {id: 'e'}], 'Move item forward (1 -> 3)');
|
307
|
+
|
308
|
+
// Swap adjacent items (forward)
|
309
|
+
moveCollection.move(0, 1);
|
310
|
+
t.isDeeplyStrict(moveCollection.getRange(), [{id: 'b'}, {id: 'a'}, {id: 'c'}, {id: 'd'}, {id: 'e'}], 'Swap adjacent items (0 -> 1)');
|
311
|
+
|
312
|
+
// Swap adjacent items (backward)
|
313
|
+
moveCollection.move(1, 0);
|
314
|
+
t.isDeeplyStrict(moveCollection.getRange(), [{id: 'a'}, {id: 'b'}, {id: 'c'}, {id: 'd'}, {id: 'e'}], 'Swap adjacent items back (1 -> 0)');
|
315
|
+
|
316
|
+
// Move to end
|
317
|
+
moveCollection.move(0, 4);
|
318
|
+
t.isDeeplyStrict(moveCollection.getRange(), [{id: 'b'}, {id: 'c'}, {id: 'd'}, {id: 'e'}, {id: 'a'}], 'Move to end (0 -> 4)');
|
319
|
+
|
320
|
+
// Move to start
|
321
|
+
moveCollection.move(4, 0);
|
322
|
+
t.isDeeplyStrict(moveCollection.getRange(), [{id: 'a'}, {id: 'b'}, {id: 'c'}, {id: 'd'}, {id: 'e'}], 'Move to start (4 -> 0)');
|
323
|
+
});
|
278
324
|
});
|