Full Code of Q42/floating-focus-a11y for AI

develop 07961f2bbc36 cached
25 files
37.6 KB
10.2k tokens
21 symbols
1 requests
Download .txt
Repository: Q42/floating-focus-a11y
Branch: develop
Commit: 07961f2bbc36
Files: 25
Total size: 37.6 KB

Directory structure:
gitextract_sek1uzv1/

├── .browserslistrc
├── .editorconfig
├── .eslintrc
├── .github/
│   └── workflows/
│       └── node.js.yml
├── .gitignore
├── .nvmrc
├── .prettierrc
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── __mocks__/
│   └── generalMocks.js
├── babel.config.js
├── example/
│   └── index.html
├── index.d.ts
├── jest.config.js
├── package.json
├── postcss.config.js
├── src/
│   ├── floating-focus.js
│   ├── floating-focus.scss
│   └── floating-focus.spec.js
├── webpack.common.js
├── webpack.dev.js
├── webpack.prod.js
├── webpack.styled.js
└── webpack.unstyled.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .browserslistrc
================================================
Chrome >= 49
Safari >= 8
ios_saf >= 8
ie >= 11
Edge >= 12
Firefox >= 45
Samsung >= 2


================================================
FILE: .editorconfig
================================================
# This file is for unifying the coding style for different editors and IDEs
# editorconfig.org
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true


================================================
FILE: .eslintrc
================================================
{
  "parser": "@babel/eslint-parser",
  "extends": [
    "eslint:recommended",
    "plugin:prettier/recommended"
  ],
  "env": {
    "browser": true,
    "es6": true,
    "jest": true
  },
  "globals": {
    "Promise": true,
    "require": true,
    "module": true
  },
  "rules": {
    "no-console": 0,
    "indent": "off"
  }
}


================================================
FILE: .github/workflows/node.js.yml
================================================
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions

name: Node.js CI

on:
  push:
    branches: [ develop ]
  pull_request:
    branches: [ develop ]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v3
    - uses: actions/setup-node@v3
      with:
          node-version-file: package.json
    - run: npm run ci
    - run: npm run test
    - run: npm run build


================================================
FILE: .gitignore
================================================
# IntelliJ folders
.idea_modules/
.idea/

# Node modules
node_modules/

# Jest code coverage
coverage/

# Built code
dist/

# macOS
.DS_Store


================================================
FILE: .nvmrc
================================================
20.0.0


================================================
FILE: .prettierrc
================================================
{
  "trailingComma": "es5",
  "useTabs": false,
  "tabWidth": 2,
  "semi": false,
  "singleQuote": true,
  "printWidth": 120
}


