多步骤滑块表单中如何管理或阻止焦点跨表单切换?
解决表单焦点限制问题:禁止Tab键跳转到未激活的表单
核心思路
要实现焦点仅限制在当前验证通过的表单内,需要从两方面入手:一是让未激活的表单元素无法被Tab键选中,二是拦截Tab键事件,确保在当前表单内形成焦点循环,不会跳出到其他表单。
具体实现步骤
1. 标记激活表单并控制元素Tab可访问性
在切换表单的slide()方法中,给当前激活的表单添加标识类,同时修改非激活表单内元素的tabindex属性,让它们暂时无法被Tab键聚焦:
async slide() { const currentIndex = await firstValueFrom(this.currentStepIndex$) const slideItems = this.sliderContainer.nativeElement.querySelectorAll('.slideItem') slideItems.forEach((item: HTMLElement, index: number) => { this.renderer.setStyle(item, 'transform', `translateX(${-100 * currentIndex}%)`) // 处理激活状态与Tab可访问性 if (index === currentIndex) { this.renderer.addClass(item, 'active-form') // 恢复当前表单元素的原有tabindex item.querySelectorAll('button, input, select, textarea, [tabindex]:not([tabindex="-1"])').forEach(el => { if (el.hasAttribute('data-original-tabindex')) { el.setAttribute('tabindex', el.getAttribute('data-original-tabindex')!) el.removeAttribute('data-original-tabindex') } }) } else { this.renderer.removeClass(item, 'active-form') // 保存原tabindex并设置为-1,禁止Tab聚焦 item.querySelectorAll('button, input, select, textarea, [tabindex]:not([tabindex="-1"])').forEach(el => { const originalTabIndex = el.getAttribute('tabindex') || '0' el.setAttribute('data-original-tabindex', originalTabIndex) el.setAttribute('tabindex', '-1') }) } }) }
2. 拦截Tab键事件,实现当前表单内焦点循环
在组件初始化完成后,给容器添加Tab键监听,确保焦点在当前表单的首尾元素间循环:
ngAfterViewInit() { const handleTabKey = (e: KeyboardEvent) => { if (e.key !== 'Tab') return const activeForm = this.sliderContainer.nativeElement.querySelector('.active-form') if (!activeForm) return // 获取当前表单内所有可聚焦元素 const focusableElements = Array.from(activeForm.querySelectorAll( 'button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"]):not([disabled])' )) as HTMLElement[] if (focusableElements.length === 0) return const firstEl = focusableElements[0] const lastEl = focusableElements[focusableElements.length - 1] // 处理反向Tab(Shift+Tab) if (e.shiftKey) { if (document.activeElement === firstEl) { e.preventDefault() lastEl.focus() } } else { // 处理正向Tab if (document.activeElement === lastEl) { e.preventDefault() firstEl.focus() } } } this.sliderContainer.nativeElement.addEventListener('keydown', handleTabKey) // 组件销毁时移除监听,避免内存泄漏 this.destroy$ = new Subject<void>() this.destroy$.subscribe(() => { this.sliderContainer.nativeElement.removeEventListener('keydown', handleTabKey) }) } ngOnDestroy() { this.destroy$.next() this.destroy$.complete() }
3. 初始状态处理
在组件初始化时,需要给第一个表单设置激活状态并处理Tab可访问性,可在ngOnInit中调用一次初始化逻辑:
async ngOnInit() { // 初始化第一个表单的激活状态 const initialIndex = await firstValueFrom(this.currentStepIndex$) const slideItems = this.sliderContainer.nativeElement.querySelectorAll('.slideItem') slideItems.forEach((item: HTMLElement, index: number) => { if (index === initialIndex) { this.renderer.addClass(item, 'active-form') } else { item.querySelectorAll('button, input, select, textarea, [tabindex]:not([tabindex="-1"])').forEach(el => { const originalTabIndex = el.getAttribute('tabindex') || '0' el.setAttribute('data-original-tabindex', originalTabIndex) el.setAttribute('tabindex', '-1') }) } }) }
内容的提问来源于stack exchange,提问作者Steeven Delucis
相关产品推荐
相关产品推荐

