@react-hive/honey-utils
Version:
A lightweight TypeScript utility library providing a collection of helper functions for common programming tasks
344 lines (343 loc) • 13.7 kB
TypeScript
import type { Nullable } from './types';
export declare const FOCUSABLE_HTML_TAGS: string[];
interface HTMLElementTransformationValues {
translateX: number;
translateY: number;
scaleX: number;
scaleY: number;
skewX: number;
skewY: number;
}
/**
* Extracts transformation values (translate, scale, skew) from the 2D transformation matrix of a given HTML element.
*
* Only works with 2D transforms (i.e., `matrix(a, b, c, d, e, f)`).
*
* @param element - The element with a CSS transform applied.
* @returns An object with parsed transformation values.
*
* @example
* ```ts
* const values = parse2DMatrix(myElement);
* console.log(values.translateX);
* console.log(values.scaleX);
* ```
*/
export declare const parse2DMatrix: (element: HTMLElement) => HTMLElementTransformationValues;
/**
* Creates a clone of a Blob object.
*
* @param blob - The Blob object to clone.
*
* @returns A new Blob with the same content and type as the original.
*/
export declare const cloneBlob: (blob: Blob) => Blob;
/**
* Calculates the intersection ratio between two DOM rectangles.
*
* The ratio represents the proportion of the `targetRect` that is covered by `sourceRect`.
* A value of `1` means `sourceRect` completely covers `targetRect`, and `0` means no overlap.
*
* @param sourceRect - The rectangle used to measure overlap against the target.
* @param targetRect - The rectangle whose covered area is measured.
*
* @returns A number between `0` and `1` representing the intersection ratio.
*/
export declare const getDOMRectIntersectionRatio: (sourceRect: DOMRect, targetRect: DOMRect) => number;
/**
* Returns the bounding DOMRect of an element based on offset and client dimensions.
*
* This utility is useful when you need a stable, layout-based rect
* without triggering a reflow via `getBoundingClientRect()`.
*
* @param element - The target HTML element.
* @returns A `DOMRect` representing the element’s offset position and size.
*/
export declare const getElementOffsetRect: (element: HTMLElement) => DOMRect;
/**
* Determines whether the given HTMLElement is an HTMLAnchorElement.
*
* Acts as a type guard so that TypeScript narrows `element` to
* `HTMLAnchorElement` when the function returns `true`.
*
* An element qualifies as an anchor by having a tag name of `"A"`.
*
* @param element - The element to test.
*
* @returns Whether the element is an anchor element.
*/
export declare const isAnchorHtmlElement: (element: HTMLElement) => element is HTMLAnchorElement;
/**
* Checks whether an element is explicitly marked as contenteditable.
*
* Browsers treat elements with `contenteditable="true"` as focusable,
* even if they are not normally keyboard-focusable.
*
* @param element - The element to inspect.
*
* @returns True if `contenteditable="true"` is set.
*/
export declare const isContentEditableHtmlElement: (element: HTMLElement) => boolean;
/**
* Determines whether an HTMLElement is focusable under standard browser rules.
*
* The function checks a combination of factors:
* - The element must be rendered (not `display: none` or `visibility: hidden`).
* - Disabled form controls are never focusable.
* - Elements with `tabindex="-1"` are intentionally removed from the focus order.
* - Certain native HTML elements are inherently focusable (e.g. inputs, buttons, anchors with `href`).
* - Elements with `contenteditable="true"` are treated as focusable.
* - Any element with a valid `tabindex` (not null) is considered focusable.
*
* This logic approximates how browsers and the accessibility tree
* determine real-world focusability—not just tabindex presence.
*
* @param element - The element to test. `null` or `undefined` will return `false`.
*
* @returns Whether the element is focusable.
*/
export declare const isHtmlElementFocusable: (element: Nullable<HTMLElement>) => boolean;
/**
* Collects all focusable descendant elements within a container.
*
* The function queries *all* elements under the container and filters them
* using `isHtmlElementFocusable`, producing a reliable list of elements
* that can receive keyboard focus in real-world browser conditions.
*
* @param container - The root container whose focusable children will be found.
*
* @returns An array of focusable HTMLElements in DOM order.
*/
export declare const getFocusableHtmlElements: (container: HTMLElement) => HTMLElement[];
export type FocusMoveDirection = 'next' | 'previous';
export interface MoveFocusWithinContainerOptions {
/**
* Whether focus navigation should wrap around when reaching
* the beginning or end of the focusable elements list.
*
* When enabled, moving past the last element focuses the first,
* and moving before the first focuses the last.
*
* @default true
*/
wrap?: boolean;
/**
* Custom resolver for determining the next focus index.
*
* When provided, this function overrides the default navigation logic
* and receives full control over how the focus moves.
*
* @param currentIndex - Index of the currently focused element.
* @param direction - Direction in which focus is moving.
* @param elements - Ordered list of focusable elements within the container.
*
* @returns The index of the element to focus next, or `null` to prevent focus movement.
*/
getNextIndex?: (currentIndex: number, direction: FocusMoveDirection, elements: HTMLElement[]) => Nullable<number>;
}
/**
* Moves focus to the next or previous focusable element within a container.
*
* This utility is commonly used to implement accessible keyboard navigation patterns such as:
* - roving tabindex
* - custom dropdowns
* - tablists
* - menus
* - horizontal or vertical navigation groups
*
* Focus movement is scoped to a container and operates on the list of
* focusable descendants returned by `getFocusableHtmlElements`.
*
* @param direction - Direction in which focus should move (`'next'` or `'previous'`).
* @param container - Optional container that defines the focus scope.
* If omitted, the parent element of the currently focused element is used.
* @param options - Optional configuration controlling wrapping behavior and custom index resolution.
*
* @remarks
* - This function reads from and mutates the document's focus state.
* - If no active element exists, no container can be resolved,
* or the active element is not part of the focusable set, no action is taken.
* - When `getNextIndex` is provided, it fully overrides the default wrapping and directional logic.
*/
export declare const moveFocusWithinContainer: (direction: FocusMoveDirection, container?: Nullable<HTMLElement>, { wrap, getNextIndex }?: MoveFocusWithinContainerOptions) => void;
/**
* Checks whether an element has horizontal overflow.
*
* @param element - The element to check.
*
* @returns `true` if the content overflows horizontally.
*/
export declare const hasXOverflow: (element: HTMLElement) => boolean;
/**
* Calculates the horizontal overflow width of an element.
*
* The overflow width represents how much wider the content is compared
* to the visible container area.
*
* @param element - The scrollable container element.
*
* @returns The overflow width in pixels. Returns `0` when the content does not overflow horizontally.
*/
export declare const getXOverflowWidth: (element: HTMLElement) => number;
/**
* Checks whether an element has vertical overflow.
*
* @param element - The element to check.
*
* @returns `true` if the content overflows vertically.
*/
export declare const hasYOverflow: (element: HTMLElement) => boolean;
/**
* Calculates the vertical overflow height of an element.
*
* The overflow height represents how much taller the content is compared
* to the visible container area.
*
* @param element - The scrollable container element.
*
* @returns The overflow height in pixels. Returns `0` when the content does not overflow vertically.
*/
export declare const getYOverflowHeight: (element: HTMLElement) => number;
export interface CalculateCenterOffsetOptions {
/**
* Total overflow size for the axis.
*
* Represents how much larger the content is compared to the visible
* container size (e.g. scroll width minus client width).
*/
overflowSize: number;
/**
* Visible size of the container along the axis.
*
* Typically, `clientWidth` for the X axis or `clientHeight` for the Y axis.
*/
containerSize: number;
/**
* Offset of the target element from the start of the container along the axis.
*
* Typically, `offsetLeft` (X axis) or `offsetTop` (Y axis).
*/
elementOffset: number;
/**
* Size of the target element along the axis.
*
* Typically, `clientWidth` (X axis) or `clientHeight` (Y axis).
*/
elementSize: number;
}
/**
* Calculates the offset required to center an element within a container along a single axis.
*
* The returned value is clamped so that the resulting translation does not
* exceed the container's scrollable bounds.
*
* This function performs pure math only and does not access the DOM.
*
* @returns A negative offset value suitable for use in a CSS `translate`
* transform, or `0` when no overflow exists on the axis.
*/
export declare const calculateCenterOffset: ({ overflowSize, containerSize, elementOffset, elementSize, }: CalculateCenterOffsetOptions) => number;
type Axis = 'x' | 'y' | 'both';
export interface CenterElementInContainerOptions {
/**
* Axis (or axes) along which centering is applied.
*
* @default 'both'
*/
axis?: Axis;
}
/**
* Translates a container so that a target element is visually centered within its visible bounds.
*
* Centering is achieved by applying a CSS `transform: translate(...)` to the
* container element rather than using native scrolling.
*
* ### Behavior
* - Centering is calculated independently for each enabled axis.
* - Translation is applied only when the container content overflows on that axis.
* - When no overflow exists, the container remains untransformed for that axis.
*
* ### Notes
* - This function performs immediate DOM reads and writes.
* - The resulting transform is clamped to valid scrollable bounds.
*
* @param containerElement - The container whose content is translated.
* @param elementToCenter - The descendant element to align to the container’s center.
* @param options - Optional configuration controlling which axis or axes are centered.
*/
export declare const centerElementInContainer: (containerElement: HTMLElement, elementToCenter: HTMLElement, { axis }?: CenterElementInContainerOptions) => void;
/**
* Determines whether the browser environment allows safe read access to
* `localStorage`. Some platforms (e.g., Safari Private Mode, sandboxed iframes)
* expose `localStorage` but still throw when accessed.
*
* This function **only tests read access**, making it safe even when write
* operations would fail due to `QuotaExceededError` or storage restrictions.
*
* @returns `true` if `localStorage` exists and calling `getItem()` does not
* throw; otherwise `false`.
*/
export declare const isLocalStorageReadable: () => boolean;
interface LocalStorageCapabilities {
readable: boolean;
writable: boolean;
}
interface LocalStorageCapabilities {
readable: boolean;
writable: boolean;
}
/**
* Determines whether the browser's `localStorage` supports safe read and write operations.
* This function performs two independent checks:
*
* **1. Readability**
* - Verified by calling `localStorage.getItem()` inside a `try` block.
* - Fails in environments where storage access throws immediately (e.g., disabled storage,
* sandboxed iframes, strict privacy modes, SSR).
*
* **2. Writeability**
* - Verified by attempting to `setItem()` and then `removeItem()` using a temporary key.
* - Can fail due to:
* - `QuotaExceededError` when storage is full.
* - Disabled write access (e.g., Safari Private Mode).
* - Security-restricted contexts (third-party frames, hardened privacy settings)
*
* @returns An object describing the detected `localStorage` capabilities.
*/
export declare const getLocalStorageCapabilities: () => LocalStorageCapabilities;
export type Downloadable = Blob | MediaSource | string;
export interface DownloadFileOptions {
/**
* Suggested filename for the downloaded file.
*
* When provided, the browser will attempt to save the file using this name.
* If omitted and the source is a URL string, the browser may infer the name
* from the URL.
*/
fileName?: string;
/**
* Target browsing context for the download link.
*/
target?: '_self' | '_blank';
}
/**
* Initiates a file download in a browser environment.
*
* This utility supports downloading from:
* - a URL string
* - a `Blob`
* - a `MediaSource`
*
* For non-string inputs, an object URL is created temporarily and
* automatically revoked after the download is triggered.
*
* @remarks
* - This function performs direct DOM manipulation and must be executed in a browser environment.
* - In non-DOM contexts (e.g. SSR), the function exits without side effects.
* - Object URLs are revoked asynchronously to avoid Safari-related issues.
*
* @param file - The file source to download (URL string or binary object).
* @param options - Optional configuration controlling filename and link target.
*/
export declare const downloadFile: (file: Downloadable, { fileName, target }?: DownloadFileOptions) => void;
export {};