================================================
FILE: .vscode/settings.json
================================================
{
	"editor.formatOnSave": true,
	"editor.codeActionsOnSave": {
		"source.organizeImports": true
	},
	"[typescript]": {
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	},
	"[javascript]": {
		"editor.defaultFormatter": "esbenp.prettier-vscode"
	}
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2019 Q42

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
<h1 align="center">Floating Focus</h1>

<p align="center">
	<a href="https://github.com/Q42/floating-focus-a11y"><img src="https://github.com/q42/floating-focus-a11y/actions/workflows/node.js.yml/badge.svg" alt="Build status"></a>
	<a href="https://www.npmjs.com/package/@q42/floating-focus-a11y"><img src="https://img.shields.io/npm/v/@q42/floating-focus-a11y.svg?sanitize=true" alt="Package version"></a>
	<a href="https://www.npmjs.com/package/@q42/floating-focus-a11y"><img src="https://img.shields.io/npm/l/@q42/floating-focus-a11y.svg?sanitize=true" alt="Package license"></a>
</p>

<p align="center">
	A clear, beautiful and easy to implement focus-state solution that improves accessibility and usability.
</p>
	
<p align="center">
<a href="https://engineering.q42.nl/floating-focus/">https://engineering.q42.nl/floating-focus/</a>
</p>

![Drag Racing](https://thumbs.gfycat.com/GrimLoneKakapo-size_restricted.gif)

---

## Installation
With [npm](https://www.npmjs.com/) installed, run
```bash
$ npm install @q42/floating-focus-a11y --save
```

## Usage
Import the package and instantiate the class on page load:
```javascript
import FloatingFocus from '@q42/floating-focus-a11y';
new FloatingFocus(containerElement); // Element is an optional parameter which defaults to `document.body`
```

Define a default outline and outline-offset. Either of these values can be overruled per component:
```css
/* Hide all default focus states if a mouse is used, this is completely optional ofcourse */
*:focus {
  outline: none;
}

/* Default outline value, which will be applied to all elements receiving focus, this is a required step. */
/* The .focus class is used by the focus target, more below. */
.floating-focus-enabled :focus, .floating-focus-enabled .focus {
  outline: dodgerblue solid 2px;
  outline-offset: 8px;
}

/* Give all buttons a green focus state instead of dodgerblue, this is optional in case it's needed. */
.floating-focus-enabled [type="button"]:focus {
  outline-color: green;
  outline-offset: 4px;
}
```

### Focus target

Sometimes the actual element that receives focus is hidden from view, as is common with a custom input field. In this case it's possible to define a `focus-target` attribute on the focusable element.

```html
<input type="file" class="hidden" id="file-upload-123" focus-target="file-upload-123-label"/>
<label id="file-upload-123-label" for="file-upload-123">Please upload a file</label>
```

This will append the `focus` class to the target element and make the visual focus box appear around the target element, instead of the element that actually has the native focus.

### Separate stylesheet

For convenience, the styles are included in the script by default. There is also an option to include the stylesheet separately. This is particularly useful with strict `style-src 'self'` CORS headers.

Import unstyled dist file:
```javascript
import FloatingFocus from '@q42/floating-focus-a11y/dist/unstyled';
```

The stylesheet can then be separately imported with your favorite CSS preprocessor:
```css
@import '@q42/floating-focus-a11y/dist/unstyled';
```

### Extra cautions

- Watch out with CSS transitions: if an element that will be focused has a `transition` for `outline-color` / `outline-width` / `outline-style` (including `all` !), the floating focus will not display correctly on that element.

## Develop
```bash
$ npm run build
$ npm run watch
$ npm run test
```

## Deploy
```bash
# bump version
$ npm version [major | minor | patch | premajor | preminor | prepatch | prerelease]

# publish
$ npm publish
```

## License
[MIT](https://opensource.org/licenses/MIT)


================================================
FILE: __mocks__/generalMocks.js
================================================
// This mocks general things like browser APIs or scripts we include in our html.
// Runs once after jest setup.
// Modules can be mocked by using jest.mock('module') in your test or, if you
// (always) need specific behaviour, by putting a mock in this folder.

window.addEventListener = document.addEventListener = jest.fn((type, listener, options) => {
});

window.removeEventListener = document.removeEventListener = jest.fn((type, listener, options) => {
});


================================================
FILE: babel.config.js
================================================
module.exports = (api) => {
  const isTest = api.env('test')
  // Cache the returned result
  api.cache(true)

  return {
    presets: [
      [
        '@babel/preset-env',
        {
          modules: isTest ? 'commonjs' : false,
          loose: true,
          corejs: 3,
          useBuiltIns: 'entry',
          exclude: ['es.regexp.to-string'],
        },
      ],
    ],
  }
}


================================================
FILE: example/index.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<meta http-equiv="X-UA-Compatible" content="ie=edge">
	<title>Document</title>
	<script src="../dist/styled/index.js"></script>
	<script>
		window.addEventListener('load', function() {
			new window['floating-focus'].default();
		});
	</script>
	<style>
		*, *::after, *::before {
			box-sizing: border-box;
		}

		*:focus {
			outline: none;
		}

		.floating-focus-enabled :focus, .floating-focus-enabled .focus {
			outline: dodgerblue solid 3px;
			outline-offset: 4px;
		}

		html {
			height: 100%;
		}

		body {
			min-height: 150%;
			display: flex;
			justify-content: center;
			align-items: center;
			flex-direction: column;
			margin: 20px;
		}

		body > fieldset,
		body > input,
		body > label {
			margin: 10px;
			width: 200px;
		}

		fieldset {
			border: 0;
			padding: 0;
			white-space: nowrap;
			overflow-x: scroll;
			overflow-y: hidden;
			padding-bottom: 20px;
		}

		fieldset > button {
			width: 100%;
		}

		button, input, label, p {
			padding: 5px 10px;
			font: 15px sans-serif;
		}

		button, label {
			background: darkgoldenrod;
			color: white;
			border-radius: 3px;
			border: 0;
		}

		label {
			position: relative;
			overflow: hidden;
		}

		.file-upload-label.focus {
			outline-offset: 0;
			outline-color: orange;
			outline-width: 2px;
			outline-style: dashed;
		}

		input[type="file"] {
			position: absolute;
			bottom: 200%;
			right: 200%;
		}

		.input-warning-wrapper {
			background: orangered;
			width: 200px;
			color: white;
			max-height: 0;
			box-sizing: border-box;
			transition: max-height .25s ease;
		}
		.input-warning-wrapper > p {
			margin: 0;
		}
		.input-warning-wrapper.show {
			max-height: 50px;
		}
	</style>
</head>
<body>
	<fieldset>
		<button>Test 1</button>
		<button>Test 2</button>
		<button>Test 3</button>
	</fieldset>

	<input type="text">
	<input type="text">
	<input type="text">

	<label id="file-upload-label" class="file-upload-label">
		<input type="file" focus-target="file-upload-label"/>
		Please upload a file
	</label>

	<div class="input-warning-wrapper" id="input-warning-wrapper"><p>Please input 3 or more characters!</p></div>
	<input id="warning-field" type="text">

	<script>
		const warningField = document.querySelector('#warning-field');
		const inputWarningWrapper = document.querySelector('#input-warning-wrapper');
		warningField.addEventListener('input', function () {
			if (warningField.value && warningField.value.length < 3) {
				inputWarningWrapper.classList.add('show');
			} else {
				inputWarningWrapper.classList.remove('show');
			}
		})
	</script>
</body>
</html>


================================================
FILE: index.d.ts
================================================
declare class FloatingFocus {
	constructor(container?: Element)
}

export default FloatingFocus


================================================
FILE: jest.config.js
================================================
module.exports = {
  testEnvironment: 'jsdom',
  clearMocks: true,
  moduleFileExtensions: ['js', 'jsx', 'json'],
  setupFilesAfterEnv: ['<rootDir>/__mocks__/generalMocks'],
  transform: {
    // Stub all styling & assets
    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
    '^.+\\.jsx?$': 'babel-jest',
  },
  transformIgnorePatterns: ['/node_modules/'],
  testEnvironmentOptions: {
    url: 'http://localhost/',
  },
}


================================================
FILE: package.json
================================================
{
	"name": "@q42/floating-focus-a11y",
	"version": "1.4.0",
	"description": "An a11y focus solution that is clear, beautiful and easy to implement.",
	"keywords": [
		"a11y",
		"focus",
		"floating"
	],
	"author": {
		"name": "Ricardo Snoek",
		"email": "ricardo@q42.nl",
		"url": "https://q42.nl"
	},
	"homepage": "https://github.com/Q42/FloatingFocus",
	"repository": {
		"type": "git",
		"url": "https://github.com/Q42/FloatingFocus.git"
	},
	"bugs": {
		"url": "https://github.com/Q42/FloatingFocus/issues"
	},
	"main": "dist/styled/index.js",
	"types": "index.d.ts",
	"scripts": {
		"ci": "npm ci --omit=optional",
		"build": "webpack --config webpack.prod.js",
		"watch": "webpack --config webpack.dev.js",
		"lint:check": "prettier --check ./src/**/*.{scss,js}",
		"lint:fix": "prettier --write ./src/**/*.{scss,js}",
		"test": "jest --verbose --coverage",
		"prepublishOnly": "npm run lint:check && npm run test && npm run build"
	},
	"devDependencies": {
		"@babel/core": "^7.21.4",
		"@babel/eslint-parser": "^7.21.3",
		"@babel/preset-env": "^7.21.4",
		"@jest/globals": "^29.5.0",
		"autoprefixer": "^10.4.14",
		"babel-jest": "^29.5.0",
		"babel-loader": "^9.1.2",
		"clean-webpack-plugin": "^4.0.0",
		"copy-webpack-plugin": "^11.0.0",
		"core-js": "^3.30.1",
		"css-loader": "^6.7.3",
		"eslint": "^8.38.0",
		"eslint-config-prettier": "^8.8.0",
		"eslint-plugin-prettier": "^4.2.1",
		"eslint-webpack-plugin": "^4.0.1",
		"jest": "^29.5.0",
		"jest-cli": "^29.5.0",
		"jest-environment-jsdom": "^29.5.0",
		"jest-transform-stub": "^2.0.0",
		"mini-css-extract-plugin": "^2.7.5",
		"postcss-loader": "^7.2.4",
		"prettier": "^2.8.7",
		"regenerator-runtime": "^0.13.11",
		"sass": "^1.62.0",
		"sass-loader": "^13.2.2",
		"style-loader": "^3.3.2",
		"webpack": "^5.79.0",
		"webpack-cli": "^5.0.1",
		"webpack-merge": "^5.8.0"
	},
	"license": "MIT",
	"files": [
		"/dist",
		"index.d.ts"
	],
	"volta": {
		"node": "20.0.0"
	}
}


================================================
FILE: postcss.config.js
================================================
module.exports = {
  plugins: [require('autoprefixer')({ flexbox: 'no-2009' })],
}


================================================
FILE: src/floating-focus.js
================================================
import './floating-focus.scss'

export const HELPER_FADE_TIME = 800
export const MONITOR_INTERVAL = 250

export default class FloatingFocus {
  constructor(container = document.body) {
    this.container = container
    this.previousTargetRect = null
    this.floaterIsMoving = false

    this.addEventListeners()
  }

  addEventListeners() {
    this.handleKeyDown = this.handleKeyDown.bind(this)
    this.handleMouseDown = this.handleMouseDown.bind(this)
    this.handleFocus = this.handleFocus.bind(this)
    this.handleBlur = this.handleBlur.bind(this)
    this.handleScrollResize = this.handleScrollResize.bind(this)
    this.monitorElementPosition = this.monitorElementPosition.bind(this)

    document.addEventListener('keydown', this.handleKeyDown, false)
    document.addEventListener('mousedown', this.handleMouseDown, false)
    document.addEventListener('focus', this.handleFocus, true)
    document.addEventListener('blur', this.handleBlur, true)
    document.addEventListener('scroll', this.handleScrollResize, true)
    window.addEventListener('resize', this.handleScrollResize, true)
  }

  handleKeyDown(e) {
    // Show animation only upon Tab or Arrow keys press.
    if (e.keyCode !== 9 && !(e.keyCode > 36 && e.keyCode < 41)) {
      return
    }

    if (!this.floater) {
      this.floater = this.constructFloatingElement()
    }

    this.enableFloatingFocus()
  }

  handleMouseDown() {
    if (!this.floater) {
      return
    }

    this.disableFloatingFocus()
  }

  handleScrollResize() {
    if (!this.floater || !this.target) {
      return
    }

    requestAnimationFrame(() => this.repositionElement(this.target, this.floater))
  }

  constructFloatingElement() {
    const element = document.createElement('div')
    element.classList.add('floating-focus')

    this.container.appendChild(element)
    return element
  }

  handleFocus(e) {
    let target = e.target

    if (!this.floater || !this.container) {
      return
    }

    if (target === this.floater) {
      this.handleBlur()
      return
    }

    if (!this.container.contains(target)) {
      this.handleBlur()
      return
    }

    this.floater.classList.add('visible')
    this.floater.classList.add('helper')
    this.floater.classList.add('moving')

    const focusTargetAttribute = target.getAttribute('focus-target')
    if (focusTargetAttribute) {
      target = document.querySelector(`#${focusTargetAttribute}`) || target
    }

    this.target = target

    // Make sure we can read the target style (even when refocussing the viewport)
    this.target.classList.remove('floating-focused')
    this.target.classList.add('focus')

    this.resolveTargetOutlineStyle(this.target, this.floater)
    this.repositionElement(this.target, this.floater)

    this.target.classList.add('floating-focused')

    this.handleFloaterMove()

    clearTimeout(this.helperFadeTimeout)
    this.helperFadeTimeout = setTimeout(() => this.floater.classList.remove('helper'), HELPER_FADE_TIME)
  }

  handleBlur() {
    if (!this.floater) {
      return
    }

    this.floater.classList.remove('visible')
    this.floater.classList.remove('helper')
    this.floater.classList.remove('moving')

    if (!this.target) {
      return
    }

    this.target.classList.remove('floating-focused')
    this.target.classList.remove('focus')
  }

  enableFloatingFocus() {
    this.container.classList.add('floating-focus-enabled')
    this.floater.classList.add('enabled')
    clearInterval(this.monitorElementPositionInterval)
    this.monitorElementPositionInterval = setInterval(this.monitorElementPosition, MONITOR_INTERVAL)
  }

  disableFloatingFocus() {
    this.container.classList.remove('floating-focus-enabled')
    this.floater.classList.remove('enabled')
    clearInterval(this.monitorElementPositionInterval)
  }

  handleFloaterMove() {
    if (this.floaterIsMoving) {
      return
    }

    this.floaterIsMoving = true

    const removeMovingClass = () => {
      this.floater.classList.remove('moving')
      this.floater.removeEventListener('transitionend', removeMovingClass)
      this.floaterIsMoving = false
    }
    this.floater.addEventListener('transitionend', removeMovingClass.bind(this))
  }

  addPixels(pixels1, pixels2) {
    const result = parseFloat(pixels1) + parseFloat(pixels2)
    return !isNaN(result) ? `${result}px` : null
  }

  getOffsetBorderRadius(baseRadius, offset) {
    if (!baseRadius || parseFloat(baseRadius) === 0) {
      return '0px'
    }
    if (!offset) {
      return baseRadius
    }

    offset = Math.max(parseFloat(offset), 0)

    return this.addPixels(baseRadius, offset) || '0px'
  }

  resolveTargetOutlineStyle(target, floater) {
    const targetStyle = window.getComputedStyle(target)
    const padding = targetStyle.outlineOffset || null

    Object.assign(floater.style, {
      color: targetStyle.outlineColor,
      borderWidth: targetStyle.outlineWidth,
      borderStyle: targetStyle.outlineStyle,
      borderBottomLeftRadius: this.getOffsetBorderRadius(targetStyle.borderBottomLeftRadius, padding),
      borderBottomRightRadius: this.getOffsetBorderRadius(targetStyle.borderBottomRightRadius, padding),
      borderTopLeftRadius: this.getOffsetBorderRadius(targetStyle.borderTopLeftRadius, padding),
      borderTopRightRadius: this.getOffsetBorderRadius(targetStyle.borderTopRightRadius, padding),
    })
  }

  getFloaterPosition(target) {
    const targetStyle = window.getComputedStyle(target)
    const padding = parseFloat(targetStyle.outlineOffset || '0px')

    const rect = target.getBoundingClientRect()
    this.previousTargetRect = rect

    const width = rect.width + padding * 2
    const height = rect.height + padding * 2
    const left = window.scrollX + rect.left - padding + width / 2
    const top = window.scrollY + rect.top - padding + height / 2

    return {
      left: `${left}px`,
      top: `${top}px`,
      width: `${width}px`,
      height: `${height}px`,
    }
  }

  monitorElementPosition() {
    if (!this.target || !this.previousTargetRect || this.floaterIsMoving) {
      return
    }

    const { left, top, width, height } = this.target.getBoundingClientRect()
    const { left: leftPrev, top: topPrev, width: widthPrev, height: heightPrev } = this.previousTargetRect

    if (left === leftPrev && top === topPrev && width === widthPrev && height === heightPrev) {
      return
    }

    this.floater.classList.add('moving')
    this.repositionElement(this.target, this.floater)
    this.handleFloaterMove()
  }

  repositionElement(target, floater) {
    Object.assign(floater.style, this.getFloaterPosition(target))
  }
}


================================================
FILE: src/floating-focus.scss
================================================
.floating-focus {
  border: 0 solid currentColor;
  position: absolute;
  transform: translate(-50%, -50%);
  opacity: 0;
  will-change: top, left, width, height;
  box-sizing: content-box;
  pointer-events: none;
  overflow: hidden;
  z-index: 9999999999; // It should always be on top of everything, no matter what.

  &.moving {
    transition-property: opacity, left, top, width, height, border-width, border-radius;
    transition-duration: 0.2s, 0.1s, 0.1s, 0.1s, 0.1s, 0.1s, 0.1s;
    transition-timing-function: linear, ease, ease, ease, ease, ease, ease;
  }

  @media (prefers-reduced-motion: reduce) {
    &.moving {
      transition: none;
    }
  }

  &.enabled.visible {
    opacity: 1;
  }

  &::after {
    content: '';
    background: currentColor;
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    opacity: 0;

    transition: opacity 0.2s linear;
  }

  &.helper::after {
    transition-duration: 0.1s;
    opacity: 0.3;
  }
}

.floating-focused {
  outline-style: none !important;

  &::-moz-focus-inner {
    border: 0 !important;
  }
}


================================================
FILE: src/floating-focus.spec.js
================================================
import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'
import FloatingFocus, { HELPER_FADE_TIME, MONITOR_INTERVAL } from './floating-focus'

describe('Floating focus', () => {
  beforeEach(() => {
    jest.useFakeTimers()
    jest.spyOn(window, 'requestAnimationFrame').mockImplementation((cb) => cb())
  })

  afterEach(() => {
    document.body.className = ''
    document.body.innerHTML = ''
    jest.clearAllTimers()
    window.requestAnimationFrame.mockRestore()
  })

  it('Should bind all required event listeners on construction', () => {
    expect(document.addEventListener).not.toHaveBeenCalled()

    const floatingFocus = new FloatingFocus()

    expect(document.addEventListener).toHaveBeenNthCalledWith(1, 'keydown', floatingFocus.handleKeyDown, false)
    expect(document.addEventListener).toHaveBeenNthCalledWith(2, 'mousedown', floatingFocus.handleMouseDown, false)
    expect(document.addEventListener).toHaveBeenNthCalledWith(3, 'focus', floatingFocus.handleFocus, true)
    expect(document.addEventListener).toHaveBeenNthCalledWith(4, 'blur', floatingFocus.handleBlur, true)
    expect(document.addEventListener).toHaveBeenNthCalledWith(5, 'scroll', floatingFocus.handleScrollResize, true)
    expect(window.addEventListener).toHaveBeenNthCalledWith(6, 'resize', floatingFocus.handleScrollResize, true)
  })

  it('Should not do anything if the keyboard input is not Tab or Arrow keys', () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.enableFloatingFocus = jest.fn()

    floatingFocus.handleKeyDown({ keyCode: 21 })
    expect(floatingFocus.enableFloatingFocus).not.toHaveBeenCalled()

    floatingFocus.handleKeyDown({ keyCode: 42 })
    expect(floatingFocus.enableFloatingFocus).not.toHaveBeenCalled()

    floatingFocus.handleKeyDown({ keyCode: 9 })
    expect(floatingFocus.enableFloatingFocus).toHaveBeenCalled()
  })

  it('Should construct, append and return a floating element', () => {
    const floatingFocus = new FloatingFocus()
    const floatingElement = floatingFocus.constructFloatingElement()

    expect(floatingElement instanceof Element).toBe(true)
    expect(floatingElement.classList.contains('floating-focus')).toBe(true)
    expect(floatingElement.tagName).toBe('DIV')
    expect(document.body.contains(floatingElement)).toBe(true)
  })

  it("Should create the 'floater' element when it is not present yet", () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.constructFloatingElement = jest.fn().mockImplementation(() => document.createElement('div'))

    expect(floatingFocus.constructFloatingElement).not.toHaveBeenCalled()

    floatingFocus.handleKeyDown({ keyCode: 9 })
    expect(floatingFocus.constructFloatingElement).toHaveBeenCalled()
  })

  it("Should not recreate the 'floater' element when it's already present created", () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.constructFloatingElement = jest.fn().mockImplementation(() => document.createElement('div'))

    expect(floatingFocus.constructFloatingElement).not.toHaveBeenCalled()

    floatingFocus.handleKeyDown({ keyCode: 9 })
    expect(floatingFocus.constructFloatingElement).toHaveBeenCalled()

    floatingFocus.handleKeyDown({ keyCode: 9 })
    expect(floatingFocus.constructFloatingElement).toHaveBeenCalledTimes(1)
  })

  it("Should only try to disable focus it the 'element' was created before", () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.disableFloatingFocus = jest.fn()

    expect(floatingFocus.disableFloatingFocus).not.toHaveBeenCalled()

    floatingFocus.handleMouseDown()

    expect(floatingFocus.disableFloatingFocus).not.toHaveBeenCalled()

    floatingFocus.handleKeyDown({ keyCode: 9 })
    floatingFocus.handleMouseDown()

    expect(floatingFocus.disableFloatingFocus).toHaveBeenCalled()
  })

  it("Should only reposition if a target and 'floater' was set, when scrolling or resizing", async () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.repositionElement = jest.fn()

    floatingFocus.handleScrollResize()

    expect(floatingFocus.repositionElement).not.toHaveBeenCalled()

    floatingFocus.floater = document.createElement('div')
    floatingFocus.target = document.createElement('div')

    floatingFocus.handleScrollResize()

    await new Promise((resolve) => requestAnimationFrame(resolve))

    expect(floatingFocus.repositionElement).toHaveBeenCalled()
  })

  it('Should enable the floating element by setting the appropriate classes', () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.floater = document.createElement('div')

    expect(document.body.classList.contains('floating-focus-enabled')).toBe(false)
    expect(floatingFocus.floater.classList.contains('enabled')).toBe(false)

    floatingFocus.enableFloatingFocus()

    expect(document.body.classList.contains('floating-focus-enabled')).toBe(true)
    expect(floatingFocus.floater.classList.contains('enabled')).toBe(true)
  })

  it('Should disable the floating element by removing the appropriate classes', () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.floater = document.createElement('div')

    floatingFocus.enableFloatingFocus()

    expect(document.body.classList.contains('floating-focus-enabled')).toBe(true)
    expect(floatingFocus.floater.classList.contains('enabled')).toBe(true)

    floatingFocus.disableFloatingFocus()

    expect(document.body.classList.contains('floating-focus-enabled')).toBe(false)
    expect(floatingFocus.floater.classList.contains('enabled')).toBe(false)
  })

  it('Should early return if not meeting requirements yet, when calling for focus handling', () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.resolveTargetOutlineStyle = jest.fn()
    const target = document.createElement('div')

    floatingFocus.handleFocus({ target }) // Missing 'floater' element

    expect(floatingFocus.resolveTargetOutlineStyle).not.toHaveBeenCalled()

    floatingFocus.floater = floatingFocus.constructFloatingElement()
    floatingFocus.handleFocus({ target: floatingFocus.floater }) // Target is the same as 'floater' element
    floatingFocus.handleFocus({ target }) // Target is not inside the body

    expect(floatingFocus.resolveTargetOutlineStyle).not.toHaveBeenCalled()

    document.body.appendChild(target)
    floatingFocus.handleFocus({ target }) // Successful handleFocus call

    expect(floatingFocus.resolveTargetOutlineStyle).toHaveBeenCalled()
  })

  it('Should set all appropriate classes when handling focus', async () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.floater = floatingFocus.constructFloatingElement()
    const target = document.createElement('div')
    document.body.appendChild(target)

    floatingFocus.handleFocus({ target })

    expect(floatingFocus.floater.classList.contains('visible')).toBe(true)
    expect(floatingFocus.floater.classList.contains('helper')).toBe(true)
    expect(floatingFocus.floater.classList.contains('moving')).toBe(true)

    expect(floatingFocus.target).toBe(target)
    expect(floatingFocus.target.classList.contains('floating-focused')).toBe(true)

    floatingFocus.floater.dispatchEvent(new Event('transitionend'))

    expect(floatingFocus.floater.classList.contains('moving')).toBe(false)

    jest.advanceTimersByTime(HELPER_FADE_TIME)

    expect(floatingFocus.floater.classList.contains('helper')).toBe(false)
  })

  it('Should change the target to a different element when the focused element has a focus-target attribute', async () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.floater = floatingFocus.constructFloatingElement()
    const target = document.createElement('div')
    const focusTarget = document.createElement('div')
    target.setAttribute('focus-target', 'element123')
    focusTarget.id = 'element123'
    document.body.appendChild(target)
    document.body.appendChild(focusTarget)

    floatingFocus.handleFocus({ target })

    expect(floatingFocus.target).toEqual(focusTarget)
    expect(focusTarget.classList.contains('focus')).toBe(true)
  })

  it('Should use the existing target if its focus-target cannot be found', () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.floater = floatingFocus.constructFloatingElement()
    const target = document.createElement('div')
    target.setAttribute('focus-target', 'element123')
    document.body.appendChild(target)

    floatingFocus.handleFocus({ target })

    expect(floatingFocus.target).toEqual(target)
    expect(target.classList.contains('focus')).toBe(true)
  })

  it('Should resolve the target outline style and reposition the element when handling focus', () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.resolveTargetOutlineStyle = jest.fn()
    floatingFocus.repositionElement = jest.fn()
    floatingFocus.floater = floatingFocus.constructFloatingElement()
    const target = document.createElement('div')
    document.body.appendChild(target)

    expect(floatingFocus.resolveTargetOutlineStyle).not.toHaveBeenCalled()
    expect(floatingFocus.resolveTargetOutlineStyle).not.toHaveBeenCalled()

    floatingFocus.handleFocus({ target })

    expect(floatingFocus.resolveTargetOutlineStyle).toHaveBeenCalled()
    expect(floatingFocus.resolveTargetOutlineStyle).toHaveBeenCalled()
  })

  it("Should early return when 'floater' is not present when handling blur", () => {
    const floatingFocus = new FloatingFocus()

    expect(floatingFocus.handleBlur()).toBe(undefined)
  })

  it("Should remove all visibility classes from the 'floater' when handleBlur is called", () => {
    const floatingFocus = new FloatingFocus()
    floatingFocus.target = document.createElement('div')
    floatingFocus.floater = floatingFocus.constructFloatingElement()
    floatingFocus.floater.classList.add('visible')
    floatingFocus.floater.classList.add('helper')
    floatingFocus.floater.classList.add('moving')

    expect(floatingFocus.floater.classList.contains('visible')).toBe(true)
    expect(floatingFocus.floater.classList.contains('helper')).toBe(true)
    expect(floatingFocus.floater.classList.contains('moving')).toBe(true)

    floatingFocus.handleBlur()

    expect(floatingFocus.floater.classList.contains('visible')).toBe(false)
    expect(floatingFocus.floater.classList.contains('helper')).toBe(false)
    expect(floatingFocus.floater.classList.contains('moving')).toBe(false)
  })

  it('Should resolve and append the outline styling from the target element', () => {
    const floatingFocus = new FloatingFocus()
    const target = document.createElement('div')
    const floater = floatingFocus.constructFloatingElement()

    const targetStyle = {
      outlineOffset: '8px',
      outlineColor: 'dodgerblue',
      outlineStyle: 'dashed',
      outlineWidth: '2px',
      borderBottomLeftRadius: '0px',
      borderBottomRightRadius: '0px',
      borderTopLeftRadius: '0px',
      borderTopRightRadius: '0px',
    }

    window.getComputedStyle = jest.fn().mockImplementation(() => targetStyle)

    floatingFocus.resolveTargetOutlineStyle(target, floater)

    expect(floater.style.color).toBe(targetStyle.outlineColor)
    expect(floater.style.borderWidth).toBe(targetStyle.outlineWidth)
    expect(floater.style.borderStyle).toBe(targetStyle.outlineStyle)
    expect(floater.style.borderBottomLeftRadius).toBe(targetStyle.borderBottomLeftRadius)
    expect(floater.style.borderBottomRightRadius).toBe(targetStyle.borderBottomRightRadius)
    expect(floater.style.borderTopLeftRadius).toBe(targetStyle.borderTopLeftRadius)
    expect(floater.style.borderTopRightRadius).toBe(targetStyle.borderTopRightRadius)
  })

  it("Should correctly offset the target element's border radii by its outline offset", () => {
    const floatingFocus = new FloatingFocus()
    const target = document.createElement('div')
    const floater = floatingFocus.constructFloatingElement()

    const targetStyle = {
      outlineOffset: '8px',
      borderBottomLeftRadius: '6px',
      borderBottomRightRadius: '0px',
      borderTopLeftRadius: null,
    }

    window.getComputedStyle = jest.fn().mockImplementation(() => targetStyle)

    floatingFocus.resolveTargetOutlineStyle(target, floater)

    expect(floater.style.borderBottomLeftRadius).toBe('14px')
    expect(floater.style.borderBottomRightRadius).toBe('0px')
    expect(floater.style.borderTopLeftRadius).toBe('0px')
    expect(floater.style.borderTopRightRadius).toBe('0px')

    targetStyle.outlineOffset = null

    floatingFocus.resolveTargetOutlineStyle(target, floater)

    expect(floater.style.borderBottomLeftRadius).toBe(targetStyle.borderBottomLeftRadius)
  })

  it.each([4, 0])("Should reposition 'floater' based on target position -- outline offset %d", (outlineOffset) => {
    const floatingFocus = new FloatingFocus()
    const target = document.createElement('div')
    const floater = floatingFocus.constructFloatingElement()
    const targetStyle = window.getComputedStyle(target)
    targetStyle.outlineOffset = outlineOffset

    const rect = {
      left: 42,
      top: 84,
      width: 42,
      height: 128,
    }

    target.getBoundingClientRect = jest.fn().mockImplementation(() => rect)

    floatingFocus.repositionElement(target, floater)

    expect(floater.style.left).toBe(`${rect.left + rect.width / 2}px`)
    expect(floater.style.top).toBe(`${rect.top + rect.height / 2}px`)
    expect(floater.style.width).toBe(`${rect.width + outlineOffset * 2}px`)
    expect(floater.style.height).toBe(`${rect.height + outlineOffset * 2}px`)
  })

  it("Should automatically reposition the 'floater' when the target element's position changes", async () => {
    const floatingFocus = new FloatingFocus()
    const target = document.createElement('div')
    document.body.appendChild(target)

    const rect = {
      left: 42,
      top: 84,
      width: 42,
      height: 128,
    }

    target.getBoundingClientRect = jest.fn().mockImplementation(() => ({ ...rect }))

    floatingFocus.handleKeyDown({ keyCode: 9 })
    floatingFocus.enableFloatingFocus()
    floatingFocus.handleFocus({ target }, true)

    // Cleanup because transitionend is not called in this setup of jsdom
    floatingFocus.floater.classList.remove('moving')
    floatingFocus.floaterIsMoving = false

    expect(floatingFocus.floater.style.left).toBe(`${rect.left + rect.width / 2}px`)
    expect(floatingFocus.floater.style.top).toBe(`${rect.top + rect.height / 2}px`)

    jest.advanceTimersByTime(MONITOR_INTERVAL)
    expect(floatingFocus.floater.classList.contains('moving')).toBe(false)

    jest.advanceTimersByTime(MONITOR_INTERVAL)
    expect(floatingFocus.floater.classList.contains('moving')).toBe(false)

    rect.left += 42
    rect.top += 42

    expect(floatingFocus.floater.classList.contains('moving')).toBe(false)
    expect(floatingFocus.floater.style.left).not.toBe(`${rect.left + rect.width / 2}px`)
    expect(floatingFocus.floater.style.top).not.toBe(`${rect.top + rect.height / 2}px`)

    jest.advanceTimersByTime(MONITOR_INTERVAL)

    expect(floatingFocus.floater.classList.contains('moving')).toBe(true)
    expect(floatingFocus.floater.style.left).toBe(`${rect.left + rect.width / 2}px`)
    expect(floatingFocus.floater.style.top).toBe(`${rect.top + rect.height / 2}px`)
  })

  describe('addPixels', () => {
    it("Should correctly add up pixel amounts as if it's a normal calculation", () => {
      const floatingFocus = new FloatingFocus()

      const number1 = Math.random() * 10
      const number2 = Math.random() * 10

      expect(floatingFocus.addPixels(`${number1}px`, `${number2}px`)).toBe(`${number1 + number2}px`)
    })

    it('Should return null in case of invalid input', () => {
      const floatingFocus = new FloatingFocus()

      const number1 = '10px'
      const number2 = 'apx'

      expect(floatingFocus.addPixels(number1, number2)).toBeNull()
    })
  })
})


