Util

trapFocus

Keep keyboard focus cycling inside a container — wrap your dialog / popover's keydown handler so Tab and Shift+Tab loop between the first and last focusable child.

Usage

import { trapFocus } from 'ngwr/utils';

@HostListener('keydown', ['$event']) onKey(e: KeyboardEvent) {
  // Cycle Tab focus inside the dialog while it's open.
  trapFocus(dialogRef.nativeElement, e);
}

Why ngwr provides this

Doing this by hand is ~30 lines of Tab detection, focusable enumeration, and active-element checks — repeated in every dialog, drawer, command palette, and tooltip-with-actions. Getting it wrong is an accessibility bug. One helper, called from any keydown handler, makes the trap a one-liner.

// Native — what an accessible dialog actually needs to do.
@HostListener('keydown', ['$event'])
onKey(e: KeyboardEvent) {
  if (e.key !== 'Tab') return;
  const focusables = getAllVisibleFocusableSortedByTabindex(this.el.nativeElement);
  if (focusables.length === 0) return;
  const first = focusables[0];
  const last = focusables[focusables.length - 1];
  const active = document.activeElement;
  if (e.shiftKey && active === first) { e.preventDefault(); last.focus(); }
  else if (!e.shiftKey && active === last) { e.preventDefault(); first.focus(); }
}
// → Then you implement `getAllVisibleFocusableSortedByTabindex` per dialog.

// ngwr — one line.
@HostListener('keydown', ['$event'])
onKey(e: KeyboardEvent) {
  trapFocus(this.el.nativeElement, e);
}

API

NameDescriptionTypeDefault
trapFocus(root, event)Cycle Tab focus inside root — call from a keydown handler. Returns true when the event was a Tab that got handled.(root: HTMLElement, e: KeyboardEvent) => boolean