{"_id":"@a11yfred/rogers","_rev":"2-cdc506670809eeab739a8d463723a54e","name":"@a11yfred/rogers","dist-tags":{"latest":"0.2.0"},"versions":{"0.1.0":{"name":"@a11yfred/rogers","version":"0.1.0","keywords":["accessibility","a11y","debug","devtools","react","vue","angular","remix","vanilla","a11yfred"],"license":"MIT","_id":"@a11yfred/rogers@0.1.0","maintainers":[{"name":"mikeyil","email":"ilagan@gmail.com"}],"homepage":"https://github.com/a11yfred/rogers#readme","bugs":{"url":"https://github.com/a11yfred/rogers/issues"},"dist":{"shasum":"4397c1b5b66c8c745cb7f1f357ccd2881020683e","tarball":"https://registry.npmjs.org/@a11yfred/rogers/-/rogers-0.1.0.tgz","fileCount":22,"integrity":"sha512-Aeom/OJamZY8UDULohvNENnFOkJn1PYi8vifHxhTsXkyqwzZbQLlhvEDNGas+fwm5DvvfBb53G3YGBJYNaBFVw==","signatures":[{"sig":"MEQCIEQ0dvMD9X1d+CkbVvL2ycVc3OqETe5TB3+qEMuqERmkAiAPgBKgbPzU5vEVCQBVOPdwysmbTHxPHwHM8+Vee9s0Ag==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":80061},"type":"module","exports":{".":"./index.js","./vue":"./vue.js","./react":"./react.js","./remix3":"./remix3.js","./angular":"./angular.js"},"gitHead":"d65f001ea7c3dfa208165fa936f35251438a35b7","_npmUser":{"name":"mikeyil","email":"ilagan@gmail.com"},"repository":{"url":"git+https://github.com/a11yfred/rogers.git","type":"git"},"_npmVersion":"11.9.0","description":"Vanilla-first accessibility debug panel. Always look for the helpers.","directories":{},"_nodeVersion":"24.14.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/rogers_0.1.0_1778597477647_0.04420479976252789","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@a11yfred/rogers","version":"0.2.0","description":"Vanilla-first accessibility debug panel. Always look for the helpers.","type":"module","license":"MIT","repository":{"type":"git","url":"git+https://github.com/a11yfred/rogers.git"},"homepage":"https://github.com/a11yfred/rogers#readme","bugs":{"url":"https://github.com/a11yfred/rogers/issues"},"keywords":["accessibility","a11y","debug","devtools","react","vue","angular","remix","vanilla","a11yfred"],"scripts":{"demo":"npx serve ."},"exports":{".":"./index.js","./react":"./react.js","./vue":"./vue.js","./angular":"./angular.js","./remix3":"./remix3.js"},"gitHead":"967a5922deb9125d85c8bd0c19234ab5718a1df2","_id":"@a11yfred/rogers@0.2.0","_nodeVersion":"24.14.0","_npmVersion":"11.9.0","dist":{"integrity":"sha512-tf5kPApEGrW0w4Htl1Ye3E6SPHg3sERcilDyRtXW6k0U4ERdmZXW/NRqoAIVdNwVlw++eT9nIUMdHe0ifk4s7A==","shasum":"8111934389b9490e2d373f854886640dbda48088","tarball":"https://registry.npmjs.org/@a11yfred/rogers/-/rogers-0.2.0.tgz","fileCount":23,"unpackedSize":81593,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIEqyUhcyRCGoi4fl60jzSuujUdpi8PUHrgr3xWa6uWVZAiBZyfAr9dQBfkWex6YL0bzVsEdg1E+5qRmP96o6D5I5cw=="}]},"_npmUser":{"name":"mikeyil","email":"ilagan@gmail.com"},"directories":{},"maintainers":[{"name":"mikeyil","email":"ilagan@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/rogers_0.2.0_1778684418560_0.7275662158517222"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-12T14:51:17.513Z","modified":"2026-05-13T15:00:18.813Z","0.1.0":"2026-05-12T14:51:17.797Z","0.2.0":"2026-05-13T15:00:18.709Z"},"bugs":{"url":"https://github.com/a11yfred/rogers/issues"},"license":"MIT","homepage":"https://github.com/a11yfred/rogers#readme","keywords":["accessibility","a11y","debug","devtools","react","vue","angular","remix","vanilla","a11yfred"],"repository":{"type":"git","url":"git+https://github.com/a11yfred/rogers.git"},"description":"Vanilla-first accessibility debug panel. Always look for the helpers.","maintainers":[{"name":"mikeyil","email":"ilagan@gmail.com"}],"readme":"# @a11yfred/rogers\n\nRogers is an accessibility debug tool for testing and development. Drop it into any project to see keyboard focus, accessible names, heading structure, and tab order in real time. No framework required, no dependencies.\n\n## Install\n\n```bash\nnpm install --save-dev @a11yfred/rogers\n```\n\n## How it works\n\nRogers has two layers:\n\n- **Core** — plain JavaScript functions that watch the DOM. No framework needed.\n- **Adapters** — thin wrappers for React, Vue, Angular, and Remix that wire the core into your framework's lifecycle.\n\nEach adapter takes your framework's own hooks as parameters. Rogers never imports a framework itself, so it adds nothing to your bundle.\n\n## Enabling rogers\n\nRogers checks `globalThis.ROGERS_DEV` at startup. If it is not `true`, all functions return immediately and do nothing.\n\nSet it using a condition your build tool evaluates at build time — not a plain `true`. This ensures the flag is always `false` in a production bundle, even if someone forgets to remove the import.\n\n```js\n// Vite\nif (import.meta.env.DEV) globalThis.ROGERS_DEV = true\n\n// webpack / CRA\nif (process.env.NODE_ENV !== 'production') globalThis.ROGERS_DEV = true\n\n// Remix / Next.js\nif (process.env.NODE_ENV === 'development') globalThis.ROGERS_DEV = true\n```\n\nDo this once in your app entry point, before rogers is imported. Never write `globalThis.ROGERS_DEV = true` as a bare statement — a build tool cannot tree-shake a hardcoded `true`.\n\nFor a plain HTML demo or local prototype with no build step, a bare `true` is fine since there is no production build:\n\n```html\n<script>globalThis.ROGERS_DEV = true</script>\n<script type=\"module\" src=\"./your-app.js\"></script>\n```\n\n## File structure\n\n```text\n@a11yfred/rogers\n├── core/\n│   ├── focus.js      — focus tracking\n│   ├── names.js      — accessible name lookup\n│   ├── headings.js   — heading collection\n│   └── tabstops.js   — tab order\n├── overlay/          — DOM overlay renderers (no framework)\n├── index.js          — vanilla exports\n├── react.js          — React adapter\n├── vue.js            — Vue adapter\n├── angular.js        — Angular adapter\n├── remix3.js         — Remix 3 adapter\n└── debug.css         — styles for all overlays\n```\n\n## Demo\n\nRun the demo locally with no install required:\n\n```bash\nnpm run demo\n```\n\nThen open `http://localhost:3000`. The demo shows all four debug tools running on a page with intentional accessibility issues to inspect.\n\n## Framework integration\n\nImport `debug.css` once in your app entry point.\n\n```js\nimport '@a11yfred/rogers/debug.css'\n```\n\n### Vanilla JS\n\n```js\nimport {\n  mountFocusDebugger, mountNamesDebugger,\n  mountHeadingMapDebugger, mountTabStopsDebugger,\n  mountDebugLauncher,\n} from '@a11yfred/rogers'\nimport '@a11yfred/rogers/debug.css'\n\nconst state = { focus: null, names: null, headings: null, tabstops: null }\n\nconst launcher = mountDebugLauncher({\n  onToggle(key, on) {\n    if (on && !state[key])  state[key] = mount(key)\n    if (!on && state[key]) { state[key].destroy(); state[key] = null }\n  },\n})\n\nfunction mount(key) {\n  if (key === 'focus')    return mountFocusDebugger()\n  if (key === 'names')    return mountNamesDebugger()\n  if (key === 'headings') return mountHeadingMapDebugger()\n  if (key === 'tabstops') return mountTabStopsDebugger()\n}\n```\n\n### React\n\n```jsx\nimport { useState, useEffect, useRef } from 'react'\nimport { createComponents } from '@a11yfred/rogers/react'\nimport '@a11yfred/rogers/debug.css'\n\nconst {\n  FocusDebugger, NamesDebugger, HeadingMapDebugger,\n  TabStopsDebugger, DebugLauncher,\n} = createComponents({ useEffect, useRef })\n\nexport default function Root() {\n  const [active, setActive] = useState({\n    focus: false, names: false, headings: false, tabstops: false,\n  })\n\n  function handleToggle(key, on) {\n    setActive(prev => ({ ...prev, [key]: on }))\n  }\n\n  return (\n    <>\n      <Outlet />\n      <FocusDebugger    enabled={active.focus} />\n      <NamesDebugger    enabled={active.names} />\n      <HeadingMapDebugger enabled={active.headings} />\n      <TabStopsDebugger enabled={active.tabstops} />\n      <DebugLauncher    enabled onToggle={handleToggle} />\n    </>\n  )\n}\n```\n\n### Vue\n\n```js\nimport { onMounted, onUnmounted, watch, ref } from 'vue'\nimport { createComposables } from '@a11yfred/rogers/vue'\nimport '@a11yfred/rogers/debug.css'\n\nconst { useFocusDebugger, useNamesDebugger, useDebugLauncher } =\n  createComposables({ onMounted, onUnmounted, watch, ref })\n```\n\nIn a component:\n\n```vue\n<script setup>\nimport { ref } from 'vue'\n\nconst active = ref({ focus: false, names: false, headings: false, tabstops: false })\n\nuseFocusDebugger(computed(() => active.value.focus))\nuseNamesDebugger(computed(() => active.value.names))\nuseDebugLauncher({\n  enabled: ref(true),\n  onToggle(key, on) { active.value = { ...active.value, [key]: on } },\n})\n</script>\n```\n\n### Angular\n\n```ts\nimport { inject, DestroyRef } from '@angular/core'\nimport { createServices } from '@a11yfred/rogers/angular'\n\nconst { FocusDebuggerService, NamesDebuggerService, DebugLauncherService } =\n  createServices({ DestroyRef, inject })\n```\n\nIn a root component:\n\n```ts\n@Component({\n  selector: 'app-root',\n  providers: [FocusDebuggerService, NamesDebuggerService, DebugLauncherService],\n})\nexport class AppComponent implements OnInit {\n  private focus    = inject(FocusDebuggerService)\n  private names    = inject(NamesDebuggerService)\n  private launcher = inject(DebugLauncherService)\n\n  ngOnInit() {\n    this.launcher.enable({\n      onToggle: (key, on) => {\n        if (key === 'focus') on ? this.focus.enable() : this.focus.disable()\n        if (key === 'names') on ? this.names.enable() : this.names.disable()\n      },\n    })\n  }\n}\n```\n\n### Remix 3\n\n```js\n// app/entry.client.js\nimport { rogers } from '@a11yfred/rogers/remix3'\nimport '@a11yfred/rogers/debug.css'\n\nconst debug = rogers({\n  onToggle(key, on) {\n    // handle tool state via your own state management\n  },\n})\n\n// HMR cleanup\nif (import.meta.hot) {\n  import.meta.hot.dispose(() => debug.destroy())\n}\n```\n\n## Vanilla API\n\n### Core\n\n| Export | Description |\n| ------ | ----------- |\n| `createFocusWatcher(onToast)` | Watches focus events. Calls `onToast({ label, hasFocusOutline, isFocusVisible })` |\n| `createNamesWatcher(onTooltip, onClear)` | Watches mouse hover. Calls `onTooltip({ name, source, x, y })` |\n| `createHeadingWatcher(onHeadings)` | Watches scroll and resize. Calls `onHeadings(headings[])` |\n| `createTabStopWatcher(onStop, onClear)` | Watches tab keypresses. Calls `onStop({ seq, cx, cy, label })` |\n| `formatTarget(el)` | Returns a `<tag.class>` string for an element |\n| `getOutlineInfo(el)` | Returns `{ hasFocusOutline, isFocusVisible }` |\n| `flashElement(el)` | Briefly highlights the element |\n| `isControl(el)` | Returns true if the element is interactive |\n| `getAccessibleName(el)` | Returns `{ name, source }` |\n| `collectHeadings()` | Returns all headings on the page with metadata |\n| `isTabbable(el)` | Returns true if the element is in the tab order |\n| `getTabOrder()` | Returns all tabbable elements in order |\n\n### Overlays\n\n| Export | Description |\n| ------ | ----------- |\n| `mountFocusDebugger()` | Toast + element flash on keyboard focus |\n| `mountNamesDebugger()` | Tooltip showing accessible name on hover |\n| `mountHeadingMapDebugger()` | Overlay + panel showing heading structure |\n| `mountTabStopsDebugger()` | Numbered overlay showing tab order |\n| `mountDebugLauncher(options)` | Floating button with toggle menu |\n| `mountDebugHelp(options)` | Full command reference panel |\n| `mountDeployBanner(target)` | Fixed banner showing deployment target |\n\n### mountDebugLauncher options\n\n| Option | Type | Description |\n| ------ | ---- | ----------- |\n| `position` | `string` | FAB position. One of `bottom-right`, `bottom-left`, `bottom-center`, `top-right`, `top-left`, `top-center`, `middle-right`, `middle-left`. Default: `bottom-right` |\n| `onToggle` | `(key, on) => void` | Called when a tool is toggled. `key` is one of `focus`, `names`, `headings`, `tabstops` |\n| `customTools` | `Array` | Additional tools to show in the menu. Each item: `{ key, label, desc }` |\n| `initialState` | `Record<string, boolean>` | Initial active state per tool key |\n\nReturns `{ setActive(key, on), destroy() }`.\n\n## CSS\n\nImport `debug.css` once. It covers all overlays and is self-contained.\n\n## License\n\nMIT\n\n---\n\n*Built with help from Claude.*\n","readmeFilename":"README.md"}