================================================
FILE: webpack.common.js
================================================
const ESLintPlugin = require('eslint-webpack-plugin')

module.exports = {
  entry: {
    'floating-focus': './src/floating-focus.js',
  },
  output: {
    filename: 'index.js',
    library: 'floating-focus',
    libraryTarget: 'umd',
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules|dist/,
        loader: 'babel-loader',
      },
    ],
  },
  plugins: [
    new ESLintPlugin({
      files: 'src/**/*.js',
      failOnWarning: true,
    }),
  ],
}


================================================
FILE: webpack.dev.js
================================================
const merge = require('webpack-merge')
const styled = require('./webpack.styled.js')

module.exports = merge(styled, {
  mode: 'development',
  watch: true,
})


================================================
FILE: webpack.prod.js
================================================
const { merge } = require('webpack-merge')
const styled = require('./webpack.styled.js')
const unstyled = require('./webpack.unstyled.js')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

const prodConfig = {
  mode: 'production',
  plugins: [new CleanWebpackPlugin()],
}

module.exports = [merge(styled, prodConfig), merge(unstyled, prodConfig)]


================================================
FILE: webpack.styled.js
================================================
/* global __dirname */
const path = require('path')
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')

module.exports = merge(common, {
  output: {
    path: path.resolve(__dirname, 'dist/styled'),
  },
  module: {
    rules: [
      {
        test: /\.(css|scss)$/,
        use: ['style-loader', 'css-loader', 'postcss-loader', 'sass-loader'],
      },
    ],
  },
})


