Skip to content

Commit 286a8db

Browse files
committed
fix: Finalize Grid Multi-Body Row Pooling & Vertical Sync (#9488)
1 parent a345743 commit 286a8db

8 files changed

Lines changed: 91 additions & 260 deletions

File tree

resources/scss/src/grid/Body.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
height : 100%;
33
overflow-anchor: none;
44
overflow-x : hidden;
5-
overflow-y : auto;
5+
overflow-y : visible;
66
position : relative;
77
scrollbar-width: none;
88

resources/scss/src/grid/VerticalScrollbar.scss

Lines changed: 0 additions & 45 deletions
This file was deleted.

src/grid/Body.mjs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -528,16 +528,7 @@ class GridBody extends Component {
528528
* @protected
529529
*/
530530
afterSetScrollTop(value, oldValue) {
531-
let me = this,
532-
{bufferRowRange} = me,
533-
newStartIndex = Math.floor(value / me.rowHeight);
534-
535-
if (Math.abs(me.startIndex - newStartIndex) >= bufferRowRange) {
536-
me.startIndex = newStartIndex
537-
} else {
538-
me.visibleRows[0] = newStartIndex;
539-
me.visibleRows[1] = newStartIndex + me.availableRows
540-
}
531+
// Controlled externally by Grid.Container.syncBodies()
541532
}
542533

543534
/**
@@ -1235,7 +1226,7 @@ class GridBody extends Component {
12351226
me.timeout(50).then(() => {
12361227
Neo.main.DomAccess.scrollTo({
12371228
direction: 'top',
1238-
id : me.vdom.id,
1229+
id : me.gridContainer.bodyWrapper.id,
12391230
value : 0,
12401231
windowId
12411232
})
@@ -1324,7 +1315,7 @@ class GridBody extends Component {
13241315
}
13251316

13261317
Neo.main.DomAccess.scrollTo({
1327-
id : me.vdom.id,
1318+
id : me.gridContainer.bodyWrapper.id,
13281319
value : scrollTop,
13291320
windowId: me.windowId
13301321
})

src/grid/Container.mjs

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import Collection from '../collection/Base.mjs';
44
import GridBody from './Body.mjs';
55
import ScrollManager from './ScrollManager.mjs';
66
import Store from '../data/Store.mjs';
7-
import VerticalScrollbar from './VerticalScrollbar.mjs';
87
import FooterToolbar from './footer/Toolbar.mjs';
98
import * as column from './column/_export.mjs';
109
import * as header from './header/_export.mjs';
@@ -16,7 +15,6 @@ import {isDescriptor} from '../core/ConfigSymbols.mjs';
1615
* `Neo.grid.Container` orchestrates the entire Grid component. It uses a composite architecture consisting of:
1716
* 1. `headerToolbar` ({@link Neo.grid.header.Toolbar}): Manages column headers, sorting, and filtering UI.
1817
* 2. `body` ({@link Neo.grid.Body}): The scrollable area containing the data rows.
19-
* 3. `scrollbar` ({@link Neo.grid.VerticalScrollbar}): A virtualized scrollbar for handling large datasets.
2018
*
2119
* Key features include:
2220
* - **Virtual Scrolling:** Only renders visible rows and columns (plus a small buffer) for high performance with large datasets.
@@ -170,11 +168,7 @@ class GridContainer extends BaseContainer {
170168
* @reactive
171169
*/
172170
rowHeight_: 32,
173-
/**
174-
* @member {Neo.grid.Scrollbar|null} scrollbar=null
175-
* @protected
176-
*/
177-
scrollbar: null,
171+
178172
/**
179173
* @member {Boolean} showHeaderFilters_=false
180174
* @reactive
@@ -305,18 +299,6 @@ class GridContainer extends BaseContainer {
305299
me.items.push(me.footerToolbar)
306300
}
307301

308-
me.scrollbar = Neo.create({
309-
module : VerticalScrollbar,
310-
appName,
311-
parentId: me.id,
312-
rowHeight,
313-
store,
314-
theme : me.theme,
315-
windowId
316-
});
317-
318-
me.vdom.cn.push(me.scrollbar.createVdomReference())
319-
320302
me.vdom.id = me.getWrapperId();
321303

322304
me._columns = me.createColumns(me.columns);
@@ -450,15 +432,11 @@ class GridContainer extends BaseContainer {
450432
*/
451433
afterSetRowHeight(value, oldValue) {
452434
if (value > 0) {
453-
let {body, scrollbar} = this;
454-
455-
if (scrollbar) {
456-
scrollbar.rowHeight = value
457-
}
435+
let {body, bodyEnd, bodyStart} = this;
458436

459-
if (body) {
460-
body.rowHeight = value
461-
}
437+
if (body) body.rowHeight = value;
438+
if (bodyStart) bodyStart.rowHeight = value;
439+
if (bodyEnd) bodyEnd.rowHeight = value;
462440
}
463441
}
464442

@@ -519,9 +497,9 @@ class GridContainer extends BaseContainer {
519497
oldValue?.un(listeners);
520498

521499
// in case we dynamically change the store, grid.Body needs to get the new reference
522-
if (me.body) {
523-
me.body.store = value
524-
}
500+
if (me.body) me.body.store = value;
501+
if (me.bodyStart) me.bodyStart.store = value;
502+
if (me.bodyEnd) me.bodyEnd.store = value
525503

526504
if (me.footerToolbar && me.footerToolbar.store !== value) {
527505
me.footerToolbar.store = value
@@ -537,8 +515,10 @@ class GridContainer extends BaseContainer {
537515
* @protected
538516
*/
539517
afterSetUseInternalId(value, oldValue) {
540-
if (oldValue !== undefined && this.body) {
541-
this.body.useInternalId = value
518+
if (oldValue !== undefined) {
519+
if (this.body) this.body.useInternalId = value;
520+
if (this.bodyStart) this.bodyStart.useInternalId = value;
521+
if (this.bodyEnd) this.bodyEnd.useInternalId = value;
542522
}
543523
}
544524

@@ -1160,10 +1140,14 @@ class GridContainer extends BaseContainer {
11601140
await me.timeout(100);
11611141
await me.passSizeToBody(silent)
11621142
} else {
1163-
me.body[silent ? 'setSilent' : 'set']({
1143+
let config = {
11641144
availableHeight: containerRect.height - headerRect.height - (footerRect?.height || 0),
11651145
containerWidth : containerRect.width
1166-
})
1146+
};
1147+
1148+
me.body[silent ? 'setSilent' : 'set'](config);
1149+
me.bodyStart && me.bodyStart[silent ? 'setSilent' : 'set'](config);
1150+
me.bodyEnd && me.bodyEnd[silent ? 'setSilent' : 'set'](config)
11671151
}
11681152
}
11691153

@@ -1234,6 +1218,40 @@ class GridContainer extends BaseContainer {
12341218
}
12351219
}
12361220

1221+
/**
1222+
* Pushes synchronized scrolling coordinates (like startIndex) into all active bodies
1223+
* @param {Number} scrollTop
1224+
*/
1225+
syncBodies(scrollTop) {
1226+
let me = this,
1227+
{body, bodyEnd, bodyStart, scrollManager} = me,
1228+
{bufferRowRange, rowHeight} = body,
1229+
newStartIndex = Math.floor(scrollTop / rowHeight);
1230+
1231+
let updateBody = _body => {
1232+
_body.skipCreateViewData = true;
1233+
1234+
_body.set({
1235+
scrollLeft: scrollManager.scrollLeft, // Horizontal sync applies from scrollManager state
1236+
scrollTop : scrollTop
1237+
});
1238+
1239+
if (Math.abs(_body.startIndex - newStartIndex) >= bufferRowRange) {
1240+
_body.startIndex = newStartIndex
1241+
} else {
1242+
_body.visibleRows[0] = newStartIndex;
1243+
_body.visibleRows[1] = newStartIndex + _body.availableRows
1244+
}
1245+
1246+
_body.skipCreateViewData = false;
1247+
_body.createViewData();
1248+
};
1249+
1250+
updateBody(body);
1251+
bodyStart && updateBody(bodyStart);
1252+
bodyEnd && updateBody(bodyEnd)
1253+
}
1254+
12371255
/**
12381256
* Serializes the instance into a JSON-compatible object for the Neural Link.
12391257
* @returns {Object}
@@ -1249,7 +1267,7 @@ class GridContainer extends BaseContainer {
12491267
footerToolbar : me.footerToolbar?.toJSON(),
12501268
headerToolbar : me.headerToolbar?.toJSON(),
12511269
rowHeight : me.rowHeight,
1252-
scrollbar : me.scrollbar?.toJSON(),
1270+
12531271
scrollManager : me.scrollManager?.toJSON(),
12541272
showHeaderFilters : me.showHeaderFilters,
12551273
sortable : me.sortable,

src/grid/ScrollManager.mjs

Lines changed: 19 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ class ScrollManager extends Base {
148148
onBodyScroll({scrollTop}) {
149149
let me = this;
150150

151-
me.scrollTop = scrollTop;
152-
me.gridBody.isScrolling = true;
151+
me.scrollTop = scrollTop;
152+
me.gridContainer.body.isScrolling = true;
153153

154154
me.onBodyScrollEnd();
155155
me.syncGridBody()
@@ -161,22 +161,28 @@ class ScrollManager extends Base {
161161
onBodyScrollEnd() {
162162
let me = this;
163163

164-
me.gridBody.isScrolling = false;
164+
me.gridContainer.body.isScrolling = false;
165165
me.syncGridBody()
166166
}
167167

168168
/**
169169
* @param {Object} data
170170
* @param {Number} data.scrollLeft
171+
* @param {Number} data.scrollTop
171172
* @param {Object} data.target
172173
*/
173-
onContainerScroll({scrollLeft, target}) {
174+
onContainerScroll({scrollLeft, scrollTop, target}) {
174175
let me = this;
175176

176-
// We must ignore events for grid-scrollbar
177-
if (target.id.includes('grid-container')) {
178-
me.scrollLeft = scrollLeft;
179-
me.gridBody.isScrolling = true;
177+
if (target.id === me.gridContainer.bodyWrapper?.id) {
178+
me.scrollTop = scrollTop;
179+
me.gridContainer.body.isScrolling = true;
180+
181+
me.onBodyScrollEnd();
182+
me.syncGridBody()
183+
} else if (target.id.includes('grid-container')) {
184+
me.scrollLeft = scrollLeft;
185+
me.gridContainer.body.isScrolling = true;
180186

181187
me.onBodyScrollEnd();
182188
me.syncGridBody()
@@ -187,19 +193,9 @@ class ScrollManager extends Base {
187193
* @protected
188194
*/
189195
syncGridBody() {
190-
let me = this,
191-
body = me.gridBody;
192-
193-
body.skipCreateViewData = true;
194-
195-
body.set({
196-
scrollLeft: me.scrollLeft,
197-
scrollTop : me.scrollTop
198-
});
199-
200-
body.skipCreateViewData = false;
201-
body.createViewData();
196+
let me = this;
202197

198+
me.gridContainer.syncBodies(me.scrollTop);
203199
me.gridContainer.headerToolbar.scrollLeft = me.scrollLeft
204200
}
205201

@@ -258,9 +254,9 @@ class ScrollManager extends Base {
258254

259255
if (active) {
260256
addon.register({
261-
bodyId : me.gridBody.id,
262-
id : me.id,
263-
scrollbarId: me.gridContainer.scrollbar.id,
257+
bodyId : me.gridBody.id,
258+
bodyWrapperId: me.gridContainer.bodyWrapper.id,
259+
id : me.id,
264260
windowId
265261
})
266262
} else {

0 commit comments

Comments
 (0)