{"_id":"@a11ytools/focus-management","_rev":"3-68a2c66310cfbfe7c036848ead4e486e","name":"@a11ytools/focus-management","dist-tags":{"latest":"1.0.3"},"versions":{"1.0.0":{"name":"@a11ytools/focus-management","version":"1.0.0","keywords":["accessibility","a11y","focus","focus-trap","focus-management","keyboard","WCAG","WAI-ARIA","screen reader"],"author":{"name":"a11ytools Contributors"},"license":"MIT","_id":"@a11ytools/focus-management@1.0.0","maintainers":[{"name":"zenluv","email":"venkatajanapareddy9@gmail.com"}],"homepage":"https://github.com/a11ytools/focus-management#readme","bugs":{"url":"https://github.com/a11ytools/focus-management/issues"},"dist":{"shasum":"64f92e7a38112ec593172746a99407694b9b95dd","tarball":"https://registry.npmjs.org/@a11ytools/focus-management/-/focus-management-1.0.0.tgz","fileCount":9,"integrity":"sha512-9zPx6kgQjP3Uc1afEFwcaWwN+rHWTAmmNQFyfKvNBEL/Edjglk6U4FeTRD6DALaqggYtUUqNcN3SvSZ1N/8JRA==","signatures":[{"sig":"MEUCIQDq4Jb1fBcji78OabZL10YjYuwgFU6gdor+idoqyFd7mAIgJt2cW1BRNQpm7mjPST0pZx0dIntKLRysb+EenZBcwgM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":108936},"main":"dist/index.js","types":"dist/index.d.ts","module":"dist/index.mjs","engines":{"node":">=14"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"gitHead":"cf199b1437e2e57da6df0ef619817216ad32acf6","scripts":{"dev":"tsup --watch","lint":"eslint src --ext .ts,.tsx","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","test:coverage":"vitest run --coverage","prepublishOnly":"npm run build"},"_npmUser":{"name":"zenluv","email":"venkatajanapareddy9@gmail.com"},"deprecated":"This package has moved to @a11y-tools/focus-management. Please upgrade.","repository":{"url":"git+https://github.com/a11ytools/focus-management.git","type":"git"},"_npmVersion":"10.1.0","description":"A lightweight, accessibility-focused utility library for managing keyboard focus in web applications","directories":{},"sideEffects":false,"_nodeVersion":"20.9.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.4.0","vite":"^6.3.4","react":"^18.2.0","eslint":"^8.39.0","vitest":"^3.1.2","happy-dom":"^15.11.7","react-dom":"^18.2.0","typescript":"^5.0.4","@types/node":"^18.16.0","@types/react":"^18.2.0","@vitest/coverage-v8":"^3.1.2","eslint-plugin-react":"^7.37.5","@testing-library/react":"^14.0.0","@testing-library/jest-dom":"^6.6.3","@typescript-eslint/parser":"^5.59.1","eslint-plugin-react-hooks":"^5.2.0","eslint-plugin-vitest-globals":"^1.5.0","@typescript-eslint/eslint-plugin":"^5.59.1"},"peerDependencies":{"react":">=16.8.0"},"peerDependenciesMeta":{"react":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/focus-management_1.0.0_1746224775109_0.6688474652800798","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@a11ytools/focus-management","version":"1.0.3","description":"A lightweight, accessibility-focused utility library for managing keyboard focus in web applications","keywords":["accessibility","a11y","focus","focus-trap","focus-management","keyboard","WCAG","WAI-ARIA","screen reader"],"author":{"name":"a11ytools Contributors"},"license":"MIT","main":"dist/index.js","module":"dist/index.mjs","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"sideEffects":false,"engines":{"node":">=14"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","lint":"eslint src --ext .ts,.tsx","typecheck":"tsc --noEmit"},"peerDependencies":{"react":">=16.8.0"},"peerDependenciesMeta":{"react":{"optional":true}},"devDependencies":{"@testing-library/jest-dom":"^6.6.3","@testing-library/react":"^14.0.0","@types/node":"^18.16.0","@types/react":"^18.2.0","@typescript-eslint/eslint-plugin":"^5.59.1","@typescript-eslint/parser":"^5.59.1","@vitest/coverage-v8":"^3.1.2","eslint":"^8.39.0","eslint-plugin-react":"^7.37.5","eslint-plugin-react-hooks":"^5.2.0","eslint-plugin-vitest-globals":"^1.5.0","happy-dom":"^15.11.7","react":"^18.2.0","react-dom":"^18.2.0","tsup":"^8.4.0","typescript":"^5.0.4","vite":"^6.3.4","vitest":"^3.1.2"},"_id":"@a11ytools/focus-management@1.0.3","_nodeVersion":"23.9.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-XEYhWkxxxEoQ9AuQkMk+WrzntQljC/od4wYHBaizw6ovxveugolvHLQ/xh0iRDOWPOtoM4gLLg79mhiWaK53BQ==","shasum":"508f1070d559fff7e1c5b99364701b170dda4564","tarball":"https://registry.npmjs.org/@a11ytools/focus-management/-/focus-management-1.0.3.tgz","fileCount":9,"unpackedSize":108592,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCjCCa2fm0fL2m0YLKdDcB05fCuyA57/o0RGyEoT9rmKwIhAKiPEXaWJcldHdTz49lRt+jyx4Eiv0I59iZjMptucpNd"}]},"_npmUser":{"name":"zenluv","email":"venkatajanapareddy9@gmail.com"},"directories":{},"maintainers":[{"name":"zenluv","email":"venkatajanapareddy9@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/focus-management_1.0.3_1749919453946_0.8715941252999084"},"_hasShrinkwrap":false}},"time":{"created":"2025-05-02T22:26:15.012Z","modified":"2025-06-14T16:44:14.313Z","1.0.0":"2025-05-02T22:26:15.302Z","1.0.3":"2025-06-14T16:44:14.117Z"},"author":{"name":"a11ytools Contributors"},"license":"MIT","keywords":["accessibility","a11y","focus","focus-trap","focus-management","keyboard","WCAG","WAI-ARIA","screen reader"],"description":"A lightweight, accessibility-focused utility library for managing keyboard focus in web applications","maintainers":[{"name":"zenluv","email":"venkatajanapareddy9@gmail.com"}],"readme":"# @a11ytools/focus-management\n\n![npm](https://img.shields.io/npm/v/@a11ytools/focus-management)\n![License](https://img.shields.io/npm/l/@a11ytools/focus-management)\n![Bundle Size](https://img.shields.io/bundlephobia/minzip/@a11ytools/focus-management)\n\n> Part of the @a11ytools suite of open-source accessibility libraries\n\nA lightweight, accessibility-focused utility library for managing keyboard focus in web applications. This package helps developers build accessible interfaces by simplifying focus management in modals, dialogs, popovers, and other UI components.\n\n## Features\n\n- 🧠 **Accessibility-First**: Built to meet WCAG 2.2 standards and ARIA Authoring Practices\n- 🌐 **Framework-Agnostic**: Core utilities work in any JavaScript environment\n- ⚛️ **React Integration**: React-specific hooks available but not required\n- 📦 **Lightweight**: Minimal bundle size with zero dependencies\n- 🔍 **Edge Case Coverage**: Handles complex scenarios like nested focus traps and portals\n- 📱 **Modern Support**: Works in all modern browsers\n- 🔒 **TypeScript**: Fully typed for a great developer experience\n\n## Installation\n\n```bash\n# npm\nnpm install @a11ytools/focus-management\n\n# yarn\nyarn add @a11ytools/focus-management\n\n# pnpm\npnpm add @a11ytools/focus-management\n```\n\n**Important:** React is a peer dependency but is **optional** - the core utilities work without it in any JavaScript environment.\n\n## Quick Start\n\n### React Usage\n\n```tsx\nimport { useState } from 'react';\nimport { useFocusTrap } from '@a11ytools/focus-management';\n\nfunction Modal({ isOpen, onClose, children }) {\n  const modalRef = useFocusTrap({\n    active: isOpen,\n    onEscapeKey: onClose\n  });\n  \n  if (!isOpen) return null;\n  \n  return (\n    <div className=\"overlay\">\n      <div \n        ref={modalRef}\n        role=\"dialog\"\n        aria-modal=\"true\"\n        className=\"modal\"\n      >\n        <button onClick={onClose}>Close</button>\n        {children}\n      </div>\n    </div>\n  );\n}\n```\n\n### Vanilla JavaScript Usage\n\n```js\nimport { \n  getFocusableElements, \n  saveFocus, \n  returnFocus \n} from '@a11ytools/focus-management';\n\n// When opening a modal\nfunction openModal() {\n  const modal = document.getElementById('my-modal');\n  \n  // Save the current focus to restore later\n  saveFocus();\n  \n  // Show the modal\n  modal.style.display = 'block';\n  \n  // Focus the first focusable element\n  const elements = getFocusableElements(modal);\n  if (elements.length > 0) {\n    elements[0].focus();\n  }\n  \n  // Set up trap\n  document.addEventListener('keydown', handleTabKey);\n}\n\n// Handle Tab key to trap focus\nfunction handleTabKey(event) {\n  if (event.key !== 'Tab') return;\n  \n  const modal = document.getElementById('my-modal');\n  const focusableElements = getFocusableElements(modal, { onlyTabbable: true });\n  \n  if (focusableElements.length === 0) return;\n  \n  const firstElement = focusableElements[0];\n  const lastElement = focusableElements[focusableElements.length - 1];\n  \n  // If shift+tab on first element, move to last element\n  if (event.shiftKey && document.activeElement === firstElement) {\n    event.preventDefault();\n    lastElement.focus();\n  }\n  // If tab on last element, move to first element\n  else if (!event.shiftKey && document.activeElement === lastElement) {\n    event.preventDefault();\n    firstElement.focus();\n  }\n}\n\n// When closing a modal\nfunction closeModal() {\n  const modal = document.getElementById('my-modal');\n  \n  // Hide the modal\n  modal.style.display = 'none';\n  \n  // Remove event listener\n  document.removeEventListener('keydown', handleTabKey);\n  \n  // Return focus to the previously focused element\n  returnFocus();\n}\n```\n\n## API Reference\n\n### Core Utilities\n\n#### `isFocusable(element)`\n\nDetermines if an element is focusable.\n\n```ts\nimport { isFocusable } from '@a11ytools/focus-management';\n\nconst button = document.querySelector('button');\nif (isFocusable(button)) {\n  // Element can receive focus\n}\n```\n\n#### `isTabbable(element)`\n\nDetermines if an element is keyboard-tabbable (can be reached with Tab key).\n\n```ts\nimport { isTabbable } from '@a11ytools/focus-management';\n\nconst element = document.querySelector('.my-element');\nif (isTabbable(element)) {\n  // Element is part of the tab order\n}\n```\n\n#### `getFocusableElements(container, options?)`\n\nGets all focusable elements within a container.\n\n```ts\nimport { getFocusableElements } from '@a11ytools/focus-management';\n\n// Get all focusable elements\nconst elements = getFocusableElements(document.getElementById('modal'));\n\n// Get only keyboard-tabbable elements (excludes tabindex=\"-1\")\nconst tabbableElements = getFocusableElements(modal, { onlyTabbable: true });\n```\n\nOptions:\n- `onlyTabbable`: Boolean (default: `false`) - Only include elements reachable via keyboard Tab\n- `includeShadowDOM`: Boolean (default: `true`) - Include elements within shadow DOM\n\n#### `focusFirstElement(container, options?)`\n\nFocus the first focusable element within a container.\n\n```ts\nimport { focusFirstElement } from '@a11ytools/focus-management';\n\n// Focus the first element in a modal\nfocusFirstElement(document.getElementById('modal'));\n\n// Focus first element, including those with tabindex=\"-1\"\nfocusFirstElement(dialogElement, { onlyTabbable: false });\n```\n\nOptions:\n- `onlyTabbable`: Boolean (default: `true`) - Only focus elements reachable via keyboard Tab\n- `includeShadowDOM`: Boolean (default: `true`) - Include elements within shadow DOM\n- `preventScroll`: Boolean (default: `true`) - Prevent scrolling when focusing\n\n#### `focusFirstElementBySelector(container, selector, options?)`\n\nFocus the first element matching a selector within a container.\n\n```ts\nimport { focusFirstElementBySelector } from '@a11ytools/focus-management';\n\n// Focus the first button with 'submit' type\nfocusFirstElementBySelector(dialog, 'button[type=\"submit\"]');\n```\n\n#### `saveFocus()`\n\nSaves the currently focused element to return to later.\n\n```ts\nimport { saveFocus } from '@a11ytools/focus-management';\n\n// Save focus before opening a modal\nfunction openMyModal() {\n  saveFocus();\n  showMyModal();\n}\n```\n\n#### `returnFocus(options?)`\n\nReturns focus to the previously saved element.\n\n```ts\nimport { returnFocus } from '@a11ytools/focus-management';\n\n// Return focus when closing a modal\nfunction closeMyModal() {\n  hideMyModal();\n  returnFocus();\n}\n```\n\nOptions:\n- `preventScroll`: Boolean (default: `true`) - Prevent scrolling when returning focus\n- `fallbackElement`: HTMLElement (default: `document.body`) - Element to focus if original is gone\n\n#### `createFocusManager()`\n\nCreates a scoped focus manager for nested contexts, such as when you have multiple dialogs or modals that can be opened in sequence.\n\n```ts\nimport { createFocusManager } from '@a11ytools/focus-management';\n\n// Create separate focus managers for different UI components\nconst dialogFocusManager = createFocusManager();\nconst tooltipFocusManager = createFocusManager();\n\n// Example: Handling nested dialogs\nfunction openNestedDialog() {\n  // Save focus state from the parent dialog\n  dialogFocusManager.saveFocus();\n  \n  // Show the nested dialog\n  showNestedDialog();\n  \n  // Later, when closing nested dialog\n  closeNestedDialog();\n  \n  // Return focus to the element in the parent dialog\n  dialogFocusManager.returnFocus();\n}\n```\n\nThis approach allows you to manage focus independently in different components, supporting UI patterns like nested modals without focus conflicts.\n\n### React Hooks\n\n#### `useFocusTrap(options?)`\n\nReact hook that creates a focus trap within a container element. This hook implements WCAG 2.1.2 (No Keyboard Trap) by ensuring that keyboard focus can be moved to and from the component when appropriate, while still containing focus within the component when needed for modals, dialogs, and other overlay patterns.\n\n```tsx\nimport { useFocusTrap } from '@a11ytools/focus-management';\n\nfunction Modal({ isOpen, onClose }) {\n  const trapRef = useFocusTrap({\n    active: isOpen,       // Only trap focus when the modal is open\n    onEscapeKey: onClose  // Allow escape key to dismiss (WCAG 2.1.2 compliance)\n  });\n  \n  return (\n    <div ref={trapRef} role=\"dialog\" aria-modal=\"true\">\n      <button onClick={onClose}>Close</button>\n      <input />\n    </div>\n  );\n}\n```\n\nOptions:\n- `active`: Boolean (default: `true`) - Whether the focus trap is active\n- `autoFocus`: Boolean (default: `true`) - Auto-focus first element when activated\n- `restoreFocus`: Boolean (default: `true`) - Restore focus when deactivated\n- `lockFocus`: Boolean (default: `true`) - Lock focus even if DOM changes\n- `returnFocusOnDeactivate`: Boolean (default: `true`) - Return focus when trap is deactivated\n- `onEscapeKey`: Function - Callback when Escape key is pressed\n\n## WCAG Compliance\n\nThis library helps satisfy the following WCAG 2.2 success criteria:\n\n- **2.1.1 Keyboard** (Level A): All functionality is operable through a keyboard interface\n- **2.1.2 No Keyboard Trap** (Level A): Focus can be moved away from components using keyboard\n- **2.4.3 Focus Order** (Level A): Components receive focus in an order that preserves meaning\n- **2.4.7 Focus Visible** (Level AA): Keyboard focus indicator is visible (when used with proper CSS)\n- **3.2.1 On Focus** (Level A): Elements do not change context when receiving focus\n\n## Browser Support\n\n- Chrome 60+\n- Firefox 55+\n- Safari 10.1+\n- Edge 79+\n\n## Accessibility Testing\n\nWe recommend using this library in conjunction with testing tools:\n\n- [axe-core]([removed]) for automated testing\n- [Testing Library](https://testing-library.com/) for user-centric tests\n\n## License\n\nMIT © [a11ytools Contributors]([removed]) ","readmeFilename":"README.md"}