================================================
FILE: webpack.unstyled.js
================================================
/* global __dirname */
const path = require('path')
const { merge } = require('webpack-merge')
const common = require('./webpack.common.js')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')

module.exports = merge(common, {
  output: {
    path: path.resolve(__dirname, 'dist/unstyled'),
  },
  module: {
    rules: [
      {
        test: /\.(css|scss)$/,
        use: [MiniCssExtractPlugin.loader, 'css-loader', 'postcss-loader', 'sass-loader'],
      },
    ],
  },
  plugins: [
    new MiniCssExtractPlugin({
      filename: 'index.css',
    }),
    new CopyWebpackPlugin({
      patterns: ['index.d.ts'],
    }),
  ],
})
Download .txt
gitextract_sek1uzv1/

├── .browserslistrc
├── .editorconfig
├── .eslintrc
├── .github/
│   └── workflows/
│       └── node.js.yml
├── .gitignore
├── .nvmrc
├── .prettierrc
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── __mocks__/
│   └── generalMocks.js
├── babel.config.js
├── example/
│   └── index.html
├── index.d.ts
├── jest.config.js
├── package.json
├── postcss.config.js
├── src/
│   ├── floating-focus.js
│   ├── floating-focus.scss
│   └── floating-focus.spec.js
├── webpack.common.js
├── webpack.dev.js
├── webpack.prod.js
├── webpack.styled.js
└── webpack.unstyled.js
Download .txt
SYMBOL INDEX (21 symbols across 2 files)

