CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-react-types--shared

Shared TypeScript type definitions for React Spectrum components and hooks, providing common interfaces for DOM interactions, styling, accessibility, internationalization, and component behavior across the React Spectrum ecosystem

Pending
Overview
Eval results
Files

events.mddocs/

Event System

Custom event system with propagation control, pointer type awareness, and comprehensive interaction support including press, hover, focus, keyboard, and move events.

Capabilities

Base Event System

Enhanced event types with propagation control and pointer type awareness.

/**
 * Base event type with propagation control
 * @template T The underlying synthetic event type
 */
type BaseEvent<T extends SyntheticEvent> = T & {
  /** @deprecated Use continuePropagation */
  stopPropagation(): void;
  /** Allow event to continue propagating to parent elements */
  continuePropagation(): void;
};

/** Enhanced keyboard event with propagation control */
type KeyboardEvent = BaseEvent<ReactKeyboardEvent<any>>;

/** Pointer interaction types */
type PointerType = "mouse" | "pen" | "touch" | "keyboard" | "virtual";

Press Events

Press events provide a unified interaction model across different input methods.

/**
 * Press event details
 */
interface PressEvent {
  /** The type of press event being fired */
  type: "pressstart" | "pressend" | "pressup" | "press";
  /** The pointer type that triggered the press event */
  pointerType: PointerType;
  /** The target element of the press event */
  target: Element;
  /** Whether the shift keyboard modifier was held during the press event */
  shiftKey: boolean;
  /** Whether the ctrl keyboard modifier was held during the press event */
  ctrlKey: boolean;
  /** Whether the meta keyboard modifier was held during the press event */
  metaKey: boolean;
  /** Whether the alt keyboard modifier was held during the press event */
  altKey: boolean;
  /** X position relative to the target */
  x: number;
  /** Y position relative to the target */
  y: number;
  /**
   * By default, press events stop propagation to parent elements.
   * In cases where a handler decides not to handle a specific event,
   * it can call continuePropagation() to allow a parent to handle it.
   */
  continuePropagation(): void;
}

/**
 * Long press event (extends PressEvent but omits type and continuePropagation)
 */
interface LongPressEvent extends Omit<PressEvent, "type" | "continuePropagation"> {
  /** The type of long press event being fired */
  type: "longpressstart" | "longpressend" | "longpress";
}

/**
 * Press event handlers
 */
interface PressEvents {
  /** Handler that is called when the press is released over the target */
  onPress?: (e: PressEvent) => void;
  /** Handler that is called when a press interaction starts */
  onPressStart?: (e: PressEvent) => void;
  /**
   * Handler that is called when a press interaction ends, either
   * over the target or when the pointer leaves the target
   */
  onPressEnd?: (e: PressEvent) => void;
  /** Handler that is called when the press state changes */
  onPressChange?: (isPressed: boolean) => void;
  /**
   * Handler that is called when a press is released over the target, regardless of
   * whether it started on the target or not
   */
  onPressUp?: (e: PressEvent) => void;
  /**
   * Not recommended – use onPress instead. onClick is an alias for onPress
   * provided for compatibility with other libraries. onPress provides 
   * additional event details for non-mouse interactions.
   */
  onClick?: (e: MouseEvent<FocusableElement>) => void;
}

Hover Events

Hover events for mouse and pen interactions.

/**
 * Hover event details
 */
interface HoverEvent {
  /** The type of hover event being fired */
  type: "hoverstart" | "hoverend";
  /** The pointer type that triggered the hover event */
  pointerType: "mouse" | "pen";
  /** The target element of the hover event */
  target: HTMLElement;
}

/**
 * Hover event handlers
 */
interface HoverEvents {
  /** Handler that is called when a hover interaction starts */
  onHoverStart?: (e: HoverEvent) => void;
  /** Handler that is called when a hover interaction ends */
  onHoverEnd?: (e: HoverEvent) => void;
  /** Handler that is called when the hover state changes */
  onHoverChange?: (isHovering: boolean) => void;
}

