Skip to content

Commit 6016d61

Browse files
fix(scrollbar): rebuild drag helper when orientation changes at runtime
ElementDragHelper captures its constraint axis at construction time, so flipping the scrollbar orientation while a handle element exists left the helper still constraining drags to the old axis. _onHandleDrag would then read the wrong component of the position vector and value updates could stop working until the helper was destroyed and rebuilt through some other path (e.g. handleEntity reassignment). Extract the helper-rebuild logic from _onHandleElementGain into a shared _rebuildDragHelper method, and call it (plus _updateHandlePositionAndSize) from the orientation setter when there is an active handle element. Adds a regression test that flips the orientation post-attach and verifies the drag helper is rebuilt for the new axis. Reported by Copilot in #8693. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent b4e78e5 commit 6016d61

2 files changed

Lines changed: 29 additions & 2 deletions

File tree

src/framework/components/scrollbar/component.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,12 @@ class ScrollbarComponent extends Component {
118118

119119
this._orientation = arg;
120120

121+
// ElementDragHelper captures its axis at construction, so an existing helper
122+
// must be rebuilt to keep dragging in sync with the new orientation
121123
if (this._handleEntity?.element) {
122124
this._handleEntity.element[this._getOppositeDimension()] = 0;
125+
this._rebuildDragHelper();
126+
this._updateHandlePositionAndSize();
123127
}
124128
}
125129

@@ -253,14 +257,17 @@ class ScrollbarComponent extends Component {
253257

254258
_onHandleElementGain() {
255259
this._handleEntityElementSubscribe();
260+
this._rebuildDragHelper();
261+
this._updateHandlePositionAndSize();
262+
}
263+
264+
_rebuildDragHelper() {
256265
this._destroyDragHelper();
257266
this._handleDragHelper = new ElementDragHelper(this._handleEntity.element, this._getAxis());
258267
// ElementDragHelper defaults to enabled; mirror the component's current state so a helper
259268
// built while the scrollbar is disabled does not start out draggable
260269
this._handleDragHelper.enabled = this.enabled && this.entity.enabled;
261270
this._handleDragHelper.on('drag:move', this._onHandleDrag, this);
262-
263-
this._updateHandlePositionAndSize();
264271
}
265272

266273
_onHandleElementLose() {

test/framework/components/scrollbar/component.test.mjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,26 @@ describe('ScrollbarComponent', function () {
169169
expect(handle.element.height).to.equal(heightBefore);
170170
});
171171

172+
it('rebuilds the drag helper for the new axis when orientation changes at runtime', function () {
173+
const handle = new Entity();
174+
handle.addComponent('element', { type: ELEMENTTYPE_IMAGE });
175+
176+
const e = new Entity();
177+
e.addChild(handle);
178+
e.addComponent('element', { type: ELEMENTTYPE_IMAGE });
179+
e.addComponent('scrollbar', { handleEntity: handle, orientation: ORIENTATION_HORIZONTAL });
180+
app.root.addChild(e);
181+
182+
// ElementDragHelper captures its axis at construction, so the helper must be
183+
// rebuilt for the new axis when orientation flips - otherwise drags stay on the
184+
// old axis and value updates can stop working
185+
expect(e.scrollbar._handleDragHelper._axis).to.equal('x');
186+
187+
e.scrollbar.orientation = ORIENTATION_VERTICAL;
188+
189+
expect(e.scrollbar._handleDragHelper._axis).to.equal('y');
190+
});
191+
172192
});
173193

174194
describe('#handleEntity', function () {

0 commit comments

Comments
 (0)