FILE: index.d.ts
  class FloatingFocus (line 1) | class FloatingFocus {

FILE: src/floating-focus.js
  constant HELPER_FADE_TIME (line 3) | const HELPER_FADE_TIME = 800
  constant MONITOR_INTERVAL (line 4) | const MONITOR_INTERVAL = 250
  class FloatingFocus (line 6) | class FloatingFocus {
    method constructor (line 7) | constructor(container = document.body) {
    method addEventListeners (line 15) | addEventListeners() {
    method handleKeyDown (line 31) | handleKeyDown(e) {
    method handleMouseDown (line 44) | handleMouseDown() {
    method handleScrollResize (line 52) | handleScrollResize() {
    method constructFloatingElement (line 60) | constructFloatingElement() {
    method handleFocus (line 68) | handleFocus(e) {
    method handleBlur (line 111) | handleBlur() {
    method enableFloatingFocus (line 128) | enableFloatingFocus() {
    method disableFloatingFocus (line 135) | disableFloatingFocus() {
    method handleFloaterMove (line 141) | handleFloaterMove() {
    method addPixels (line 156) | addPixels(pixels1, pixels2) {
    method getOffsetBorderRadius (line 161) | getOffsetBorderRadius(baseRadius, offset) {
    method resolveTargetOutlineStyle (line 174) | resolveTargetOutlineStyle(target, floater) {
    method getFloaterPosition (line 189) | getFloaterPosition(target) {
    method monitorElementPosition (line 209) | monitorElementPosition() {
    method repositionElement (line 226) | repositionElement(target, floater) {
Condensed preview — 25 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (42K chars).
[
  {
    "path": ".browserslistrc",
    "chars": 85,
    "preview": "Chrome >= 49\nSafari >= 8\nios_saf >= 8\nie >= 11\nEdge >= 12\nFirefox >= 45\nSamsung >= 2\n"
  },
  {
    "path": ".editorconfig",
    "chars": 242,
    "preview": "# This file is for unifying the coding style for different editors and IDEs\n# editorconfig.org\nroot = true\n\n[*]\ncharset "
  },
  {
    "path": ".eslintrc",
    "chars": 330,
    "preview": "{\n  \"parser\": \"@babel/eslint-parser\",\n  \"extends\": [\n    \"eslint:recommended\",\n    \"plugin:prettier/recommended\"\n  ],\n  "
  },
  {
    "path": ".github/workflows/node.js.yml",
    "chars": 600,
    "preview": "# This workflow will do a clean install of node dependencies, build the source code and run tests across different versi"
  },
  {
    "path": ".gitignore",
    "chars": 142,
    "preview": "# IntelliJ folders\n.idea_modules/\n.idea/\n\n# Node modules\nnode_modules/\n\n# Jest code coverage\ncoverage/\n\n# Built code\ndis"
  },
  {
    "path": ".nvmrc",
    "chars": 7,
    "preview": "20.0.0\n"
  },
  {
    "path": ".prettierrc",
    "chars": 127,
    "preview": "{\n  \"trailingComma\": \"es5\",\n  \"useTabs\": false,\n  \"tabWidth\": 2,\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"printWidth\":"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 255,
    "preview": "{\n\t\"editor.formatOnSave\": true,\n\t\"editor.codeActionsOnSave\": {\n\t\t\"source.organizeImports\": true\n\t},\n\t\"[typescript]\": {\n\t"
  },
  {
    "path": "LICENSE",
    "chars": 1060,
    "preview": "MIT License\n\nCopyright (c) 2019 Q42\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof thi"
  },
  {
    "path": "README.md",
    "chars": 3636,
    "preview": "<h1 align=\"center\">Floating Focus</h1>\n\n<p align=\"center\">\n\t<a href=\"https://github.com/Q42/floating-focus-a11y\"><img sr"
  },
  {
    "path": "__mocks__/generalMocks.js",
    "chars": 464,
    "preview": "// This mocks general things like browser APIs or scripts we include in our html.\n// Runs once after jest setup.\n// Modu"
  },
  {
    "path": "babel.config.js",
    "chars": 385,
    "preview": "module.exports = (api) => {\n  const isTest = api.env('test')\n  // Cache the returned result\n  api.cache(true)\n\n  return "
  },
  {
    "path": "example/index.html",
    "chars": 2723,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"UTF-8\">\n\t<meta name=\"viewport\" content=\"width=device-width, init"
  },
  {
    "path": "index.d.ts",
    "chars": 96,
    "preview": "declare class FloatingFocus {\n\tconstructor(container?: Element)\n}\n\nexport default FloatingFocus\n"
  },
  {
    "path": "jest.config.js",
    "chars": 461,
    "preview": "module.exports = {\n  testEnvironment: 'jsdom',\n  clearMocks: true,\n  moduleFileExtensions: ['js', 'jsx', 'json'],\n  setu"
  },
  {
    "path": "package.json",
    "chars": 1943,
    "preview": "{\n\t\"name\": \"@q42/floating-focus-a11y\",\n\t\"version\": \"1.4.0\",\n\t\"description\": \"An a11y focus solution that is clear, beaut"
  },
  {
    "path": "postcss.config.js",
    "chars": 83,
    "preview": "module.exports = {\n  plugins: [require('autoprefixer')({ flexbox: 'no-2009' })],\n}\n"
  },
  {
    "path": "src/floating-focus.js",
    "chars": 6632,
    "preview": "import './floating-focus.scss'\n\nexport const HELPER_FADE_TIME = 800\nexport const MONITOR_INTERVAL = 250\n\nexport default "
  },
  {
    "path": "src/floating-focus.scss",
    "chars": 1090,
    "preview": ".floating-focus {\n  border: 0 solid currentColor;\n  position: absolute;\n  transform: translate(-50%, -50%);\n  opacity: 0"
  },
  {
    "path": "src/floating-focus.spec.js",
    "chars": 16009,
    "preview": "import { afterEach, beforeEach, describe, expect, it, jest } from '@jest/globals'\nimport FloatingFocus, { HELPER_FADE_TI"
  },
  {
    "path": "webpack.common.js",
    "chars": 492,
    "preview": "const ESLintPlugin = require('eslint-webpack-plugin')\n\nmodule.exports = {\n  entry: {\n    'floating-focus': './src/floati"
  },
  {
    "path": "webpack.dev.js",
    "chars": 160,
    "preview": "const merge = require('webpack-merge')\nconst styled = require('./webpack.styled.js')\n\nmodule.exports = merge(styled, {\n "
  },
  {
    "path": "webpack.prod.js",
    "chars": 362,
    "preview": "const { merge } = require('webpack-merge')\nconst styled = require('./webpack.styled.js')\nconst unstyled = require('./web"
  },
  {
    "path": "webpack.styled.js",
    "chars": 408,
    "preview": "/* global __dirname */\nconst path = require('path')\nconst { merge } = require('webpack-merge')\nconst common = require('."
  },
  {
    "path": "webpack.unstyled.js",
    "chars": 698,
    "preview": "/* global __dirname */\nconst path = require('path')\nconst { merge } = require('webpack-merge')\nconst common = require('."
  }
]

About this extraction

This page contains the full source code of the Q42/floating-focus-a11y GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 25 files (37.6 KB), approximately 10.2k tokens, and a symbol index with 21 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!