diff --git a/packages/core/makeup-active-descendant/dist/index.d.ts b/packages/core/makeup-active-descendant/dist/index.d.ts new file mode 100644 index 00000000..2445dff5 --- /dev/null +++ b/packages/core/makeup-active-descendant/dist/index.d.ts @@ -0,0 +1,149 @@ +/** + * Implements ARIA active descendant keyboard navigation. + * + * @example + * ```ts + * import * as ActiveDescendant from "makeup-active-descendant"; + * + * const widgetEl = document.querySelector(".widget"); + * const focusEl = widgetEl.querySelector("input"); + * const containerEl = widgetEl.querySelector("ul"); + * + * const activeDescendant = ActiveDescendant.createLinear(widgetEl, focusEl, containerEl, "li"); + * + * widgetEl.addEventListener("activeDescendantChange", function (e) { + * console.log(e.detail); + * }); + * ``` + */ + +import type { AutoInitOption, AutoResetOption, AxisOption } from "makeup-navigation-emitter"; + +/** + * Options for creating a linear active descendant + */ +export interface ActiveDescendantOptions { + /** + * The HTML class name that will be applied to the ARIA active descendant element (default: 'active-descendant') + */ + activeDescendantClassName?: string; + /** + * Declares the initial active descendant item (default: "none") + * - "none": no index position is set (useful in programmatic active-descendant) + * - "interactive": first non aria-disabled or hidden element + * - "ariaChecked": first element with aria-checked=true (useful in ARIA menu) + * - "ariaSelected": first element with aria-selected=true (useful in ARIA tabs) + * - "ariaSelectedOrInteractive": first element with aria-selected=true, falling back to "interactive" if not found (useful in ARIA listbox) + * - number: specific index position of items (throws error if non-interactive) + */ + autoInit?: AutoInitOption; + /** + * Declares the active descendant item after a reset and/or when keyboard focus exits the widget (default: "none") + * - "none": no index position is set (useful in programmatic active-descendant) + * - "current": index remains current (radio button like behaviour) + * - "interactive": index moves to first non aria-disabled or hidden element + * - "ariaChecked": index moves to first element with aria-checked=true + * - "ariaSelected": index moves to first element with aria-selected=true + * - number: specific index position of items (throws error if non-interactive) + */ + autoReset?: AutoResetOption; + /** + * Specify true to scroll the container as activeDescendant changes (default: false) + */ + autoScroll?: boolean; + /** + * Specify 'x' for left/right arrow keys, 'y' for up/down arrow keys, or 'both' (default: 'both') + */ + axis?: AxisOption; + /** + * CSS selector of descendant elements that will be ignored by the navigation emitters key event delegation + * (i.e. these elements will _not_ operate the active descendant) (default: null) + */ + ignoreByDelegateSelector?: string | null; + /** + * Specify whether arrow keys should wrap/loop (default: false) + */ + wrap?: boolean; +} + +/** + * Event detail for activeDescendantInit event + */ +export interface ActiveDescendantInitEventDetail { + /** All items that match item selector */ + items: HTMLElement[]; + /** Index position before initialization */ + fromIndex: number | null; + /** Index position after initialization */ + toIndex: number | null; +} + +/** + * Event detail for activeDescendantChange event + */ +export interface ActiveDescendantChangeEventDetail { + /** Index position before change */ + fromIndex: number | null; + /** Index position after change */ + toIndex: number | null; +} + +/** + * Event detail for activeDescendantReset event + */ +export interface ActiveDescendantResetEventDetail { + /** Index position before reset */ + fromIndex: number | null; + /** Index position after reset */ + toIndex: number | null; +} + +/** + * Event detail for activeDescendantMutation event + */ +export interface ActiveDescendantMutationEventDetail { + /** Index position before mutation */ + fromIndex: number | null; + /** Index position after mutation */ + toIndex: number | null; +} + +/** + * Active descendant instance + */ +export interface LinearActiveDescendant { + /** The index position of the current active descendant */ + index: number | null; + /** The current item at the index position */ + currentItem: HTMLElement | undefined; + /** All items that match item selector */ + items: HTMLElement[]; + /** Whether arrow keys should wrap/loop */ + wrap: boolean; + /** + * Will force a reset to the value specified by `autoReset` + */ + reset(): void; + /** + * Destroys all event listeners + */ + destroy(): void; +} + +/** + * Creates a linear active descendant instance + * @param el - Root widget element + * @param focusEl - The focusable element that will have aria-activedescendant set + * @param itemContainerEl - The element that contains the "descendant" items + * @param itemSelector - CSS selector for navigation items + * @param selectedOptions - Optional configuration + * @returns Active descendant instance + */ +export function createLinear( + el: HTMLElement, + focusEl: HTMLElement, + itemContainerEl: HTMLElement, + itemSelector: string, + selectedOptions?: ActiveDescendantOptions +): LinearActiveDescendant; + diff --git a/packages/core/makeup-active-descendant/package.json b/packages/core/makeup-active-descendant/package.json index e599020f..a364529d 100644 --- a/packages/core/makeup-active-descendant/package.json +++ b/packages/core/makeup-active-descendant/package.json @@ -4,8 +4,10 @@ "version": "0.7.10", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -30,6 +32,7 @@ "makeup-next-id": "^0.5.7" }, "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-exit-emitter/dist/index.d.ts b/packages/core/makeup-exit-emitter/dist/index.d.ts new file mode 100644 index 00000000..f28cb929 --- /dev/null +++ b/packages/core/makeup-exit-emitter/dist/index.d.ts @@ -0,0 +1,53 @@ +/** + * Emits custom 'focusExit' event when keyboard focus has exited an element and all of its descendants. + * + * @example + * ```ts + * import * as ExitEmitter from "makeup-exit-emitter"; + * + * const el = document.getElementById("widget1"); + * + * ExitEmitter.addFocusExit(el); + * + * el.addEventListener("focusExit", function (e) { + * console.log(this, e); + * }); + * ``` + */ + +/** + * Event detail for focusExit event + */ +export interface FocusExitEventDetail { + /** The element that previously had focus */ + fromElement: HTMLElement | null; + /** The element that now has focus (undefined if focus left the window) */ + toElement: HTMLElement | undefined; +} + +/** + * Adds focus exit tracking to an element + * @param el - Element to track focus exit for + * @returns The exit emitter instance (or null if already exists) + */ +export function addFocusExit(el: HTMLElement): FocusExitEmitter | null; + +/** + * Removes focus exit tracking from an element + * @param el - Element to remove focus exit tracking from + */ +export function removeFocusExit(el: HTMLElement): void; + +/** + * Focus exit emitter instance (internal, returned by addFocusExit) + * @internal + */ +export interface FocusExitEmitter { + /** The element being tracked */ + el: HTMLElement; + /** The currently focused element within the tracked element */ + currentFocusElement: HTMLElement | null; + /** Removes all event listeners */ + removeEventListeners(): void; +} + diff --git a/packages/core/makeup-exit-emitter/package.json b/packages/core/makeup-exit-emitter/package.json index 95d9b1cb..0177d6d1 100644 --- a/packages/core/makeup-exit-emitter/package.json +++ b/packages/core/makeup-exit-emitter/package.json @@ -4,8 +4,10 @@ "version": "0.5.7", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -29,6 +31,7 @@ "makeup-next-id": "^0.5.7" }, "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-expander/dist/index.d.ts b/packages/core/makeup-expander/dist/index.d.ts new file mode 100644 index 00000000..64b337f2 --- /dev/null +++ b/packages/core/makeup-expander/dist/index.d.ts @@ -0,0 +1,160 @@ +/** + * Creates the basic interactivity for an element that expands and collapses another element. + * + * @example + * ```ts + * import Expander from "makeup-expander"; + * + * const widgetEl = document.querySelector(".expander"); + * + * const options = { + * expandOnClick: true, + * }; + * + * const widget = new Expander(widgetEl, options); + * ``` + */ + +/** + * Options for creating an expander + */ +export interface ExpanderOptions { + /** + * Whether `focusManagement` option should apply for mouse click (default: false) + */ + alwaysDoFocusManagement?: boolean; + /** + * Specify whether `aria-controls` relationship should be created between host and overlay (default: true) + */ + ariaControls?: boolean; + /** + * Applies a collapse behavior (`collapseOnClick`, `collapseOnFocusOut`, `collapseOnMouseOut`) + * based on expand behaviour (default: false) + */ + autoCollapse?: boolean; + /** + * Whether the content should collapse when clicking outside of content (default: false) + */ + collapseOnClickOut?: boolean; + /** + * Whether the content should collapse when focus leaves the content (default: false) + */ + collapseOnFocusOut?: boolean; + /** + * Whether the content should collapse when mouse leaves the content (default: false) + */ + collapseOnMouseOut?: boolean; + /** + * Whether the content should collapse when focus moves back to the host from content (default: false). + * This cannot be set to true when expandOnFocus is true + */ + collapseOnHostReFocus?: boolean; + /** + * The query selector for the expandee element in relation to the widget (default: '.expander__content') + */ + contentSelector?: string; + /** + * The class which will be used on the root element to signify expanded state. + * This mirrors the `aria-expanded="true"` setting on the host element (default: null) + */ + expandedClass?: string | null; + /** + * Whether the host should be click activated (default: false) + */ + expandOnClick?: boolean; + /** + * Whether the host should be focus activated (default: false) + */ + expandOnFocus?: boolean; + /** + * Whether the host should be hover activated (default: false) + */ + expandOnHover?: boolean; + /** + * Where keyboard focus should go (null, 'content', 'focusable', 'interactive', or ID reference) + * when expanded via `ENTER` or `SPACEBAR` (default: null) + */ + focusManagement?: string | null; + /** + * The query selector for the host element in relation to the widget (default: '.expander__host') + */ + hostSelector?: string; + /** + * If host element does not naturally trigger a click event on spacebar, we can force one to trigger here. + * Careful! if host already triggers click events naturally, we end up with a "double-click". (default: false) + */ + simulateSpacebarClick?: boolean; +} + +/** + * Expander widget class + */ +export default class Expander { + /** + * Creates an expander instance + * @param el - The root widget element + * @param selectedOptions - Optional configuration + */ + constructor(el: HTMLElement, selectedOptions?: ExpanderOptions); + + /** The root widget element */ + el: HTMLElement; + /** The keyboard focusable host element */ + hostEl: HTMLElement; + /** The content element that expands/collapses */ + contentEl: HTMLElement; + /** The expander options */ + options: ExpanderOptions; + + /** + * Whether the content is currently expanded + */ + get expanded(): boolean; + set expanded(value: boolean); + + /** + * Whether expand on click is enabled + */ + set expandOnClick(value: boolean); + + /** + * Whether expand on focus is enabled + */ + set expandOnFocus(value: boolean); + + /** + * Whether expand on hover is enabled + */ + set expandOnHover(value: boolean); + + /** + * Whether collapse on click out is enabled + */ + set collapseOnClickOut(value: boolean); + + /** + * Whether collapse on focus out is enabled + */ + set collapseOnFocusOut(value: boolean); + + /** + * Whether collapse on mouse out is enabled + */ + set collapseOnMouseOut(value: boolean); + + /** + * Whether collapse on host refocus is enabled + */ + set collapseOnHostReFocus(value: boolean); + + /** + * Disables all expand/collapse behaviors without destroying the instance + */ + sleep(): void; + + /** + * Destroys all event listeners and cleans up + */ + destroy(): void; +} + diff --git a/packages/core/makeup-expander/package.json b/packages/core/makeup-expander/package.json index 5ad3c36a..68da9810 100644 --- a/packages/core/makeup-expander/package.json +++ b/packages/core/makeup-expander/package.json @@ -4,8 +4,10 @@ "version": "0.11.9", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -31,6 +33,7 @@ "makeup-next-id": "^0.5.7" }, "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-focusables/dist/index.d.ts b/packages/core/makeup-focusables/dist/index.d.ts new file mode 100644 index 00000000..d8ad13f5 --- /dev/null +++ b/packages/core/makeup-focusables/dist/index.d.ts @@ -0,0 +1,39 @@ +/** + * Returns an array of all focusable descendants of the given element, excluding elements that are hidden + * or children of hidden elements. + * + * @param el - The element to search for focusable descendants + * @param keyboardOnly - If true, return only elements focusable in sequential keyboard navigation (default: false) + * @param callback - Optional callback function. If provided, will call focusables after `requestAnimationFrame` + * and pass the list of focusables in the callback method. Returns a cleanup function. + * @returns Array of focusable HTMLElement instances, or cleanup function if callback is provided + * + * @example + * ```ts + * import focusables from "makeup-focusables"; + * + * const widgetEl = document.querySelector(".widget"); + * + * // Get all focusable elements (keyboard and programmatic) + * const allItems = focusables(widgetEl); + * + * // Get only keyboard focusable elements + * const keyboardItems = focusables(widgetEl, true); + * + * // With callback + * const cleanup = focusables(widgetEl, false, (items) => { + * console.log(items); + * }); + * cleanup(); // Cancel the requestAnimationFrame + * ``` + */ +declare function focusables(el: HTMLElement): HTMLElement[]; +declare function focusables(el: HTMLElement, keyboardOnly: boolean): HTMLElement[]; +declare function focusables( + el: HTMLElement, + keyboardOnly: boolean, + callback: (items: HTMLElement[]) => void +): () => void; + +export default focusables; + diff --git a/packages/core/makeup-focusables/package.json b/packages/core/makeup-focusables/package.json index 30d29a81..f89525f0 100644 --- a/packages/core/makeup-focusables/package.json +++ b/packages/core/makeup-focusables/package.json @@ -4,8 +4,10 @@ "version": "0.4.5", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -26,6 +28,7 @@ "a11y" ], "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-key-emitter/dist/index.d.ts b/packages/core/makeup-key-emitter/dist/index.d.ts new file mode 100644 index 00000000..2b59bde5 --- /dev/null +++ b/packages/core/makeup-key-emitter/dist/index.d.ts @@ -0,0 +1,55 @@ +/** + * Emits custom events for common accessibility keys (arrowRightKeyDown, escKeyDown, etc). + * + * Supported keys: Enter, Escape, PageUp, PageDown, End, Home, ArrowLeft, ArrowUp, ArrowRight, ArrowDown, Spacebar + * + * @example + * ```ts + * import * as KeyEmitter from "makeup-key-emitter"; + * + * const el = document.getElementById("widget1"); + * + * KeyEmitter.addKeyDown(el); + * + * el.addEventListener("arrowRightKeyDown", function (e) { + * console.log(this, e.type); + * }); + * ``` + */ + +/** + * Adds keydown event listener to emit custom key events + * @param el - Element to attach keydown listener to + */ +export function addKeyDown(el: HTMLElement): void; + +/** + * Adds keyup event listener to emit custom key events + * @param el - Element to attach keyup listener to + */ +export function addKeyUp(el: HTMLElement): void; + +/** + * Removes keydown event listener + * @param el - Element to remove keydown listener from + */ +export function removeKeyDown(el: HTMLElement): void; + +/** + * Removes keyup event listener + * @param el - Element to remove keyup listener from + */ +export function removeKeyUp(el: HTMLElement): void; + +/** + * Adds both keydown and keyup event listeners + * @param el - Element to attach listeners to + */ +export function add(el: HTMLElement): void; + +/** + * Removes both keydown and keyup event listeners + * @param el - Element to remove listeners from + */ +export function remove(el: HTMLElement): void; + diff --git a/packages/core/makeup-key-emitter/package.json b/packages/core/makeup-key-emitter/package.json index 11cf1da1..9aa50f11 100644 --- a/packages/core/makeup-key-emitter/package.json +++ b/packages/core/makeup-key-emitter/package.json @@ -4,8 +4,10 @@ "version": "0.4.7", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -26,6 +28,7 @@ "a11y" ], "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-keyboard-trap/dist/index.d.ts b/packages/core/makeup-keyboard-trap/dist/index.d.ts new file mode 100644 index 00000000..a0dadc5d --- /dev/null +++ b/packages/core/makeup-keyboard-trap/dist/index.d.ts @@ -0,0 +1,39 @@ +/** + * Restricts keyboard tabindex to a single subtree in the DOM. + * This behaviour is useful when implementing a modal interface (e.g. a modal dialog). + * + * It will ignore programmatically focusable items with a tabindex of `-1`. + * + * @example + * ```ts + * import * as keyboardTrap from "makeup-keyboard-trap"; + * + * // Trap an element + * keyboardTrap.trap(document.querySelector("el")); + * + * // Untrap the current trapped element + * keyboardTrap.untrap(); + * ``` + */ + +/** + * Traps keyboard focus within the specified element + * @param el - Element to trap keyboard focus within + * @returns The trapped element + */ +export function trap(el: HTMLElement): HTMLElement; + +/** + * Removes keyboard trap from the currently trapped element + * + * Note: the current implementation does not return the previously trapped element. + * It returns `null` (or `undefined` if called before any trap is set). + */ +export function untrap(): null | undefined; + +/** + * Refreshes the keyboard trap by recalculating focusable elements + * Useful when DOM changes occur within the trapped element + */ +export function refresh(): void; + diff --git a/packages/core/makeup-keyboard-trap/package.json b/packages/core/makeup-keyboard-trap/package.json index e74b4e1d..a7e0c17e 100644 --- a/packages/core/makeup-keyboard-trap/package.json +++ b/packages/core/makeup-keyboard-trap/package.json @@ -4,8 +4,10 @@ "version": "0.5.7", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -29,6 +31,7 @@ "makeup-focusables": "^0.4.5" }, "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-modal/dist/index.d.ts b/packages/core/makeup-modal/dist/index.d.ts new file mode 100644 index 00000000..a50b1c6e --- /dev/null +++ b/packages/core/makeup-modal/dist/index.d.ts @@ -0,0 +1,52 @@ +/** + * Sets an element to a modal state using keyboard-trap and screenreader-trap. + * All other elements become "inert". + * + * @example + * ```ts + * import * as modal from "makeup-modal"; + * + * // Set an element to modal + * modal.modal(document.querySelector("el")); + * + * // Reset the element to non-modal + * modal.unmodal(); + * ``` + */ + +/** + * Options for modal behavior + */ +export interface ModalOptions { + /** + * Use `hidden` property for inert content instead of `aria-hidden` + * Useful for fullscreen modals (default: false) + */ + useHiddenProperty?: boolean; + /** + * Moves the element to the document root (default: false) + */ + hoist?: boolean; + /** + * If element is at document root, wraps all "inert" sibling elements + * into a single container (default: false) + */ + wrap?: boolean; +} + +/** + * Sets an element to modal state + * @param el - Element to make modal + * @param options - Optional configuration + * @returns The modal element + */ +export function modal(el: HTMLElement, options?: ModalOptions): HTMLElement; + +/** + * Resets the element to non-modal state + * + * Note: the current implementation does not return the previously modal element. + * It returns `null` (or `undefined` if called before any modal is set). + */ +export function unmodal(): null | undefined; + diff --git a/packages/core/makeup-modal/package.json b/packages/core/makeup-modal/package.json index d0f5178e..4cb69529 100644 --- a/packages/core/makeup-modal/package.json +++ b/packages/core/makeup-modal/package.json @@ -4,8 +4,10 @@ "version": "0.5.7", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -30,6 +32,7 @@ "makeup-screenreader-trap": "^0.5.6" }, "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-navigation-emitter/dist/index.d.ts b/packages/core/makeup-navigation-emitter/dist/index.d.ts new file mode 100644 index 00000000..e599d93b --- /dev/null +++ b/packages/core/makeup-navigation-emitter/dist/index.d.ts @@ -0,0 +1,160 @@ +/** + * Emits custom `navigationModelChange` event when keyboard navigation keys (e.g ARROW-UP, ARROW-DOWN) + * occur on given array of elements, their container element or other associated owner. + * + * This module can be used as the underlying logic & state for both roving-tabindex and active-descendant + * (hierarchical & programmatic) behaviour. + * + * @example + * ```ts + * import * as NavigationEmitter from "makeup-navigation-emitter"; + * + * const widgetEl = document.querySelector(".widget"); + * + * const emitter = NavigationEmitter.createLinear(widgetEl, "li"); + * + * widgetEl.addEventListener("navigationModelChange", function (e) { + * console.log(e.detail.fromIndex, e.detail.toIndex); + * }); + * ``` + */ + +/** + * Auto init/reset option values + */ +export type AutoInitOption = "none" | "interactive" | "ariaChecked" | "ariaSelected" | "ariaSelectedOrInteractive" | number; +export type AutoResetOption = "none" | "current" | "interactive" | "ariaChecked" | "ariaSelected" | number; +export type AxisOption = "x" | "y" | "both"; + +/** + * Options for creating a linear navigation emitter + */ +export interface NavigationEmitterOptions { + /** + * Declares the initial item (default: "interactive") + * - "none": no index position is set (useful in programmatic active-descendant) + * - "interactive": first non aria-disabled or hidden element (default) + * - "ariaChecked": first element with aria-checked=true (useful in ARIA menu) + * - "ariaSelected": first element with aria-selected=true (useful in ARIA tabs) + * - "ariaSelectedOrInteractive": first element with aria-selected=true, falling back to "interactive" if not found (useful in ARIA listbox) + * - number: specific index position of items (throws error if non-interactive) + */ + autoInit?: AutoInitOption; + /** + * Declares the item after a reset and/or when keyboard focus exits the widget (default: "current") + * - "none": no index position is set (useful in programmatic active-descendant) + * - "current": index remains current (radio button like behaviour) + * - "interactive": index moves to first non aria-disabled or hidden element + * - "ariaChecked": index moves to first element with aria-checked=true + * - "ariaSelected": index moves to first element with aria-selected=true + * - number: specific index position of items (throws error if non-interactive) + */ + autoReset?: AutoResetOption; + /** + * Specify 'x' for left/right arrow keys, 'y' for up/down arrow keys, or 'both' (default: 'both') + */ + axis?: AxisOption; + /** + * CSS selector of descendant elements that will be ignored by the key event delegation + * (i.e. these elements will _not_ operate the navigation emitter) (default: null) + */ + ignoreByDelegateSelector?: string | null; + /** + * Specify whether arrow keys should wrap/loop (default: false) + */ + wrap?: boolean; +} + +/** + * Event detail for navigationModelInit event + */ +export interface NavigationModelInitEventDetail { + /** All items that match item selector */ + items: HTMLElement[]; + /** Index position before initialization */ + fromIndex: number | null; + /** Index position after initialization */ + toIndex: number | null; + /** First interactive index */ + firstInteractiveIndex: number; +} + +/** + * Event detail for navigationModelChange event + */ +export interface NavigationModelChangeEventDetail { + /** Index position before change */ + fromIndex: number | null; + /** Index position after change */ + toIndex: number | null; +} + +/** + * Event detail for navigationModelReset event + */ +export interface NavigationModelResetEventDetail { + /** Index position before reset */ + fromIndex: number | null; + /** Index position after reset */ + toIndex: number | null; +} + +/** + * Event detail for navigationModelMutation event + */ +export interface NavigationModelMutationEventDetail { + /** Index position before mutation */ + fromIndex: number | null; + /** Index position after mutation */ + toIndex: number | null; +} + +/** + * Navigation emitter instance + */ +export interface NavigationEmitter { + /** The navigation model */ + model: NavigationModel; + /** The element the emitter is attached to */ + el: HTMLElement; + /** + * Destroys all event listeners + */ + destroy(): void; +} + +/** + * Navigation model instance + */ +export interface NavigationModel { + /** Current index position */ + index: number | null; + /** Current item at index position */ + currentItem: HTMLElement | undefined; + /** All items that match item selector */ + items: HTMLElement[]; + /** Navigation options */ + options: NavigationEmitterOptions; + /** + * Will force a reset to the value specified by `autoReset` + */ + reset(): void; + /** + * Get index of an element in the items array + */ + indexOf(element: HTMLElement): number; +} + +/** + * Creates a linear navigation emitter + * @param el - Root element containing the navigation items + * @param itemSelector - CSS selector for navigation items + * @param selectedOptions - Optional configuration + * @returns Navigation emitter instance + */ +export function createLinear( + el: HTMLElement, + itemSelector: string, + selectedOptions?: NavigationEmitterOptions +): NavigationEmitter; + diff --git a/packages/core/makeup-navigation-emitter/package.json b/packages/core/makeup-navigation-emitter/package.json index ad9c626c..3cad7c19 100644 --- a/packages/core/makeup-navigation-emitter/package.json +++ b/packages/core/makeup-navigation-emitter/package.json @@ -4,8 +4,10 @@ "version": "0.7.7", "main": "./dist/cjs/index.js", "module": "dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -30,6 +32,7 @@ "makeup-key-emitter": "^0.4.7" }, "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-next-id/dist/index.d.ts b/packages/core/makeup-next-id/dist/index.d.ts new file mode 100644 index 00000000..9c8d110e --- /dev/null +++ b/packages/core/makeup-next-id/dist/index.d.ts @@ -0,0 +1,22 @@ +/** + * Assigns the next id in sequence to an element, if an id property does not already exist. + * + * The id will consist of a configurable prefix (default: 'nid'), followed by three randomly generated chars, + * then a number in sequence. For example: `nid-sdv-1`, `nid-sdv-2`, `nid-sdv-3`, etc. + * + * @param el - The element to assign an id to + * @param prefix - Optional prefix for the id (default: 'nid') + * @returns The assigned id (or existing id if element already had one) + * + * @example + * ```ts + * import nextId from "makeup-next-id"; + * + * const widgetEls = document.querySelectorAll(".widget"); + * widgetEls.forEach((el) => nextId(el)); + * ``` + */ +declare function nextId(el: HTMLElement, prefix?: string): string; + +export default nextId; + diff --git a/packages/core/makeup-next-id/package.json b/packages/core/makeup-next-id/package.json index a2ee52f0..efc9e5d5 100644 --- a/packages/core/makeup-next-id/package.json +++ b/packages/core/makeup-next-id/package.json @@ -4,8 +4,10 @@ "version": "0.5.7", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -26,6 +28,7 @@ "a11y" ], "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-prevent-scroll-keys/dist/index.d.ts b/packages/core/makeup-prevent-scroll-keys/dist/index.d.ts new file mode 100644 index 00000000..f8b91c3d --- /dev/null +++ b/packages/core/makeup-prevent-scroll-keys/dist/index.d.ts @@ -0,0 +1,29 @@ +/** + * Prevents the default scroll event when pressing down arrow, page down, spacebar, etc. + * This behaviour is required for ARIA widgets such as menu, tabs and comboboxes. + * + * @example + * ```ts + * import * as scrollKeyPreventer from "makeup-prevent-scroll-keys"; + * + * const widgetEl = document.querySelector(".widget"); + * + * scrollKeyPreventer.add(widgetEl); + * + * // To remove + * scrollKeyPreventer.remove(widgetEl); + * ``` + */ + +/** + * Adds keydown event listener to prevent default scroll behavior for arrow keys, page keys, and spacebar + * @param el - Element to attach prevent scroll behavior to + */ +export function add(el: HTMLElement): void; + +/** + * Removes keydown event listener that prevents default scroll behavior + * @param el - Element to remove prevent scroll behavior from + */ +export function remove(el: HTMLElement): void; + diff --git a/packages/core/makeup-prevent-scroll-keys/package.json b/packages/core/makeup-prevent-scroll-keys/package.json index 549e2fe0..7ecb2001 100644 --- a/packages/core/makeup-prevent-scroll-keys/package.json +++ b/packages/core/makeup-prevent-scroll-keys/package.json @@ -4,8 +4,10 @@ "version": "0.3.4", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -26,6 +28,7 @@ "a11y" ], "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-roving-tabindex/dist/index.d.ts b/packages/core/makeup-roving-tabindex/dist/index.d.ts new file mode 100644 index 00000000..5fdda781 --- /dev/null +++ b/packages/core/makeup-roving-tabindex/dist/index.d.ts @@ -0,0 +1,130 @@ +/** + * Implements a roving tab index on given collection of elements + * + * @example + * ```ts + * import * as RovingTabindex from "makeup-roving-tabindex"; + * + * const widgetEl = document.querySelector(".widget"); + * + * const rovingTabindex = RovingTabindex.createLinear(widgetEl, "li"); + * + * widgetEl.addEventListener("rovingTabindexChange", function (e) { + * console.log(e.detail); + * }); + * ``` + */ + +import type { AutoInitOption, AutoResetOption, AxisOption } from "makeup-navigation-emitter"; + +/** + * Options for creating a linear roving tabindex + */ +export interface RovingTabindexOptions { + /** + * Declares the initial roving tabindex item (default: "interactive") + * - "none": no index position is set (useful in programmatic active-descendant) + * - "interactive": first non aria-disabled or hidden element (default) + * - "ariaChecked": first element with aria-checked=true (useful in ARIA menu) + * - "ariaSelected": first element with aria-selected=true (useful in ARIA tabs) + * - "ariaSelectedOrInteractive": first element with aria-selected=true, falling back to "interactive" if not found (useful in ARIA listbox) + * - number: specific index position of items (throws error if non-interactive) + */ + autoInit?: AutoInitOption; + /** + * Declares the roving tabindex item after a reset and/or when keyboard focus exits the widget (default: "current") + * - "none": no index position is set (useful in programmatic active-descendant) + * - "current": index remains current (radio button like behaviour) + * - "interactive": index moves to first non aria-disabled or hidden element + * - "ariaChecked": index moves to first element with aria-checked=true + * - "ariaSelected": index moves to first element with aria-selected=true + * - number: specific index position of items (throws error if non-interactive) + */ + autoReset?: AutoResetOption; + /** + * Specify whether arrow keys should wrap/loop (default: false) + */ + wrap?: boolean; + /** + * Specify 'x' for left/right arrow keys, 'y' for up/down arrow keys, or 'both' (default: 'both') + */ + axis?: AxisOption; +} + +/** + * Event detail for rovingTabindexInit event + */ +export interface RovingTabindexInitEventDetail { + /** All items that match item selector */ + items: HTMLElement[]; + /** Index position before initialization */ + fromIndex: number | null; + /** Index position after initialization */ + toIndex: number | null; +} + +/** + * Event detail for rovingTabindexChange event + */ +export interface RovingTabindexChangeEventDetail { + /** Index position before change */ + fromIndex: number | null; + /** Index position after change */ + toIndex: number | null; +} + +/** + * Event detail for rovingTabindexReset event + */ +export interface RovingTabindexResetEventDetail { + /** Index position before reset */ + fromIndex: number | null; + /** Index position after reset */ + toIndex: number | null; +} + +/** + * Event detail for rovingTabindexMutation event + */ +export interface RovingTabindexMutationEventDetail { + /** Index position before mutation */ + fromIndex: number | null; + /** Index position after mutation */ + toIndex: number | null; +} + +/** + * Roving tabindex instance + */ +export interface LinearRovingTabindex { + /** The index position of the roving tabindex (i.e. the element with tabindex="0") */ + index: number | null; + /** The current item at the index position */ + currentItem: HTMLElement | undefined; + /** All items that match item selector */ + items: HTMLElement[]; + /** Whether arrow keys should wrap/loop */ + wrap: boolean; + /** + * Will force a reset to the value specified by `autoReset` + */ + reset(): void; + /** + * Destroys all event listeners + */ + destroy(): void; +} + +/** + * Creates a linear roving tabindex instance + * @param el - Root element containing the navigation items + * @param itemSelector - CSS selector for navigation items + * @param selectedOptions - Optional configuration + * @returns Roving tabindex instance + */ +export function createLinear( + el: HTMLElement, + itemSelector: string, + selectedOptions?: RovingTabindexOptions +): LinearRovingTabindex; + diff --git a/packages/core/makeup-roving-tabindex/package.json b/packages/core/makeup-roving-tabindex/package.json index 902ba9f8..9f412208 100644 --- a/packages/core/makeup-roving-tabindex/package.json +++ b/packages/core/makeup-roving-tabindex/package.json @@ -4,8 +4,10 @@ "version": "0.7.7", "main": "./dist/cjs/index.js", "module": "dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -29,6 +31,7 @@ "makeup-navigation-emitter": "^0.7.7" }, "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json", diff --git a/packages/core/makeup-screenreader-trap/dist/index.d.ts b/packages/core/makeup-screenreader-trap/dist/index.d.ts new file mode 100644 index 00000000..9b8a101e --- /dev/null +++ b/packages/core/makeup-screenreader-trap/dist/index.d.ts @@ -0,0 +1,39 @@ +/** + * Restricts screen reader virtual cursor to a single subtree in the DOM. + * This behaviour is useful when implementing a modal interface (e.g. a modal dialog). + * + * @example + * ```ts + * import * as screenreaderTrap from "makeup-screenreader-trap"; + * + * // Trap an element + * screenreaderTrap.trap(document.querySelector("el")); + * + * // Untrap the current trapped element + * screenreaderTrap.untrap(); + * ``` + */ + +/** + * Options for screenreader trap + */ +export interface ScreenreaderTrapOptions { + /** + * Use `hidden` property instead of `aria-hidden` (default: false) + * Useful for fullscreen modals + */ + useHiddenProperty?: boolean; +} + +/** + * Traps screen reader virtual cursor within the specified element + * @param el - Element to trap screen reader within + * @param options - Optional configuration + */ +export function trap(el: HTMLElement, options?: ScreenreaderTrapOptions): void; + +/** + * Removes screen reader trap from the currently trapped element + */ +export function untrap(): void; + diff --git a/packages/core/makeup-screenreader-trap/package.json b/packages/core/makeup-screenreader-trap/package.json index 8aef6736..8349fd7c 100644 --- a/packages/core/makeup-screenreader-trap/package.json +++ b/packages/core/makeup-screenreader-trap/package.json @@ -4,8 +4,10 @@ "version": "0.5.6", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -26,6 +28,7 @@ "a11y" ], "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/cjs/util.js", "dist/mjs/index.js", diff --git a/packages/core/makeup-typeahead/dist/index.d.ts b/packages/core/makeup-typeahead/dist/index.d.ts new file mode 100644 index 00000000..233e4cde --- /dev/null +++ b/packages/core/makeup-typeahead/dist/index.d.ts @@ -0,0 +1,56 @@ +/** + * Produces a function generator. The generated function produces the index of the suggested menu item + * to highlight / focus. It keeps track of the characters entered, adding them onto a string. + * + * Its parameters are a list of DOM nodes, a char, and the length of a timeout. The timeout is restarted + * when a new char is given the function. + * + * When the timeout executes the callback, it will re-start the suggestions with an empty string. + * + * @example + * ```ts + * import typeahead from "makeup-typeahead"; + * + * const list = document.querySelector("ul"); + * const selected = document.querySelector(".selected"); + * const TIMEOUT_LENGTH = 2000; + * + * const getIndex = typeahead(); + * + * function handleKeyDown(e) { + * if (e.key.length === 1) { + * const listIndex = getIndex(list.children, e.key, TIMEOUT_LENGTH); + * if (listIndex !== -1) { + * selected.innerHTML = list.children[listIndex].innerHTML; + * } + * } + * } + * ``` + */ + +/** + * Typeahead instance returned by the factory function + */ +export interface TypeaheadInstance { + /** + * Gets the index of the suggested menu item based on typed characters + * @param nodeList - List of DOM nodes to search through + * @param char - Character that was typed + * @param timeoutLength - Length of timeout in milliseconds before resetting the typed string + * @returns Index of matching item, or -1 if no match found + */ + getIndex(nodeList: NodeListOf | HTMLCollection | Element[], char: string, timeoutLength: number): number; + /** + * Destroys the typeahead instance and clears any pending timeout + */ + destroy(): void; +} + +/** + * Creates a typeahead instance + * @returns Typeahead instance with getIndex and destroy methods + */ +declare function typeahead(): TypeaheadInstance; + +export default typeahead; + diff --git a/packages/core/makeup-typeahead/package.json b/packages/core/makeup-typeahead/package.json index af5b4fbc..9ade55fd 100644 --- a/packages/core/makeup-typeahead/package.json +++ b/packages/core/makeup-typeahead/package.json @@ -4,8 +4,10 @@ "version": "0.3.4", "main": "./dist/cjs/index.js", "module": "./dist/mjs/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { + "types": "./dist/index.d.ts", "import": "./dist/mjs/index.js", "require": "./dist/cjs/index.js" } @@ -25,6 +27,7 @@ "a11y" ], "files": [ + "dist/index.d.ts", "dist/cjs/index.js", "dist/mjs/index.js", "dist/mjs/package.json",