Skip to content

refactor(scrollview): align ScrollViewComponent with CameraComponent architecture#8777

Merged
willeastcott merged 5 commits into
mainfrom
claude/amazing-tharp-3b5c42
May 26, 2026
Merged

refactor(scrollview): align ScrollViewComponent with CameraComponent architecture#8777
willeastcott merged 5 commits into
mainfrom
claude/amazing-tharp-3b5c42

Conversation

@willeastcott
Copy link
Copy Markdown
Contributor

Summary

Applies the same refactor pattern adopted by ScrollbarComponent in #8693 to ScrollViewComponent, continuing the move away from *ComponentData property bags.

  • ScrollViewComponent now owns _horizontal, _vertical, _scrollMode, _bounceAmount, _friction, _dragThreshold, _useMouseWheel, _mouseWheelSensitivity, _horizontalScrollbarVisibility, _verticalScrollbarVisibility directly. Setters write the private fields and (for horizontal / vertical) inline the _syncScrollbarEnabledState side effect that used to live in set_horizontal / set_vertical listeners. The _setValue helper, _toggleLifecycleListeners, _onSetHorizontalScrollingEnabled, and _onSetVerticalScrollingEnabled are gone. The four entity setters drop their trailing this.data.<name> = guid writes.
  • ScrollViewComponentData is reduced to a single enabled field.
  • ScrollViewComponentSystem matches CameraComponentSystem / ScrollbarComponentSystem: _schema = ['enabled'], an explicit _properties list drives initializeComponentData, an explicit cloneComponent is added (the default base-class clone would otherwise lose every non-enabled field), and Component._buildAccessors(ScrollViewComponent.prototype, _schema) handles the enabled accessor.

The schema previously declared mouseWheelSensitivity as type: 'vec2', which made ComponentSystem.convertValue clone Vec2 inputs and construct a Vec2 from [x, y] arrays. With the schema reduced to ['enabled'], that normalization is moved into the mouseWheelSensitivity setter so JSON-loaded scenes shipping mouseWheelSensitivity: [1, 1] and callers passing shared Vec2 instances (e.g. Vec2.ONE) continue to work.

The constructor's entity.on('element:add', ...) registration is captured to a new _evtElementAdd event-handle field and torn down explicitly in onRemove. The pre-existing missing _evtViewportEntityElementAdd field declaration is added alongside the other event-handle fields for consistency.

Public API

No additions, removals, or behaviour changes. The full property surface of ScrollViewComponent is preserved (verified against the regenerated .d.ts): enabled, horizontal, vertical, scrollMode, bounceAmount, friction, dragThreshold, useMouseWheel, mouseWheelSensitivity, horizontalScrollbarVisibility, verticalScrollbarVisibility, viewportEntity, contentEntity, horizontalScrollbarEntity, verticalScrollbarEntity, scroll, and the static EVENT_SETSCROLL / set:scroll event.

Tests

Adds test/framework/components/scroll-view/component.test.mjs, modelled on the ScrollbarComponent test suite, covering:

  • #addComponent — defaults match the data class; round-trip every property passed via the data argument.
  • #mouseWheelSensitivity — accepts [x, y] arrays (the regression bait of dropping the schema entry); clones Vec2 inputs so caller mutations do not leak into component state.
  • #horizontal / #vertical — flipping the flag syncs the attached scrollbar entity's enabled state; same-value writes are no-ops.
  • #scroll — assigning a Vec2 fires set:scroll exactly once.
  • #viewportEntity, #contentEntity, #horizontalScrollbarEntity, #verticalScrollbarEntity — accept Entity, accept a GUID string and resolve via app.getEntityFromIndex, accept null, unsubscribe from the previous entity when reassigned.
  • #cloneComponent — round-trips every scalar property; remaps every entity ref to the cloned subtree.
  • resolveDuplicatedEntityReferenceProperties — remaps every entity ref through the supplied duplicatedIdsMap.

