npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@blockquote/dev-utilities

v1.0.0

Published

Developer utilities for handling DOM, events, and other web development tasks

Downloads

54

Readme

Developer utilities for handling DOM, events, and other web development tasks

This repository provides a set of utility functions designed to facilitate DOM manipulation, event handling, and other common tasks in web development. These functions address various needs such as re-dispatching events, checking element visibility, and traversing the DOM tree.

Table of Contents

Re-dispatch Event Functions

redispatchEventFromEvent

Re-dispatches an event from the provided element. This function stops the propagation of bubbling events and dispatches a copy of the event from the specified element.

/**
 * Re-dispatches an event from the provided element.
 *
 * @param {Element} element - The element to dispatch the event from.
 * @param {Event} ev - The event to re-dispatch.
 * @param {Object} [options={}] - An object with properties to override in the new event.
 * @returns {boolean} - Whether or not the event was dispatched (if cancelable).
 */
const redispatchEventFromEvent = (element, ev, options = {}) => {
  if (ev.bubbles && (!element.shadowRoot || ev.composed)) {
    ev.stopPropagation();
  }

  const copy = Reflect.construct(ev.constructor, [ev.type, { ...ev, ...options }]);
  const dispatched = element.dispatchEvent(copy);
  if (!dispatched) {
    ev.preventDefault();
  }

  return dispatched;
};

redispatchEvent

This function simplifies re-dispatching an event. If the event parameter is a string, it creates a new CustomEvent and dispatches it.

/**
 * Re-dispatches an event from the provided element.
 *
 * @param {Element} element - The element to dispatch the event from.
 * @param {Event|string} ev - The event to re-dispatch. If it's a string, a new Event is created.
 * @param {Object} [options={}] - An object with properties to override in the new event.
 * @returns {boolean} - Whether or not the event was dispatched (if cancelable).
 */
export const redispatchEvent = (element, ev, options = {}) => {
  if (typeof ev === 'string') {
    const eventType = ev;
    const newEvent = new CustomEvent(eventType);
    return redispatchEventFromEvent(element, newEvent, options);
  }
  return redispatchEventFromEvent(element, ev, options);
};

Element Visibility and Focus Functions

isElementInvisible

Checks if an element should be ignored based on its visibility or specific attributes.

/**
 * Checks if an element should be ignored.
 *
 * @param {Element} element - The DOM element to check.
 * @param {Array} [exceptions=['dialog', '[popover]']] - Array of Elements to ignore when checking the element.
 * @returns {boolean} True if the element should be ignored by a screen reader, false otherwise.
 */
export const isElementInvisible = (element, exceptions = ['dialog', '[popover]']) => {
  if (!element || !(element instanceof HTMLElement)) {
    return false;
  }

  if (element.matches(exceptions.join(','))) {
    return false;
  }

  const computedStyle = window.getComputedStyle(element);
  const isStyleHidden = computedStyle.display === 'none' || computedStyle.visibility === 'hidden';
  const isAttributeHidden = element.matches('[disabled], [hidden], [inert], [aria-hidden="true"]');

  return isStyleHidden || isAttributeHidden;
};

isFocusable

Determines if an element is focusable based on standard criteria or custom element properties.

/**
 * Checks if an element is focusable.
 *
 * @param {Element} element - The DOM element to check for focusability.
 * @returns {boolean} True if the element is focusable, false otherwise.
 */
export const isFocusable = element => {
  if (!(element instanceof HTMLElement)) {
    return false;
  }

  const knownFocusableElements = `a[href],area[href],button:not([disabled]),details,iframe,object,input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[contentEditable="true"],[tabindex]:not([tabindex^="-"]),audio[controls],video[controls]`;

  if (element.matches(knownFocusableElements)) {
    return true;
  }

  const isDisabledCustomElement =
    element.localName.includes('-') && element.matches('[disabled], [aria-disabled="true"]');
  if (isDisabledCustomElement) {
    return false;
  }
  return /** @type {ShadowRoot | *} */ (element.shadowRoot)?.delegatesFocus ?? false;
};

getFirstAndLastFocusableChildren

Retrieves the first and last focusable children of a node using a TreeWalker.

/**
 * Retrieves the first and last focusable children of a node.
 *
 * @param {IterableIterator<HTMLElement>} walker - The TreeWalker object used to traverse the node's children.
 * @returns {[first: HTMLElement|null, last: HTMLElement|null]} An object containing the first and last focusable children.
 */
export const getFirstAndLastFocusableChildren = walker => {
  let firstFocusableChild = null;
  let lastFocusableChild = null;

  for (const currentNode of walker) {
    if (!firstFocusableChild) {
      firstFocusableChild = currentNode;
    }
    lastFocusableChild = currentNode;
  }

  return [firstFocusableChild, lastFocusableChild];
};