Focus Events

Focus event handlers with focus state tracking.

/**
 * Focus event handlers
 * @template Target The type of the target element
 */
interface FocusEvents<Target = Element> {
  /** Handler that is called when the element receives focus */
  onFocus?: (e: FocusEvent<Target>) => void;
  /** Handler that is called when the element loses focus */
  onBlur?: (e: FocusEvent<Target>) => void;
  /** Handler that is called when the element's focus status changes */
  onFocusChange?: (isFocused: boolean) => void;
}

/**
 * Properties for focusable elements
 * @template Target The type of the target element
 */
interface FocusableProps<Target = Element> extends FocusEvents<Target>, KeyboardEvents {
  /** Whether the element should receive focus on render */
  autoFocus?: boolean;
}

Keyboard Events

Keyboard event handlers.

/**
 * Keyboard event handlers
 */
interface KeyboardEvents {
  /** Handler that is called when a key is pressed */
  onKeyDown?: (e: KeyboardEvent) => void;
  /** Handler that is called when a key is released */
  onKeyUp?: (e: KeyboardEvent) => void;
}

Move Events

Move events for drag-like interactions without drag and drop.

/**
 * Base properties for move events
 */
interface BaseMoveEvent {
  /** The pointer type that triggered the move event */
  pointerType: PointerType;
  /** Whether the shift keyboard modifier was held during the move event */
  shiftKey: boolean;
  /** Whether the ctrl keyboard modifier was held during the move event */
  ctrlKey: boolean;
  /** Whether the meta keyboard modifier was held during the move event */
  metaKey: boolean;
  /** Whether the alt keyboard modifier was held during the move event */
  altKey: boolean;
}

/**
 * Move start event
 */
interface MoveStartEvent extends BaseMoveEvent {
  /** The type of move event being fired */
  type: "movestart";
}

/**
 * Move event with delta information
 */
interface MoveMoveEvent extends BaseMoveEvent {
  /** The type of move event being fired */
  type: "move";
  /** The amount moved in the X direction since the last event */
  deltaX: number;
  /** The amount moved in the Y direction since the last event */
  deltaY: number;
}

/**
 * Move end event
 */
interface MoveEndEvent extends BaseMoveEvent {
  /** The type of move event being fired */
  type: "moveend";
}

/** Union of all move event types */
type MoveEvent = MoveStartEvent | MoveMoveEvent | MoveEndEvent;

/**
 * Move event handlers
 */
interface MoveEvents {
  /** Handler that is called when a move interaction starts */
  onMoveStart?: (e: MoveStartEvent) => void;
  /** Handler that is called when the element is moved */
  onMove?: (e: MoveMoveEvent) => void;
  /** Handler that is called when a move interaction ends */
  onMoveEnd?: (e: MoveEndEvent) => void;
}

Scroll Events

Scroll event support with delta information.

/**
 * Scroll event details
 */
interface ScrollEvent {
  /** The amount moved in the X direction since the last event */
  deltaX: number;
  /** The amount moved in the Y direction since the last event */
  deltaY: number;
}

/**
 * Scroll event handlers
 */
interface ScrollEvents {
  /** Handler that is called when the scroll wheel moves */
  onScroll?: (e: ScrollEvent) => void;
}

Usage Examples:

import { 
  PressEvents, 
  HoverEvents, 
  FocusableProps, 
  MoveEvents,
  PressEvent,
  HoverEvent,
  KeyboardEvent 
} from "@react-types/shared";

// Interactive button with press and hover support
interface InteractiveButtonProps extends PressEvents, HoverEvents, FocusableProps {
  children: React.ReactNode;
  isDisabled?: boolean;
}

