neo.mjs 10.4.0 → 10.4.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.4.1.md +16 -0
- package/ServiceWorker.mjs +2 -2
- package/apps/portal/view/home/FooterContainer.mjs +1 -1
- package/package.json +1 -1
- package/src/DefaultConfig.mjs +2 -2
- package/src/data/Store.mjs +17 -7
- package/src/grid/Body.mjs +4 -2
- package/src/grid/Container.mjs +13 -8
- package/src/grid/VerticalScrollbar.mjs +7 -6
- package/src/table/Body.mjs +28 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Neo.mjs v10.4.1 Release Notes
|
|
2
|
+
|
|
3
|
+
## Highlights
|
|
4
|
+
|
|
5
|
+
This patch release polishes the "instant preview" feature for grids introduced in v10.4.0, ensuring the grid's scrollbar and accessibility properties are correctly sized from the very first paint when loading large datasets. The non-buffered `table.Body` also receives a major performance boost for clearing data.
|
|
6
|
+
|
|
7
|
+
## Enhancements & Bug Fixes
|
|
8
|
+
|
|
9
|
+
### 1. Polished "Instant Preview" for Large Datasets in Grids
|
|
10
|
+
The chunked loading mechanism for stores and grids has been refined. When adding a large dataset, the initial `load` event now carries the final total record count. The `grid.Container` uses this information immediately to set the correct `aria-rowcount` and to size the vertical scrollbar accurately, preventing layout shifts and providing a more stable and accessible user experience from the very first render.
|
|
11
|
+
|
|
12
|
+
### 2. Standardized Internal Store `load` Event
|
|
13
|
+
To support the "instant preview" refinements, the `data.Store` `load` event payload has been standardized internally. It now consistently uses an object containing an `items` property (e.g., `{items: [...]}`). Framework components have been updated to use this new signature.
|
|
14
|
+
|
|
15
|
+
### 3. Performance Fast Path for `table.Body`
|
|
16
|
+
The non-buffered `table.Body` is now significantly faster when clearing its data. It uses the same "fast path" optimization as `grid.Body`, directly clearing the DOM instead of processing a large and unnecessary VDOM diff when an empty dataset is loaded. This aligns the performance of the two components.
|
package/ServiceWorker.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "neo.mjs",
|
|
3
|
-
"version": "10.4.
|
|
3
|
+
"version": "10.4.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.4.
|
|
302
|
+
* @default '10.4.1'
|
|
303
303
|
* @memberOf! module:Neo
|
|
304
304
|
* @name config.version
|
|
305
305
|
* @type String
|
|
306
306
|
*/
|
|
307
|
-
version: '10.4.
|
|
307
|
+
version: '10.4.1'
|
|
308
308
|
};
|
|
309
309
|
|
|
310
310
|
Object.assign(DefaultConfig, {
|
package/src/data/Store.mjs
CHANGED
|
@@ -144,9 +144,13 @@ class Store extends Base {
|
|
|
144
144
|
|
|
145
145
|
if (items.length > threshold) {
|
|
146
146
|
const me = this,
|
|
147
|
+
total = me.count + items.length,
|
|
147
148
|
chunk = items.splice(0, threshold);
|
|
148
149
|
|
|
149
|
-
|
|
150
|
+
me.chunkingTotal = total;
|
|
151
|
+
|
|
152
|
+
// 1. Add the first chunk. This fires 'mutate' -> 'load' (via onCollectionMutate)
|
|
153
|
+
// and triggers the initial grid render. The 'load' event will contain the final total count.
|
|
150
154
|
super.add(me.createRecord(chunk));
|
|
151
155
|
|
|
152
156
|
// 2. Suspend events to prevent the next 'add' from firing 'load'.
|
|
@@ -158,13 +162,15 @@ class Store extends Base {
|
|
|
158
162
|
// 4. Resume events.
|
|
159
163
|
me.suspendEvents = false;
|
|
160
164
|
|
|
161
|
-
// 5. Manually fire a final 'load' event to update the grid's scrollbar.
|
|
162
|
-
me.fire('load', me.items);
|
|
165
|
+
// 5. Manually fire a final 'load' event to update the grid's scrollbar and notify other listeners.
|
|
166
|
+
me.fire('load', {items: me.items, total: me.chunkingTotal});
|
|
167
|
+
|
|
168
|
+
delete me.chunkingTotal;
|
|
163
169
|
|
|
164
|
-
return me.count
|
|
170
|
+
return me.count;
|
|
165
171
|
}
|
|
166
172
|
|
|
167
|
-
return super.add(this.createRecord(item))
|
|
173
|
+
return super.add(this.createRecord(item));
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
/**
|
|
@@ -447,7 +453,11 @@ class Store extends Base {
|
|
|
447
453
|
let me = this;
|
|
448
454
|
|
|
449
455
|
if (me.isConstructed && !me.isLoading) {
|
|
450
|
-
|
|
456
|
+
if (me.chunkingTotal) {
|
|
457
|
+
me.fire('load', {items: me.items, total: me.chunkingTotal});
|
|
458
|
+
} else {
|
|
459
|
+
me.fire('load', {items: me.items});
|
|
460
|
+
}
|
|
451
461
|
}
|
|
452
462
|
}
|
|
453
463
|
|
|
@@ -478,7 +488,7 @@ class Store extends Base {
|
|
|
478
488
|
// => break the sync flow to ensure potential listeners got applied
|
|
479
489
|
Promise.resolve().then(() => {
|
|
480
490
|
if (me.isLoaded) {
|
|
481
|
-
me.fire('load', me.items)
|
|
491
|
+
me.fire('load', {items: me.items})
|
|
482
492
|
} else if (me.autoLoad) {
|
|
483
493
|
me.load()
|
|
484
494
|
}
|
package/src/grid/Body.mjs
CHANGED
|
@@ -932,7 +932,9 @@ class GridBody extends Component {
|
|
|
932
932
|
}
|
|
933
933
|
|
|
934
934
|
/**
|
|
935
|
-
* @param {Object
|
|
935
|
+
* @param {Object} data
|
|
936
|
+
* @param {Object[]} data.items
|
|
937
|
+
* @param {Number} [data.total]
|
|
936
938
|
* @protected
|
|
937
939
|
*/
|
|
938
940
|
onStoreLoad(data) {
|
|
@@ -944,7 +946,7 @@ class GridBody extends Component {
|
|
|
944
946
|
* This logic bypasses the standard update() cycle by directly clearing the vdom,
|
|
945
947
|
* vnode cache and the real DOM via textContent.
|
|
946
948
|
*/
|
|
947
|
-
if (data?.length < 1) {
|
|
949
|
+
if (data?.items.length < 1) {
|
|
948
950
|
const vdomRoot = me.getVdomRoot();
|
|
949
951
|
|
|
950
952
|
// No change, opt out
|
package/src/grid/Container.mjs
CHANGED
|
@@ -565,13 +565,16 @@ class GridContainer extends BaseContainer {
|
|
|
565
565
|
}
|
|
566
566
|
|
|
567
567
|
/**
|
|
568
|
-
* @param {Object
|
|
568
|
+
* @param {Object} data
|
|
569
|
+
* @param {Object[]} data.items
|
|
570
|
+
* @param {Number} [data.total]
|
|
569
571
|
* @protected
|
|
570
572
|
*/
|
|
571
573
|
onStoreLoad(data) {
|
|
572
|
-
let me
|
|
574
|
+
let me = this,
|
|
575
|
+
totalCount = data.total ? data.total : this.store.count;
|
|
573
576
|
|
|
574
|
-
me.updateRowCount();
|
|
577
|
+
me.updateRowCount(totalCount);
|
|
575
578
|
|
|
576
579
|
if (me.store.sorters?.length < 1) {
|
|
577
580
|
me.removeSortingCss()
|
|
@@ -664,13 +667,15 @@ class GridContainer extends BaseContainer {
|
|
|
664
667
|
}
|
|
665
668
|
|
|
666
669
|
/**
|
|
667
|
-
* @param {
|
|
670
|
+
* @param {Number} [count] The total number of rows in the store. Optional, will use store.count if not provided.
|
|
671
|
+
* @param {Boolean} [silent=false]
|
|
668
672
|
*/
|
|
669
|
-
updateRowCount(silent=false) {
|
|
670
|
-
let me
|
|
673
|
+
updateRowCount(count, silent=false) {
|
|
674
|
+
let me = this,
|
|
675
|
+
finalCount = count ? count : me.store.count;
|
|
671
676
|
|
|
672
|
-
|
|
673
|
-
!silent &&
|
|
677
|
+
me.getVdomRoot()['aria-rowcount'] = finalCount + 2;
|
|
678
|
+
!silent && me.update()
|
|
674
679
|
}
|
|
675
680
|
}
|
|
676
681
|
|
|
@@ -100,18 +100,19 @@ class VerticalScrollbar extends Component {
|
|
|
100
100
|
filter: me.updateScrollHeight,
|
|
101
101
|
load : me.updateScrollHeight,
|
|
102
102
|
scope : me
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
value.getCount() > 0 && me.updateScrollHeight()
|
|
103
|
+
})
|
|
106
104
|
}
|
|
107
105
|
}
|
|
108
106
|
|
|
109
107
|
/**
|
|
110
|
-
*
|
|
108
|
+
* @param {Object} data
|
|
109
|
+
* @param {Object[]} data.items
|
|
110
|
+
* @param {Number} [data.total]
|
|
111
|
+
* @protected
|
|
111
112
|
*/
|
|
112
|
-
updateScrollHeight() {
|
|
113
|
+
updateScrollHeight(data) {
|
|
113
114
|
let me = this,
|
|
114
|
-
countRecords = me.store.
|
|
115
|
+
countRecords = data.total ? data.total : me.store.count,
|
|
115
116
|
{rowHeight} = me;
|
|
116
117
|
|
|
117
118
|
if (countRecords > 0 && rowHeight > 0) {
|
package/src/table/Body.mjs
CHANGED
|
@@ -558,12 +558,39 @@ class TableBody extends Component {
|
|
|
558
558
|
}
|
|
559
559
|
|
|
560
560
|
/**
|
|
561
|
-
* @param {Object
|
|
561
|
+
* @param {Object} data
|
|
562
|
+
* @param {Object[]} data.items
|
|
563
|
+
* @param {Number} [data.total]
|
|
562
564
|
* @protected
|
|
563
565
|
*/
|
|
564
566
|
onStoreLoad(data) {
|
|
565
567
|
let me = this;
|
|
566
568
|
|
|
569
|
+
/*
|
|
570
|
+
* Fast path to handle clearing all rows (e.g., store.removeAll()).
|
|
571
|
+
* A full vdom diff against all existing rows is a performance bottleneck.
|
|
572
|
+
* This logic bypasses the standard update() cycle by directly clearing the vdom,
|
|
573
|
+
* vnode cache and the real DOM via textContent.
|
|
574
|
+
*/
|
|
575
|
+
if (data?.items.length < 1) {
|
|
576
|
+
const vdomRoot = me.getVdomRoot();
|
|
577
|
+
|
|
578
|
+
// No change, opt out
|
|
579
|
+
if (vdomRoot.cn.length < 1) {
|
|
580
|
+
return
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
vdomRoot.cn = [];
|
|
584
|
+
me.getVnodeRoot().childNodes = [];
|
|
585
|
+
|
|
586
|
+
Neo.applyDeltas(me.appName, {
|
|
587
|
+
id : vdomRoot.id,
|
|
588
|
+
textContent: ''
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
return
|
|
592
|
+
}
|
|
593
|
+
|
|
567
594
|
me.createViewData();
|
|
568
595
|
|
|
569
596
|
if (me.mounted) {
|