DOM Traversal Functions

walkComposedTree

Traverse the composed tree from the root, selecting elements that meet the provided filter criteria.

/**
 * Traverse the composed tree from the root, selecting elements that meet the provided filter criteria.
 *
 * @param {Node} node - The root node for traversal.
 * @param {number} [whatToShow=0] - NodeFilter code for node types to include.
 * @param {function} [filter=(n: Node) => true] - Filters nodes. Child nodes are considered even if parent does not satisfy the filter.
 * @param {function} [skipNode=(n: Node) => false] - Determines whether to skip a node and its children.
 * @returns {IterableIterator<Node>} An iterator yielding nodes meeting the filter criteria.
 */
export function* walkComposedTree(
  node,
  whatToShow = 0,
  filter = () => true,
  skipNode = () => false,
) {
  if ((whatToShow && node.nodeType !== whatToShow) || skipNode(node)) {
    return;
  }

  if (filter(node)) {
    yield node;
  }

  const children =
    node instanceof HTMLElement && node.shadowRoot
      ? node.shadowRoot.children
      : node instanceof HTMLSlotElement
        ? node.assignedNodes({ flatten: true })
        : node.childNodes;

  for (const child of children) {
    yield* walkComposedTree(child, whatToShow, filter, skipNode);
  }
}

getDeepActiveElement

Returns the deepest active element considering Shadow DOM subtrees.

/**
 * Returns the deepest active element, considering Shadow DOM subtrees.
 *
 * @param {Document | ShadowRoot} root - The root element to start the search from.
 * @returns {Element} The deepest active element or body element if no active element is found.
 */
export const getDeepActiveElement = (root = document) => {
  const activeEl = root?.activeElement;
  if (activeEl) {
    if (activeEl.shadowRoot) {
      return getDeepActiveElement(activeEl.shadowRoot) ?? activeEl;
    }
    return activeEl;
  }
  return document.body;
};

deepContains

Checks if a container node contains the target node, considering Shadow DOM boundaries.

/**
 * Returns true if the first node contains the second, even if the second node is in a shadow tree.
 *
 * @param {Node} container - The container to search within.
 * @param {Node} target - The node that may be inside the container.
 * @returns {boolean} - True if the container contains the target node.
 */
export const deepContains = (container, target) => {
  let current = target;
  while (current) {
    const parent = current.assignedSlot || current.parentNode || current.host;
    if (parent === container) {
      return true;
    }
    current = parent;
  }
  return false;
};

composedAncestors

Yields the ancestors of the given node in the composed tree, considering Shadow DOM

.

/**
 * Return the ancestors of the given node in the composed tree.
 *
 * @param {Node} node - The node to find ancestors for.
 * @returns {Iterable<Node>} - The ancestors in the composed tree.
 */
export function* composedAncestors(node) {
  for (let current = node; current; ) {
    const next =
      current instanceof HTMLElement && current.assignedSlot
        ? current.assignedSlot
        : current instanceof ShadowRoot
          ? current.host
          : current.parentNode;
    if (next) {
      yield next;
    }
    current = next;
  }
}

Event Handling Functions

isClickInsideRect

Checks if a click event occurred inside a given bounding rectangle.

/**
 * Checks if a click event occurred inside a given bounding rectangle.
 *
 * @param {DOMRect} rect - The bounding rectangle.
 * @param {PointerEvent} ev - The click event.
 * @returns {boolean} True if the click occurred inside the rectangle, false otherwise.
 */
export const isClickInsideRect = (rect, ev) => {
  const { top, left, height, width } = rect;
  const { clientX, clientY } = ev;
  return clientY >= top && clientY <= top + height && clientX >= left && clientX <= left + width;
};

Miscellaneous Functions

randomID

Generates a random alphanumeric string of a specified length.

/**
 * Generates a random alphanumeric string of a specified length.
 *
 * @param {number} [length=10] - The length of the random string to generate. Default is 10.
 * @returns {string} A random alphanumeric string of the specified length.
 */
export const randomID = (length = 10) => Math.random().toString(36).substring(2, length);

urlToPlainObject

Converts a URL object to a plain object with its properties as key-value pairs.

/**
 * Converts a URL object to a plain object.
 *
 * @param {URL|string} url - The URL object to parse.
 * @returns {Object} An object representing the parsed URL.
 */
export function urlToPlainObject(url) {
  const urlObject = typeof url === 'string' ? new URL(url) : url;
  const plainObject = {};

  // eslint-disable-next-line no-restricted-syntax
  for (const key in urlObject) {
    if (typeof urlObject[key] === 'string') {
      plainObject[key] = urlObject[key];
    }
  }
  return plainObject;
}

Author Information

  • Original Authors: @material/web, Jan Miksovsky, Cory LaViska