diff --git a/projects/element-ng/status-toggle/si-status-toggle.component.html b/projects/element-ng/status-toggle/si-status-toggle.component.html index c85da6ee50..cbcaae6380 100644 --- a/projects/element-ng/status-toggle/si-status-toggle.component.html +++ b/projects/element-ng/status-toggle/si-status-toggle.component.html @@ -25,12 +25,17 @@ class="status-toggle-item focus-inside px-4" role="radio" [attr.aria-checked]="$index === selIndexValue" - [attr.tabindex]="isDisabled() || item.disabled ? '' : '0'" + [attr.tabindex]=" + isDisabled() || item.disabled ? null : $index === focusedIndex() ? '0' : '-1' + " [attr.aria-disabled]="isDisabled() || item.disabled" [class.active]="$index === selIndexValue" [class.disabled]="item.disabled || isDisabled()" [style.width.%]="100 / $count" + (keydown.space)="$event.preventDefault(); selectItem($index)" (keydown.enter)="selectItem($index)" + (keydown.arrowRight)="navigateFocus($event, $index, 1)" + (keydown.arrowLeft)="navigateFocus($event, $index, -1)" > ('container'); private readonly draggableElement = viewChild.required('draggable'); + private readonly itemElements = viewChildren>('items'); private boundingX = 0; private x0 = 0; @@ -66,6 +71,7 @@ export class SiStatusToggleComponent implements ControlValueAccessor, OnInit, On private readonly internalDisabled = signal(false); protected readonly selectedIndex = signal(undefined); + protected readonly focusedIndex = signal(0); protected readonly draggablePosition = signal(''); protected readonly animated = signal(false); protected readonly isDisabled = computed(() => this.disabled() || this.internalDisabled()); @@ -81,6 +87,12 @@ export class SiStatusToggleComponent implements ControlValueAccessor, OnInit, On if (selectedIndex >= 0) { this.selectItem(selectedIndex, false, false); } + // If nothing ended up selected (no match, or matched item is disabled), + // ensure focusedIndex points to the first reachable (non-disabled) item + if (this.selectedIndex() === undefined) { + const firstEnabled = this.items().findIndex(item => !item.disabled); + this.focusedIndex.set(Math.max(0, firstEnabled)); + } } ngOnDestroy(): void { @@ -212,8 +224,13 @@ export class SiStatusToggleComponent implements ControlValueAccessor, OnInit, On this.value.set(value.value); this.animated.set(animated); this.selectedIndex.set(index); + this.focusedIndex.set(index); this.draggablePosition.set(`${(100 / values.length) * index}%`); + if (emit) { + this.itemElements()[index]?.nativeElement.focus(); + } + if (emit && index !== prevIndex) { if (this.onChange) { this.onChange(this.value()!); @@ -225,4 +242,18 @@ export class SiStatusToggleComponent implements ControlValueAccessor, OnInit, On } } } + + protected navigateFocus(event: Event, fromIndex: number, direction: 1 | -1): void { + event.preventDefault(); + const items = this.items(); + const step = isRTL() ? -direction : direction; + let targetIndex = (fromIndex + step + items.length) % items.length; + while (targetIndex !== fromIndex && items[targetIndex].disabled) { + targetIndex = (targetIndex + step + items.length) % items.length; + } + if (!items[targetIndex].disabled) { + this.focusedIndex.set(targetIndex); + this.itemElements()[targetIndex].nativeElement.focus(); + } + } }