Test plan

  • npm run lint passes
  • npm run build:types regenerates .d.ts cleanly (public property surface diff-clean)
  • npm test — all scroll-view tests pass; the only failing test (Http #get() retries resource and returns 404) is pre-existing on main and unrelated to this refactor

🤖 Generated with Claude Code

…architecture

Apply the same refactor pattern recently adopted by ScrollbarComponent
(#8693): the component now owns its state directly via private fields,
setters do their work inline, the data-bag round-trip and set_<name>
event chain are gone, and the system declares `_schema = ['enabled']`
with an explicit `_properties` list and `cloneComponent`.

`mouseWheelSensitivity` previously relied on the schema's `type: 'vec2'`
auto-conversion (clones Vec2 inputs, builds a Vec2 from `[x, y]` arrays);
that work is moved into the setter itself so JSON-loaded scenes shipping
`mouseWheelSensitivity: [1, 1]` and callers passing shared Vec2 instances
continue to work.

Add `test/framework/components/scroll-view/component.test.mjs` covering
defaults, the round-trip via `addComponent`, the Vec2/array normalization
on `mouseWheelSensitivity`, side effects on `horizontal`/`vertical`,
`set:scroll`, all four entity setters (Entity / GUID / null /
unsubscribe-on-reassign), `cloneComponent`, and
`resolveDuplicatedEntityReferenceProperties`.

No public API change: properties, behavior, and the `set:scroll` event
are preserved.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors ScrollViewComponent to match the “component owns its state” architecture used by CameraComponent / ScrollbarComponent, reducing reliance on *ComponentData property bags while preserving the existing public API surface. It also adds a dedicated test suite to guard against regressions from the schema reduction (notably mouseWheelSensitivity normalization and cloning behavior).

Changes:

  • Refactor ScrollViewComponent to store most properties on private fields and remove the _setValue / set_* listener plumbing.
  • Reduce ScrollViewComponentData to enabled only, and update ScrollViewComponentSystem to use an explicit _properties list plus a custom cloneComponent.
  • Add test/framework/components/scroll-view/component.test.mjs to cover defaults, property round-trips, entity reference behaviors, cloning/remapping, and mouseWheelSensitivity normalization.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
test/framework/components/scroll-view/component.test.mjs Adds coverage for the refactored ScrollView component (defaults, setters, events, cloning, ref remap).
src/framework/components/scroll-view/system.js Updates the system to schema ['enabled'], initializes via _properties, and adds explicit cloning + accessor build.
src/framework/components/scroll-view/data.js Shrinks data class to enabled only.
src/framework/components/scroll-view/component.js Moves property storage to private fields, inlines side-effects, and adjusts lifecycle/event handle management.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/framework/components/scroll-view/component.js
Comment thread src/framework/components/scroll-view/component.js Outdated
… on remove

Two follow-ups from Copilot's review on #8777:

- `mouseWheelSensitivity` setter was narrower than the old schema-driven
  `convertValue('vec2')`: it only normalized `Array.isArray(arg)`, so a
  Float32Array (or any other indexable input) would slip through to the
  passthrough branch and break later code that reads `.x`/`.y`. Match the
  old contract: nullish passes through, Vec2 is cloned, anything else is
  treated as indexable.

- `onRemove` only detached the entity's own `element:add` and element
  listeners. Listeners registered on referenced viewport / content /
  scrollbar entities, plus the pending `_evtElementRemove` once-handle
  on the element, stayed live - keeping the removed component reachable
  from those entities. Set every entity ref to null (which routes through
  the existing setter -> unsubscribe path) and `.off()` the pending
  once-handle. This is also a pre-existing leak, not a regression from
  the refactor.

Adds tests for both: a Float32Array round-trip on `mouseWheelSensitivity`,
and a `removeComponent` test asserting that referenced entities no longer
have any of the component's listeners.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread src/framework/components/scroll-view/system.js Outdated
Copilot review on #8777 caught a behavior regression: the old
initializeComponentData normalized dragThreshold / useMouseWheel /
mouseWheelSensitivity when they were `=== undefined`, so callers shipping
`{ dragThreshold: undefined }` still ended up with the default 10. The
refactored loop guarded with `hasOwnProperty`, which let explicit
undefined values clobber the class-field defaults - turning
`useMouseWheel` falsy (disables wheel scrolling) and `mouseWheelSensitivity`
into undefined (`_onMouseWheel` then throws on `.x`/`.y`).

Tighten the guard to `data[property] !== undefined` so the class-field
defaults survive explicit-undefined input, exactly matching the legacy
behavior.

Adds a test that addComponent with `{ dragThreshold: undefined, useMouseWheel:
undefined, mouseWheelSensitivity: undefined }` lands on the documented
defaults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

@willeastcott willeastcott merged commit 89a43c5 into main May 26, 2026
8 checks passed
@willeastcott willeastcott deleted the claude/amazing-tharp-3b5c42 branch May 26, 2026 10:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Request for a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants