Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/oscarmarina/dev-utilities
Developer utilities for handling DOM, events, and other web development tasks
https://github.com/oscarmarina/dev-utilities
Last synced: 12 days ago
JSON representation
Developer utilities for handling DOM, events, and other web development tasks
- Host: GitHub
- URL: https://github.com/oscarmarina/dev-utilities
- Owner: oscarmarina
- License: mit
- Created: 2024-06-07T20:28:36.000Z (5 months ago)
- Default Branch: main
- Last Pushed: 2024-10-19T22:29:22.000Z (20 days ago)
- Last Synced: 2024-10-20T07:50:53.293Z (19 days ago)
- Language: JavaScript
- Size: 91.8 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
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](#re-dispatch-event-functions)
- [redispatchEventFromEvent](#redispatcheventfromevent)
- [redispatchEvent](#redispatchevent)
- [Element Visibility and Focus Functions](#element-visibility-and-focus-functions)
- [isElementInvisible](#iselementinvisible)
- [isFocusable](#isfocusable)
- [getFirstAndLastFocusableChildren](#getfirstandlastfocusablechildren)
- [DOM Traversal Functions](#dom-traversal-functions)
- [walkComposedTree](#walkcomposedtree)
- [getDeepActiveElement](#getdeepactiveelement)
- [deepContains](#deepcontains)
- [composedAncestors](#composedancestors)
- [Event Handling Functions](#event-handling-functions)
- [isClickInsideRect](#isclickinsiderect)
- [Miscellaneous Functions](#miscellaneous-functions)
- [randomID](#randomid)
- [urlToPlainObject](#urltoplainobject)## 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.
```javascript
/**
* 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.
```javascript
/**
* 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.
```javascript
/**
* 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.
```javascript
/**
* 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.
```javascript
/**
* Retrieves the first and last focusable children of a node.
*
* @param {IterableIterator} 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.
```javascript
/**
* 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} 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.
```javascript
/**
* 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.
```javascript
/**
* 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
.
```javascript
/**
* Return the ancestors of the given node in the composed tree.
*
* @param {Node} node - The node to find ancestors for.
* @returns {Iterable} - 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.
```javascript
/**
* 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.
```javascript
/**
* 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.
```javascript
/**
* 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