function InteractiveButton({ 
  children, 
  isDisabled,
  onPress,
  onPressStart,
  onPressEnd,
  onHoverStart,
  onHoverEnd,
  onFocus,
  onBlur,
  onKeyDown,
  autoFocus 
}: InteractiveButtonProps) {
  const [isPressed, setIsPressed] = useState(false);
  const [isHovered, setIsHovered] = useState(false);
  const [isFocused, setIsFocused] = useState(false);

  const handlePress = (e: PressEvent) => {
    if (isDisabled) return;
    console.log(`Pressed with ${e.pointerType} at (${e.x}, ${e.y})`);
    onPress?.(e);
  };

  const handlePressStart = (e: PressEvent) => {
    if (isDisabled) return;
    setIsPressed(true);
    onPressStart?.(e);
  };

  const handlePressEnd = (e: PressEvent) => {
    setIsPressed(false);
    onPressEnd?.(e);
  };

  const handleHoverStart = (e: HoverEvent) => {
    if (isDisabled) return;
    setIsHovered(true);
    onHoverStart?.(e);
  };

  const handleHoverEnd = (e: HoverEvent) => {
    setIsHovered(false);
    onHoverEnd?.(e);
  };

  const handleKeyDown = (e: KeyboardEvent) => {
    if (isDisabled) return;
    if (e.key === "Enter" || e.key === " ") {
      // Simulate press event for keyboard interaction
      handlePress({
        type: "press",
        pointerType: "keyboard",
        target: e.target as Element,
        shiftKey: e.shiftKey,
        ctrlKey: e.ctrlKey,
        metaKey: e.metaKey,
        altKey: e.altKey,
        x: 0,
        y: 0,
        continuePropagation: () => {}
      });
    }
    onKeyDown?.(e);
  };

  return (
    <button
      disabled={isDisabled}
      autoFocus={autoFocus}
      onMouseDown={(e) => handlePressStart({
        type: "pressstart",
        pointerType: "mouse",
        target: e.target as Element,
        shiftKey: e.shiftKey,
        ctrlKey: e.ctrlKey,
        metaKey: e.metaKey,
        altKey: e.altKey,
        x: e.clientX,
        y: e.clientY,
        continuePropagation: () => {}
      })}
      onMouseUp={(e) => handlePressEnd({
        type: "pressend",
        pointerType: "mouse",
        target: e.target as Element,
        shiftKey: e.shiftKey,
        ctrlKey: e.ctrlKey,
        metaKey: e.metaKey,
        altKey: e.altKey,
        x: e.clientX,
        y: e.clientY,
        continuePropagation: () => {}
      })}
      onClick={(e) => handlePress({
        type: "press",
        pointerType: "mouse",
        target: e.target as Element,
        shiftKey: e.shiftKey,
        ctrlKey: e.ctrlKey,
        metaKey: e.metaKey,
        altKey: e.altKey,
        x: e.clientX,
        y: e.clientY,
        continuePropagation: () => {}
      })}
      onMouseEnter={(e) => handleHoverStart({
        type: "hoverstart",
        pointerType: "mouse",
        target: e.target as HTMLElement
      })}
      onMouseLeave={(e) => handleHoverEnd({
        type: "hoverend",
        pointerType: "mouse",
        target: e.target as HTMLElement
      })}
      onFocus={(e) => {
        setIsFocused(true);
        onFocus?.(e);
      }}
      onBlur={(e) => {
        setIsFocused(false);
        onBlur?.(e);
      }}
      onKeyDown={handleKeyDown}
      style={{
        backgroundColor: isPressed ? "#ccc" : isHovered ? "#eee" : "#fff",
        outline: isFocused ? "2px solid blue" : "none",
        opacity: isDisabled ? 0.5 : 1
      }}
    >
      {children}
    </button>
  );
}

// Draggable element with move events
interface DraggableProps extends MoveEvents {
  children: React.ReactNode;
}

