This is an automated email from the ASF dual-hosted git repository.

hugoferreira pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/royale-asjs.git


The following commit(s) were added to refs/heads/develop by this push:
     new 5b91a7e4a9 Fix VirtualDataGrid column misalignment during scroll
5b91a7e4a9 is described below

commit 5b91a7e4a9517a863e3b527ceefc839521892835
Author: Hugo Ferreira <[email protected]>
AuthorDate: Wed Apr 1 21:50:23 2026 +0100

    Fix VirtualDataGrid column misalignment during scroll
    
    Two related but distinct causes were addressed in 
VirtualDataGridListAreaLayout:
    
    1. Misalignment during scroll (compositor race)
    
    Modern browsers use a compositor thread to scroll elements visually before 
the JavaScript main thread has a chance to run. In a VirtualDataGrid, this 
meant the column under the mouse was moved ahead by the compositor while the 
other columns were still waiting for the cross-column scrollTop sync in 
scrollHandler. The result was one or more frames of visible misalignment while 
scrolling.
    
    Fixed by adding a non-passive wheel event listener to each column element. 
Calling e.preventDefault() inside it blocks the compositor from scrolling the 
column natively. Scrolling is then driven entirely from JavaScript — setting 
scrollTop manually triggers the existing scrollHandler, which propagates the 
position to all sibling columns before the browser paints.
    
    2. Misalignment after fast scroll (async layout race)
    
    During fast scrolling, the focused column's scrollHandler would set 
scrollTop on sibling columns synchronously, but those siblings only called 
layout() when their own queued scroll events eventually fired — potentially in 
a later frame. This left a window where the focused column had already painted 
its new item renderers while the others still showed stale content.
    
    Fixed by calling layout() on each sibling column immediately after setting 
its scrollTop, within the same execution context as the focused column's sync 
loop. A static _syncingDataGrid flag suppresses the subsequent redundant 
layout() calls that would otherwise arrive from the queued scroll events.
---
 .../beads/layouts/VirtualDataGridListAreaLayout.as | 79 ++++++++++++++++++----
 1 file changed, 67 insertions(+), 12 deletions(-)

diff --git 
a/frameworks/projects/Jewel/src/main/royale/org/apache/royale/jewel/beads/layouts/VirtualDataGridListAreaLayout.as
 
b/frameworks/projects/Jewel/src/main/royale/org/apache/royale/jewel/beads/layouts/VirtualDataGridListAreaLayout.as
index 8304bc9ff6..751b2eb060 100644
--- 
a/frameworks/projects/Jewel/src/main/royale/org/apache/royale/jewel/beads/layouts/VirtualDataGridListAreaLayout.as
+++ 
b/frameworks/projects/Jewel/src/main/royale/org/apache/royale/jewel/beads/layouts/VirtualDataGridListAreaLayout.as
@@ -22,6 +22,7 @@ package org.apache.royale.jewel.beads.layouts
     import org.apache.royale.html.beads.IDataGridView;
        import org.apache.royale.core.IStrand;
     import org.apache.royale.core.IDataGrid;
+    import org.apache.royale.core.IBeadLayout;
     import org.apache.royale.jewel.Container;
     import org.apache.royale.jewel.supportClasses.datagrid.IDataGridColumnList;
         
@@ -37,6 +38,10 @@ package org.apache.royale.jewel.beads.layouts
        {
         private var isAreaFocus:Boolean;
 
+        // Tracks which DataGrid is currently syncing columns, to suppress 
redundant
+        // layout() calls from scroll events triggered by programmatic 
scrollTop changes.
+        private static var _syncingDataGrid:IDataGrid = null;
+
                /**
                 *  Constructor.
                 *
@@ -59,9 +64,31 @@ package org.apache.royale.jewel.beads.layouts
             {
                 host.element.addEventListener("mouseover", function():void { 
isAreaFocus = true });
                 host.element.addEventListener("mouseleave", function():void { 
isAreaFocus = false });
+
+                // Non-passive wheel listener so preventDefault() can block 
the browser's
+                // compositor thread from scrolling this column ahead of its 
siblings.
+                // Without this, the compositor moves this column visually 
before our
+                // scrollHandler fires, producing one frame of cross-column 
misalignment.
+                var wheelOptions:Object = new Object();
+                wheelOptions["passive"] = false;
+                host.element.addEventListener("wheel", wheelHandler, 
wheelOptions);
             }
         }
 
+        COMPILE::JS
+        private function wheelHandler(e:*):void
+        {
+            if (!isAreaFocus || e.deltaY == 0) return;
+
+            e.preventDefault();
+
+            // deltaMode: 0 = pixels (trackpad/modern), 1 = lines (mouse wheel 
on Firefox)
+            var delta:Number = (e.deltaMode == 0) ? e.deltaY : e.deltaY * 40;
+            var limitY:Number = host.element.scrollHeight - 
host.element.clientHeight;
+            host.element.scrollTop = Math.max(0, 
Math.min(host.element.scrollTop + delta, limitY));
+            // The resulting scroll event triggers scrollHandler which syncs 
all other columns.
+        }
+
         private function getListArea():Container
         {
             var datagrid:IDataGrid = (host as IDataGridColumnList).datagrid;
@@ -71,23 +98,51 @@ package org.apache.royale.jewel.beads.layouts
 
         override protected function scrollHandler(e:Event):void
         {
-            super.scrollHandler(e);
-
-            //this is not ideal, but avoids that the other VirtualDataGrid 
columns
-            //also try to do scrollTop to all columns again in a loop, that 
causes a performance issue
-            //and a strang behaviour on the first column that started the 
process
             if (!isAreaFocus)
+            {
+                // Non-focused column: skip layout() if the focused column 
already synced us
+                // and called layout() directly — avoids a redundant call 
before the browser paints.
+                COMPILE::JS
+                {
+                    if (_syncingDataGrid != null && _syncingDataGrid == (host 
as IDataGridColumnList).datagrid)
+                        return;
+                }
+                super.scrollHandler(e);
                 return;
+            }
 
-            var listArea:Container = getListArea();
+            // Focused column: update its own layout first.
+            super.scrollHandler(e);
 
-            for (var i:int = 0; i < listArea.numElements; i++)
+            COMPILE::JS
             {
-                               COMPILE::JS
-                               {
-                if (listArea.getElementAt(i) != host)
-                    listArea.getElementAt(i).element.scrollTop = 
host.element.scrollTop;
-                               }       
+                var myDataGrid:IDataGrid = (host as 
IDataGridColumnList).datagrid;
+                _syncingDataGrid = myDataGrid;
+
+                var listArea:Container = getListArea();
+                var scrollTop:Number = host.element.scrollTop;
+
+                for (var i:int = 0; i < listArea.numElements; i++)
+                {
+                    var otherColumn:* = listArea.getElementAt(i);
+                    if (otherColumn != host)
+                    {
+                        otherColumn.element.scrollTop = scrollTop;
+                        // Immediately call layout() so all columns are 
visually aligned
+                        // before the browser paints — without waiting for the 
async scroll event.
+                        var otherLayout:IBeadLayout = 
otherColumn.getBeadByType(IBeadLayout) as IBeadLayout;
+                        if (otherLayout)
+                            otherLayout.layout();
+                    }
+                }
+
+                // Clear the flag after any queued scroll events from the 
programmatic
+                // scrollTop changes above have been processed and suppressed.
+                setTimeout(function():void
+                {
+                    if (_syncingDataGrid == myDataGrid)
+                        _syncingDataGrid = null;
+                }, 0);
             }
         }
        }

Reply via email to