Skip to content

Commit 83c413c

Browse files
committed
https://github.com/neomjs/neo/issues/8151
1 parent 225b9fc commit 83c413c

File tree

8 files changed

+60
-70
lines changed

8 files changed

+60
-70
lines changed

src/component/DateSelector.mjs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -861,10 +861,10 @@ class DateSelector extends Component {
861861
me.getCenterContentEl().cn = [];
862862
me.createDayViewContent(true);
863863

864-
// using force => we do want to keep the same ids
865-
syncIds && me.syncVdomIds(me.vnode, me.vdom, true);
864+
// we need to sync the new ids into the vdom
865+
syncIds && me.syncVdomState(me.vnode, me.vdom, true);
866866

867-
!silent && me.update()
867+
me.update()
868868
}
869869

870870
/**

src/date/DayViewComponent.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,8 @@ class DayViewComponent extends Base {
241241
me.vdom.cn = [];
242242
me.createContent(true);
243243

244-
// using force => we do want to keep the same ids
245-
syncIds && me.syncVdomIds(me.vnode, me.vdom, true);
244+
// we need to sync the new ids into the vdom
245+
syncIds && me.syncVdomState(me.vnode, me.vdom, true);
246246

247247
me.update()
248248
}

src/functional/component/Base.mjs

Lines changed: 1 addition & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ class FunctionalBase extends Abstract {
270270
// Re-hydrate the new vdom with stable IDs from the previous vnode tree.
271271
// This is crucial for functional components where the vdom is recreated on every render,
272272
// ensuring the diffing algorithm can track nodes correctly.
273-
me.syncVdomIds();
273+
me.syncVdomState();
274274

275275
if (me.beforeUpdate() !== false) {
276276
me.updateVdom()
@@ -480,55 +480,6 @@ class FunctionalBase extends Abstract {
480480

481481
return vdomTree
482482
}
483-
484-
/**
485-
* Overrides the default VdomLifecycle.syncVdomIds to also hydrate scroll positions.
486-
* This is critical for functional components to maintain scroll state across re-renders.
487-
* @param {Neo.vdom.VNode} [vnode=this.vnode]
488-
* @param {Object} [vdom=this.vdom]
489-
* @param {Boolean} [force=false]
490-
*/
491-
syncVdomIds(vnode=this.vnode, vdom=this.vdom, force=false) {
492-
// We cannot use super.syncVdomIds(), since we need to sync the scroll position
493-
// in the same run as the ids to ensure performance.
494-
if (vnode && vdom) {
495-
vdom = VDomUtil.getVdom(vdom);
496-
497-
let childNodes = vdom.cn,
498-
cn, i, len;
499-
500-
if (force) {
501-
if (vnode.id && vdom.id !== vnode.id) {
502-
vdom.id = vnode.id
503-
}
504-
} else {
505-
if (vnode.id && (!vdom.id || vdom.id.startsWith('neo-vnode-'))) {
506-
vdom.id = vnode.id
507-
}
508-
}
509-
510-
if (vnode.scrollTop) {
511-
vdom.scrollTop = vnode.scrollTop
512-
}
513-
514-
if (vnode.scrollLeft) {
515-
vdom.scrollLeft = vnode.scrollLeft
516-
}
517-
518-
if (childNodes) {
519-
cn = childNodes.map(item => VDomUtil.getVdom(item));
520-
cn = cn.filter(item => item && item.removeDom !== true);
521-
i = 0;
522-
len = cn?.length || 0;
523-
524-
for (; i < len; i++) {
525-
if (vnode.childNodes) {
526-
this.syncVdomIds(vnode.childNodes[i], cn[i], force)
527-
}
528-
}
529-
}
530-
}
531-
}
532483
}
533484

534485
export default Neo.setupClass(FunctionalBase);

src/grid/Body.mjs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -992,7 +992,15 @@ class GridBody extends Component {
992992
}
993993

994994
/**
995-
*
995+
* @param {Object} data
996+
*/
997+
onScrollCapture(data) {
998+
super.onScrollCapture(data);
999+
this.parent.scrollManager.onBodyScroll(data)
1000+
}
1001+
1002+
/**
1003+
* @param {Object} data
9961004
*/
9971005
onStoreFilter() {
9981006
this.onStoreLoad({items: this.store.items})

src/grid/Container.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,14 @@ class GridContainer extends BaseContainer {
557557
}
558558
}
559559

560+
/**
561+
* @param {Object} data
562+
*/
563+
onScrollCapture(data) {
564+
super.onScrollCapture(data);
565+
this.scrollManager.onContainerScroll(data)
566+
}
567+
560568
/**
561569
* @param {Object} opts
562570
* @param {String} opts.direction

src/grid/ScrollManager.mjs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,16 +74,10 @@ class ScrollManager extends Base {
7474
let me = this;
7575

7676
me.gridBody.addDomListeners({
77-
scroll : me.onBodyScroll,
7877
touchcancel: me.onTouchCancel,
7978
touchend : me.onTouchEnd,
8079
scope : me
8180
});
82-
83-
me.gridContainer.addDomListeners({
84-
scroll: me.onContainerScroll,
85-
scope : me
86-
})
8781
}
8882

8983
/**

src/mixin/VdomLifecycle.mjs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -527,13 +527,13 @@ class VdomLifecycle extends Base {
527527
}
528528

529529
/**
530-
* Placeholder method for util.VDom.syncVdomIds to allow overriding (disabling) it
530+
* Placeholder method for util.VDom.syncVdomState to allow overriding (disabling) it
531531
* @param {Neo.vdom.VNode} [vnode=this.vnode]
532532
* @param {Object} [vdom=this.vdom]
533533
* @param {Boolean} force=false
534534
*/
535-
syncVdomIds(vnode=this.vnode, vdom=this.vdom, force=false) {
536-
VDomUtil.syncVdomIds(vnode, vdom, force)
535+
syncVdomState(vnode=this.vnode, vdom=this.vdom, force=false) {
536+
VDomUtil.syncVdomState(vnode, vdom, force)
537537
}
538538

539539
/**
@@ -554,7 +554,7 @@ class VdomLifecycle extends Base {
554554
start = performance.now()
555555
}
556556

557-
me.syncVdomIds();
557+
me.syncVdomState();
558558

559559
if (vnode && me.id !== vnode.id) {
560560
ComponentManager.registerWrapperNode(vnode.id, me)

src/util/VDom.mjs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,13 +408,24 @@ class VDom extends Base {
408408
}
409409

410410
/**
411-
* Neo.vdom.Helper will create ids for each vnode which does not already have one,
412-
* so we need to sync them into the vdom.
411+
* Synchronizes state between the live VDOM (blueprint) and the incoming VNode (worker response).
412+
*
413+
* 1. **ID Synchronization (VNode -> VDOM):**
414+
* `Neo.vdom.Helper` automatically assigns dynamic IDs to any VNode that lacks one, as the
415+
* delta update engine requires unique IDs to target DOM nodes. These generated IDs are
416+
* synced back into the VDOM to ensure stability and persistent referencing for future updates.
417+
*
418+
* 2. **Scroll State Synchronization (Bidirectional):**
419+
* - **Preservation (VDOM -> VNode):** Ensures that the latest scroll position captured on the
420+
* Main Thread (stored in VDOM) overrides potentially stale state returning from the Worker.
421+
* - **Rehydration (VNode -> VDOM):** Ensures that new VDOM trees (e.g., from Functional Components)
422+
* inherit the persistent scroll state from the existing VNode.
423+
*
413424
* @param {Neo.vdom.VNode} vnode
414425
* @param {Object} vdom
415426
* @param {Boolean} force=false The force param will enforce overwriting different ids
416427
*/
417-
static syncVdomIds(vnode, vdom, force=false) {
428+
static syncVdomState(vnode, vdom, force=false) {
418429
if (vnode && vdom) {
419430
vdom = VDom.getVdom(vdom);
420431

@@ -435,6 +446,24 @@ class VDom extends Base {
435446
}
436447
}
437448

449+
// 1. Rehydration (vnode -> vdom)
450+
// Used by Functional Components (vdom is new)
451+
if (Neo.isNumber(vnode.scrollTop) && !Neo.isNumber(vdom.scrollTop)) {
452+
vdom.scrollTop = vnode.scrollTop
453+
}
454+
if (Neo.isNumber(vnode.scrollLeft) && !Neo.isNumber(vdom.scrollLeft)) {
455+
vdom.scrollLeft = vnode.scrollLeft
456+
}
457+
458+
// 2. Preservation (vdom -> vnode)
459+
// Used by Classic Components (vdom is source of truth via capture)
460+
if (Neo.isNumber(vdom.scrollTop)) {
461+
vnode.scrollTop = vdom.scrollTop
462+
}
463+
if (Neo.isNumber(vdom.scrollLeft)) {
464+
vnode.scrollLeft = vdom.scrollLeft
465+
}
466+
438467
if (childNodes) {
439468
cn = childNodes.map(item => VDom.getVdom(item));
440469
// The vnode.childNodes array is already filtered by the worker.
@@ -448,7 +477,7 @@ class VDom extends Base {
448477

449478
for (; i < len; i++) {
450479
if (vnode.childNodes) {
451-
VDom.syncVdomIds(vnode.childNodes[i], cn[i], force)
480+
VDom.syncVdomState(vnode.childNodes[i], cn[i], force)
452481
}
453482
}
454483
}

0 commit comments

Comments
 (0)