-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(aria/spinbutton): add aria spinbutton component #32663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
8f419d2 to
19a53fd
Compare
249c6f2 to
76f3abf
Compare
Implements a spinbutton ARIA primitive as a compound component following the W3C APG spinbutton pattern. The implementation includes: - SpinButtonPattern class with value management, keyboard handling, and wrap/clamp behavior - SpinButton parent directive for container and state management - SpinButtonInput directive for the focusable element (supports both input and span elements) - SpinButtonIncrement/Decrement button directives - Comprehensive test coverage - Two dev-app examples: APG hotel guest counter and time field segments
76f3abf to
8a1a79f
Compare
| afterRenderEffect(() => { | ||
| if (typeof ngDevMode === 'undefined' || ngDevMode) { | ||
| const violations = this._pattern.validate(); | ||
| for (const violation of violations) { | ||
| console.error(violation); | ||
| } | ||
| } | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In prod mode this would register an empty effect.
It's better to have it around the afterRenderEffect
| afterRenderEffect(() => { | |
| if (typeof ngDevMode === 'undefined' || ngDevMode) { | |
| const violations = this._pattern.validate(); | |
| for (const violation of violations) { | |
| console.error(violation); | |
| } | |
| } | |
| }); | |
| if (typeof ngDevMode === 'undefined' || ngDevMode) { | |
| afterRenderEffect(() => { | |
| const violations = this._pattern.validate(); | |
| for (const violation of violations) { | |
| console.error(violation); | |
| } | |
| }); | |
| } |
| if (!this._hasFocused()) { | ||
| this._pattern.setDefaultState(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the purpose of this ? setDefaultState is empty
| private _hasFocused = signal(false); | ||
|
|
||
| /** The UI pattern instance for this spinbutton. */ | ||
| readonly _pattern: SpinButtonPattern = new SpinButtonPattern({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since it's not private
| readonly _pattern: SpinButtonPattern = new SpinButtonPattern({ | |
| readonly pattern: SpinButtonPattern = new SpinButtonPattern({ |
| } | ||
|
|
||
| // @public | ||
| export class SpinButtonPattern { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it really belong in the public API?
| } | ||
|
|
||
| /** Called when the input receives focus. */ | ||
| _onFocus(): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this supposed to be private ?
| } | ||
|
|
||
| /** Handles pointerdown events for the spinbutton. */ | ||
| onPointerdown(_event: PointerEvent): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be protected ?
| readonly spinButton = inject(SPINBUTTON); | ||
|
|
||
| /** Whether the increment button should be disabled. */ | ||
| readonly _isDisabled = computed(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| readonly _isDisabled = computed(() => { | |
| protected readonly _isDisabled = computed(() => { |
| }); | ||
|
|
||
| /** Handles click events on the increment button. */ | ||
| _onClick(): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| _onClick(): void { | |
| protected _onClick(): void { |
| readonly spinButton = inject(SPINBUTTON); | ||
|
|
||
| /** Whether the decrement button should be disabled. */ | ||
| readonly _isDisabled = computed(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| readonly _isDisabled = computed(() => { | |
| protected readonly _isDisabled = computed(() => { |
| }); | ||
|
|
||
| /** Handles click events on the decrement button. */ | ||
| _onClick(): void { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| _onClick(): void { | |
| protected _onClick(): void { |
| '[attr.aria-controls]': 'spinButton.inputId()', | ||
| '[attr.aria-disabled]': '_isDisabled() || null', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is now supported out of the box (since 20.2)
| '[attr.aria-controls]': 'spinButton.inputId()', | |
| '[attr.aria-disabled]': '_isDisabled() || null', | |
| '[aria-controls]': 'spinButton.inputId()', | |
| '[aria-disabled]': '_isDisabled() || null', |
| } | ||
|
|
||
| /** Sets the spinbutton to its default initial state. */ | ||
| setDefaultState(): void {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is an implementation missing here ?
This PR implements the ARIA spinbutton pattern based on WAI-ARIA guidelines.
WAI-ARIA Pattern Reference:
https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/
Completed:
ngSpinButton,ngSpinButtonInput,ngSpinButtonIncrement,ngSpinButtonDecrementaria-valuenow,aria-valuetext,aria-valuemin,aria-valuemax,aria-disabled,aria-readonly,aria-invalidFuture Enhancements:
aria-requiredattribute support