Skip to content

refactor(button): align ButtonComponent with CameraComponent architecture#8870

Merged
willeastcott merged 6 commits into
mainfrom
refactor-button-component
Jun 11, 2026
Merged

refactor(button): align ButtonComponent with CameraComponent architecture#8870
willeastcott merged 6 commits into
mainfrom
refactor-button-component

Conversation

@willeastcott

@willeastcott willeastcott commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Description

Applies the refactor pattern from #8693 (ScrollbarComponent) and #8777 (ScrollViewComponent) to ButtonComponent, continuing the move away from *ComponentData property bags. Runtime behaviour and the public property surface are unchanged (verified against the regenerated .d.ts: all 14 properties plus enabled via the base Component, and all events preserved), with one deliberate documentation correction: the six state sprite asset accessors were declared as bare Asset, but null is their default — they are now declared Asset|null. (Numeric asset ids still work internally — scene data ships them and ImageElement.spriteAsset accepts both — but the public contract encourages Asset references, so the docs deliberately do not advertise number.)

  • ButtonComponent now owns _active, _hitPadding, _transitionMode, _hoverTint, _pressedTint, _inactiveTint, _fadeDuration and the six state sprite asset/frame fields directly. The side effects that used to live in set_* listeners are inlined into the setters (active → visual state update; transitionMode → cancel tween, reset previous mode's visuals, reapply; tints and sprite props → force reapply). The _setValue helper, _toggleLifecycleListeners, _onSetActive, _onSetTransitionMode and _onSetTransitionValue are gone. The imageEntity setter is restyled like scrollbar's handleEntity (resolve first, single early-out) and drops its trailing this.data.imageEntity = guid writes.
  • ButtonComponentData is reduced to a single enabled field.
  • ButtonComponentSystem matches ScrollbarComponentSystem: _schema = ['enabled'], an explicit _properties list drives initializeComponentData (guarded on !== undefined so explicit-undefined data does not clobber class-field defaults, per the scroll-view fix), an explicit cloneComponent is added (the default base-class clone would otherwise lose every non-enabled field), and Component._buildAccessors(ButtonComponent.prototype, _schema) handles the enabled accessor.

The schema previously declared hitPadding as type: 'vec4' and the three tints as type: 'rgba', which made ComponentSystem.convertValue clone instances and construct values from arrays. That normalization is moved into the setters so JSON-loaded scenes shipping arrays (e.g. hoverTint: [0.75, 0.75, 0.75, 1]) and callers passing shared instances continue to work. The hitPadding setter passes falsy values through untouched, which ElementInput.buildHitCorners's || ZERO_VEC4 fallback relies on. Tint setters skip the visual reapply when the value is unchanged, so value-equal writes no longer cancel an in-flight fade tween.

Also declares the previously-undeclared _hasHitElementListeners, _isApplyingTint, _isApplyingSprite and _tweenInfo fields (_cancelTween now assigns null instead of delete).

One accepted behaviour nuance (same as the scrollbar/scroll-view migrations): a guid string that fails to resolve via getEntityFromIndex is no longer stashed in the data bag for re-resolution on a later clone.

Tests

Adds test/framework/components/button/component.test.mjs (21 tests), modelled on the ScrollbarComponent suite, covering: defaults; full data round-trip; explicit-undefined handling; tint/hitPadding array conversion and clone-on-set; inactive-state tinting and event suppression; transitionMode switching (tint reset + state sprite applied) and same-value no-op; imageEntity Entity/guid/null resolution and unsubscribe-on-reassign; cloneComponent round-trip and entity remapping; resolveDuplicatedEntityReferenceProperties.

  • npm run lint passes
  • npm test — 1770 passing, 0 failures
  • npm run build:types + npm run test:types pass

Checklist

  • I have read the contributing guidelines
  • My code follows the project's coding standards
  • This PR focuses on a single change

🤖 Generated with Claude Code

…ture

ButtonComponent now owns its 14 properties as private fields with real
setters/getters, inlining the side effects that previously lived in
set_* event listeners. ButtonComponentData is reduced to a single
enabled field. The system drives initializeComponentData from an
explicit _properties list, adds an explicit cloneComponent (the base
clone would otherwise lose every non-enabled field), and builds the
enabled accessor via Component._buildAccessors.

The old schema-driven vec4/rgba conversions move into the hitPadding
and tint setters: falsy values pass through untouched, Color/Vec4
inputs are cloned so caller mutations do not leak, and [r, g, b, a]
arrays from JSON-loaded scenes are converted. Tint setters skip the
visual reapply when the value is unchanged, so value-equal writes no
longer cancel an in-flight fade tween.

Adds a 21-test suite modelled on the ScrollbarComponent tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown

Public API report

This PR changes the public API surface (+8 / −8), per the docs' rules (@ignore / @Private / undocumented are excluded).

Show API diff
-AttributeSchema.type: "string" | "number" | "boolean" | "vec2" | "vec3" | "vec4" | "json" | "rgb" | "rgba" | "asset" | "curve" | "entity"
+AttributeSchema.type: "string" | "number" | "boolean" | "vec2" | "vec3" | "vec4" | "json" | "rgb" | "asset" | "curve" | "entity" | "rgba"
-ButtonComponent.get hoverSpriteAsset(): Asset
+ButtonComponent.get hoverSpriteAsset(): Asset | null
-ButtonComponent.get inactiveSpriteAsset(): Asset
+ButtonComponent.get inactiveSpriteAsset(): Asset | null
-ButtonComponent.get pressedSpriteAsset(): Asset
+ButtonComponent.get pressedSpriteAsset(): Asset | null
-ButtonComponent.set hoverSpriteAsset(arg: Asset)
+ButtonComponent.set hoverSpriteAsset(arg: Asset | null)
-ButtonComponent.set inactiveSpriteAsset(arg: Asset)
+ButtonComponent.set inactiveSpriteAsset(arg: Asset | null)
-ButtonComponent.set pressedSpriteAsset(arg: Asset)
+ButtonComponent.set pressedSpriteAsset(arg: Asset | null)
-ScriptAttributes.add(name: string, args: { array: boolean; assetType: string; color: string; curves: string[]; default: any; description: string; enum: any[]; max: number; min: number; placeholder: string | string[]; precision: number; schema: any[]; size: number; step: number; title: string; type: "string" | "number" | "boolean" | "vec2" | "vec3" | "vec4" | "json" | "rgb" | "rgba" | "asset" | "curve" | "entity" }): void
+ScriptAttributes.add(name: string, args: { array: boolean; assetType: string; color: string; curves: string[]; default: any; description: string; enum: any[]; max: number; min: number; placeholder: string | string[]; precision: number; schema: any[]; size: number; step: number; title: string; type: "string" | "number" | "boolean" | "vec2" | "vec3" | "vec4" | "json" | "rgb" | "asset" | "curve" | "entity" | "rgba" }): void

Informational only — this never fails the build.

The type is inferred from the initializer, matching the bare
/** @Private */ style of the other directly-initialized fields.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Refactors ButtonComponent to match the “CameraComponent-style” architecture used by the recent ScrollbarComponent / ScrollViewComponent migrations, moving state out of the *ComponentData bag and into private fields on the component while preserving runtime behavior and adding a dedicated test suite.

Changes:

  • Migrate ButtonComponent properties from ButtonComponentData into component-owned private fields; inline previous set_* side effects into setters.
  • Update ButtonComponentSystem to a minimal ['enabled'] schema plus explicit property initialization and an explicit cloneComponent.
  • Add a comprehensive ButtonComponent test suite covering defaults, initialization semantics, cloning, and key behavioral regressions (tints, hit padding, transition modes, entity reference resolution).

Reviewed changes

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

File Description
test/framework/components/button/component.test.mjs New unit tests for ButtonComponent covering initialization, behavior, and cloning.
src/framework/components/button/system.js System updated to minimal schema + explicit property init list + explicit cloning.
src/framework/components/button/data.js Data bag reduced to enabled only.
src/framework/components/button/component.js Component refactor: internal state fields, updated setters, lifecycle/listener adjustments, tween handling cleanup.

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

Comment thread test/framework/components/button/component.test.mjs
Comment thread src/framework/components/button/component.js
Comment thread src/framework/components/button/component.js
The helper is only called from the sprite asset/frame setters, so the
domain is Asset|number|null rather than *.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- createButton spreads data before forcing imageEntity, so callers
  cannot silently override the wired image entity
- fix two comments claiming null/undefined pass-through where the
  code passes all falsy values through, matching legacy convertValue

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The six state sprite asset accessors were documented as bare Asset,
but null is their default, numeric asset ids are what scene data
ships (ImageElement.spriteAsset accepts both and ElementComponent
documents it as number), and the getters return whatever was stored.
Declare them Asset|number|null, matching _setTransitionValue.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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.

Numeric asset ids still work internally (scene data ships them and
ImageElement.spriteAsset accepts both), but the public contract
encourages Asset references, so the docs advertise Asset|null only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@willeastcott willeastcott merged commit badd04f into main Jun 11, 2026
9 checks passed
@willeastcott willeastcott deleted the refactor-button-component branch June 11, 2026 13:05
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