CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-material--dom

DOM manipulation utilities for Material Components providing cross-browser compatibility, focus management, and accessibility features.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

keyboard.mddocs/

Keyboard Utilities

Cross-browser keyboard event normalization utilities that provide consistent key handling across different browsers and platforms.

Capabilities

Key Constants

Normalized string values for common keyboard keys.

const KEY: {
  readonly UNKNOWN: 'Unknown';
  readonly BACKSPACE: 'Backspace';
  readonly ENTER: 'Enter';
  readonly SPACEBAR: 'Spacebar';
  readonly PAGE_UP: 'PageUp';
  readonly PAGE_DOWN: 'PageDown';
  readonly END: 'End';
  readonly HOME: 'Home';
  readonly ARROW_LEFT: 'ArrowLeft';
  readonly ARROW_UP: 'ArrowUp';
  readonly ARROW_RIGHT: 'ArrowRight';
  readonly ARROW_DOWN: 'ArrowDown';
  readonly DELETE: 'Delete';
  readonly ESCAPE: 'Escape';
  readonly TAB: 'Tab';
};

Key Normalization

Returns a normalized string for keyboard events that works consistently across browsers.

/**
 * Returns the normalized string for a navigational action derived from KeyboardEvent
 * to be standard across browsers
 * @param evt - The keyboard event to normalize
 * @returns Normalized key string or 'Unknown' if not recognized
 */
function normalizeKey(evt: KeyboardEvent): string;

Navigation Event Detection

Determine if a keyboard event represents a navigation action.

/**
 * Returns whether the event is a navigation event (Page Up, Page Down, Home, End, Arrow keys)
 * @param evt - The keyboard event to test
 * @returns True if the event is a navigation event
 */
function isNavigationEvent(evt: KeyboardEvent): boolean;

Usage Examples

Basic Key Handling

import { KEY, normalizeKey } from '@material/dom/keyboard';

document.addEventListener('keydown', (event) => {
  const key = normalizeKey(event);
  
  switch (key) {
    case KEY.ENTER:
    case KEY.SPACEBAR:
      // Activate element
      handleActivation();
      break;
    case KEY.ESCAPE:
      // Close modal/menu
      closeModal();
      break;
    case KEY.ARROW_UP:
    case KEY.ARROW_DOWN:
      // Navigate list
      navigateList(key === KEY.ARROW_UP ? -1 : 1);
      break;
  }
});

Navigation Event Handling

import { isNavigationEvent, normalizeKey, KEY } from '@material/dom/keyboard';

function handleKeydown(event: KeyboardEvent) {
  if (isNavigationEvent(event)) {
    event.preventDefault();
    
    const key = normalizeKey(event);
    switch (key) {
      case KEY.ARROW_LEFT:
      case KEY.ARROW_RIGHT:
        handleHorizontalNavigation(key === KEY.ARROW_RIGHT ? 1 : -1);
        break;
      case KEY.ARROW_UP:
      case KEY.ARROW_DOWN:
        handleVerticalNavigation(key === KEY.ARROW_DOWN ? 1 : -1);
        break;
      case KEY.HOME:
        focusFirst();
        break;
      case KEY.END:
        focusLast();
        break;
      case KEY.PAGE_UP:
      case KEY.PAGE_DOWN:
        handlePageNavigation(key === KEY.PAGE_DOWN ? 1 : -1);
        break;
    }
  }
}

Menu Navigation

import { KEY, normalizeKey, isNavigationEvent } from '@material/dom/keyboard';

class MenuComponent {
  private currentIndex = 0;
  private menuItems: HTMLElement[] = [];

  handleKeydown(event: KeyboardEvent) {
    const key = normalizeKey(event);

    if (key === KEY.ESCAPE) {
      this.closeMenu();
      return;
    }

    if (key === KEY.ENTER || key === KEY.SPACEBAR) {
      this.selectCurrentItem();
      return;
    }

    if (isNavigationEvent(event)) {
      event.preventDefault();
      
      switch (key) {
        case KEY.ARROW_UP:
          this.moveFocus(-1);
          break;
        case KEY.ARROW_DOWN:
          this.moveFocus(1);
          break;
        case KEY.HOME:
          this.focusItem(0);
          break;
        case KEY.END:
          this.focusItem(this.menuItems.length - 1);
          break;
      }
    }
  }

  private moveFocus(direction: number) {
    this.currentIndex = Math.max(0, Math.min(
      this.menuItems.length - 1,
      this.currentIndex + direction
    ));
    this.focusItem(this.currentIndex);
  }
}

Form Validation

import { KEY, normalizeKey } from '@material/dom/keyboard';

function handleFormKeydown(event: KeyboardEvent) {
  const key = normalizeKey(event);
  
  if (key === KEY.ENTER) {
    const target = event.target as HTMLElement;
    
    if (target.tagName === 'TEXTAREA') {
      // Allow line breaks in textarea
      return;
    }
    
    if (target.tagName === 'INPUT') {
      // Submit form or move to next field
      event.preventDefault();
      submitFormOrFocusNext();
    }
  }
  
  if (key === KEY.ESCAPE) {
    // Cancel form editing
    cancelFormEditing();
  }
}

Cross-Browser Compatibility

The keyboard utilities handle differences between browsers:

Modern Browsers

  • Use the standard KeyboardEvent.key property when available
  • Return consistent string values per W3C specification

Legacy Browsers

  • Fall back to KeyboardEvent.keyCode property
  • Map numeric key codes to standard key strings
  • Handle vendor-specific key code variations

Key Mapping

The normalization handles these browser differences:

KeyModern (event.key)Legacy (event.keyCode)
Enter'Enter'13
Space' ''Spacebar'32
Arrow Up'ArrowUp'38
Arrow Down'ArrowDown'40
Escape'Escape'27

Navigation Keys

The following keys are considered navigation events:

  • PAGE_UP, PAGE_DOWN - Page navigation
  • HOME, END - Document/container boundaries
  • ARROW_LEFT, ARROW_RIGHT - Horizontal navigation
  • ARROW_UP, ARROW_DOWN - Vertical navigation

Unknown Keys

Keys that aren't recognized return KEY.UNKNOWN to allow graceful handling of:

  • Unmapped key codes in legacy browsers
  • New keys not in the normalization table
  • Platform-specific keys

docs

announce.md

events.md

focus-trap.md

index.md

keyboard.md

ponyfill.md

tile.json