You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多步骤滑块表单中如何管理或阻止焦点跨表单切换?

解决表单焦点限制问题:禁止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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 05:59:54