CxJS

findFirst / closest

import { findFirst, closest } from 'cx/util'; Copied

CxJS provides utility functions for traversing the DOM tree to find elements matching a condition.

findFirst

Searches the element and its descendants (depth-first) for the first element matching a condition.

import { findFirst } from "cx/util";

// Find first focusable element
const focusable = findFirst(container, (el) => el.tagName === "INPUT");

// Find first element with a specific class
const element = findFirst(container, (el) => el.classList.contains("target"));

// Find first button
const button = findFirst(container, (el) => el.tagName === "BUTTON");

findFirstChild

Similar to findFirst but only searches among children, not the element itself.

import { findFirstChild } from "cx/util";

// Find first child input (excluding the container itself)
const input = findFirstChild(form, (el) => el.tagName === "INPUT");

closest

Searches up the DOM tree (element and ancestors) for the first element matching a condition.

import { closest } from "cx/util";

// Find closest scrollable parent
const scrollable = closest(element, (el) => {
  const overflow = getComputedStyle(el).overflow;
  return overflow === "auto" || overflow === "scroll";
});

// Find closest element with data attribute
const dataParent = closest(element, (el) => el.hasAttribute("data-id"));

// Find closest form
const form = closest(input, (el) => el.tagName === "FORM");

closestParent

Similar to closest but starts from the parent element, excluding the element itself.

import { closestParent } from "cx/util";

// Find the parent container (not the element itself)
const container = closestParent(element, (el) =>
  el.classList.contains("container")
);

Type-Safe Usage

The functions support TypeScript type guards for narrowing.

import { findFirst, closest } from "cx/util";

// Type guard for input elements
function isInput(el: Element): el is HTMLInputElement {
  return el.tagName === "INPUT";
}

const input = findFirst(container, isInput);
// input is typed as HTMLInputElement | null

// Type guard for button elements
function isButton(el: Element): el is HTMLButtonElement {
  return el.tagName === "BUTTON";
}

const button = closest(element, isButton);
// button is typed as HTMLButtonElement | null

Common Use Cases

Focus Management

import { findFirst } from "cx/util";
import { isFocusable } from "cx/util";

function focusFirstInput(container: Element): void {
  const input = findFirst(container, isFocusable);
  if (input) {
    input.focus();
  }
}

Event Delegation

import { closest } from "cx/util";

function handleClick(e: MouseEvent) {
  const target = e.target as Element;

  // Find the row that was clicked
  const row = closest(target, (el) => el.classList.contains("grid-row"));
  if (row) {
    const rowId = row.getAttribute("data-id");
    // Handle row click
  }
}

API

findFirst

function findFirst<T extends Element>(
  el: Element,
  condition: (el: Element) => el is T
): T | null;
function findFirst(
  el: Element,
  condition: (el: Element) => boolean
): Element | null;

findFirstChild

function findFirstChild<T extends Element>(
  el: Element,
  condition: (el: Element) => el is T
): T | null;
function findFirstChild(
  el: Element,
  condition: (el: Element) => boolean
): Element | null;

closest

function closest(
  el: Element | null,
  condition: (el: Element) => boolean
): Element | null;

closestParent

function closestParent(
  el: Element,
  condition: (el: Element) => boolean
): HTMLElement | null;
ParameterTypeDescription
elElementThe starting element
condition(el: Element) => booleanFunction that returns true for matching elements

Returns: The first matching element, or null if none found.