function Draggable({ children, onMoveStart, onMove, onMoveEnd }: DraggableProps) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [isDragging, setIsDragging] = useState(false);

  const handleMoveStart = (e: MouseEvent) => {
    setIsDragging(true);
    onMoveStart?.({
      type: "movestart",
      pointerType: "mouse",
      shiftKey: e.shiftKey,
      ctrlKey: e.ctrlKey,
      metaKey: e.metaKey,
      altKey: e.altKey
    });
  };

  const handleMove = (e: MouseEvent) => {
    if (!isDragging) return;
    
    const deltaX = e.movementX;
    const deltaY = e.movementY;
    
    setPosition(prev => ({
      x: prev.x + deltaX,
      y: prev.y + deltaY
    }));

    onMove?.({
      type: "move",
      pointerType: "mouse",
      deltaX,
      deltaY,
      shiftKey: e.shiftKey,
      ctrlKey: e.ctrlKey,
      metaKey: e.metaKey,
      altKey: e.altKey
    });
  };

  const handleMoveEnd = (e: MouseEvent) => {
    setIsDragging(false);
    onMoveEnd?.({
      type: "moveend",
      pointerType: "mouse",
      shiftKey: e.shiftKey,
      ctrlKey: e.ctrlKey,
      metaKey: e.metaKey,
      altKey: e.altKey
    });
  };

  useEffect(() => {
    if (isDragging) {
      document.addEventListener("mousemove", handleMove);
      document.addEventListener("mouseup", handleMoveEnd);
      return () => {
        document.removeEventListener("mousemove", handleMove);
        document.removeEventListener("mouseup", handleMoveEnd);
      };
    }
  }, [isDragging]);

  return (
    <div
      style={{
        position: "absolute",
        left: position.x,
        top: position.y,
        cursor: isDragging ? "grabbing" : "grab"
      }}
      onMouseDown={handleMoveStart}
    >
      {children}
    </div>
  );
}

// Keyboard navigation component
interface KeyboardNavigationProps extends KeyboardEvents, FocusableProps {
  items: string[];
  onSelect?: (item: string, index: number) => void;
}

function KeyboardNavigation({ 
  items, 
  onSelect, 
  onKeyDown, 
  onFocus, 
  onBlur, 
  autoFocus 
}: KeyboardNavigationProps) {
  const [selectedIndex, setSelectedIndex] = useState(0);

  const handleKeyDown = (e: KeyboardEvent) => {
    switch (e.key) {
      case "ArrowDown":
        e.preventDefault();
        setSelectedIndex(prev => Math.min(prev + 1, items.length - 1));
        break;
      case "ArrowUp":
        e.preventDefault();
        setSelectedIndex(prev => Math.max(prev - 1, 0));
        break;
      case "Enter":
        e.preventDefault();
        onSelect?.(items[selectedIndex], selectedIndex);
        break;
      case "Home":
        e.preventDefault();
        setSelectedIndex(0);
        break;
      case "End":
        e.preventDefault();
        setSelectedIndex(items.length - 1);
        break;
    }
    onKeyDown?.(e);
  };

  return (
    <div
      tabIndex={0}
      autoFocus={autoFocus}
      onKeyDown={handleKeyDown}
      onFocus={onFocus}
      onBlur={onBlur}
      role="listbox"
      aria-activedescendant={`item-${selectedIndex}`}
    >
      {items.map((item, index) => (
        <div
          key={index}
          id={`item-${index}`}
          role="option"
          aria-selected={index === selectedIndex}
          style={{
            backgroundColor: index === selectedIndex ? "#e0e0e0" : "transparent",
            padding: "8px"
          }}
          onClick={() => {
            setSelectedIndex(index);
            onSelect?.(item, index);
          }}
        >
          {item}
        </div>
      ))}
    </div>
  );
}

Install with Tessl CLI

npx tessl i tessl/npm-react-types--shared

docs

collections.md

design-tokens.md

dom-aria.md

drag-drop.md

events.md

index.md

input-handling.md

labelable.md

refs.md

selection.md

styling.md

tile.json