├── site ├── vendor │ └── .gitkeep ├── app │ ├── components │ │ ├── .gitkeep │ │ └── demo │ │ │ ├── example1.js │ │ │ └── example1.hbs │ ├── helpers │ │ └── .gitkeep │ ├── models │ │ └── .gitkeep │ ├── routes │ │ ├── .gitkeep │ │ └── application.js │ ├── controllers │ │ └── .gitkeep │ ├── templates │ │ ├── docs.hbs │ │ ├── application.hbs │ │ └── index.hbs │ ├── router.js │ ├── app.js │ ├── styles │ │ ├── docfy-demo.css │ │ ├── app.css │ │ └── highlight.css │ └── index.html ├── tests │ ├── helpers │ │ └── .gitkeep │ ├── unit │ │ ├── .gitkeep │ │ └── routes │ │ │ └── index-test.js │ ├── integration │ │ └── .gitkeep │ ├── test-helper.js │ └── index.html ├── .watchmanconfig ├── public │ └── robots.txt ├── config │ ├── optional-features.json │ ├── targets.js │ ├── ember-cli-update.json │ └── environment.js ├── .ember-cli ├── .editorconfig ├── testem.js ├── .docfy-config.js ├── .github │ └── workflows │ │ └── ci.yml ├── ember-cli-build.js ├── README.md ├── package.json └── tailwind.config.js ├── packages ├── test-app │ ├── app │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── models │ │ │ └── .gitkeep │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── components │ │ │ └── .gitkeep │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── templates │ │ │ └── application.hbs │ │ ├── styles │ │ │ └── app.css │ │ ├── router.js │ │ ├── app.js │ │ └── index.html │ ├── tests │ │ ├── unit │ │ │ └── .gitkeep │ │ ├── integration │ │ │ ├── .gitkeep │ │ │ └── modifiers │ │ │ │ └── focus-trap-test.js │ │ ├── test-helper.js │ │ ├── index.html │ │ └── helpers │ │ │ └── index.js │ ├── .watchmanconfig │ ├── public │ │ └── robots.txt │ ├── .template-lintrc.js │ ├── .stylelintrc.js │ ├── .stylelintignore │ ├── .prettierignore │ ├── config │ │ ├── targets.js │ │ ├── optional-features.json │ │ ├── ember-cli-update.json │ │ ├── ember-try.js │ │ └── environment.js │ ├── .ember-cli │ ├── ember-cli-build.js │ ├── .gitignore │ ├── .prettierrc.js │ ├── .editorconfig │ ├── testem.js │ ├── tsconfig.json │ ├── README.md │ ├── eslint.config.mjs │ └── package.json └── ember-focus-trap │ ├── src │ ├── index.ts │ ├── template-registry.ts │ └── modifiers │ │ └── focus-trap.js │ ├── addon-main.cjs │ ├── .prettierignore │ ├── .prettierrc.cjs │ ├── .gitignore │ ├── babel.config.json │ ├── unpublished-development-types │ └── index.d.ts │ ├── LICENSE.md │ ├── tsconfig.json │ ├── rollup.config.mjs │ ├── package.json │ └── eslint.config.mjs ├── pnpm-workspace.yaml ├── .prettierrc.js ├── lerna.json ├── jsconfig.json ├── docs ├── examples.md ├── installation.md ├── acknowledgments.md ├── index.md ├── examples-demo │ ├── initial-focus.md │ ├── container-self-focus.md │ └── activated-on-render.md └── arguments.md ├── .template-lintrc.js ├── tsconfig.eslint.json ├── .github ├── workflows │ ├── release-drafter.yml │ └── ci.yml └── release-drafter.yml ├── .editorconfig ├── .prettierignore ├── .eslintignore ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── tsconfig.base.json ├── .npmrc ├── LICENSE.md ├── package.json ├── .eslintrc.js ├── CHANGELOG.md └── README.md /site/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/tests/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/test-app/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/test-app/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/test-app/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/test-app/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/test-app/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/test-app/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/test-app/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | - site 4 | -------------------------------------------------------------------------------- /site/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /packages/test-app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["dist"] 3 | } 4 | -------------------------------------------------------------------------------- /site/app/templates/docs.hbs: -------------------------------------------------------------------------------- 1 | 2 | {{outlet}} 3 | -------------------------------------------------------------------------------- /site/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true 5 | }; 6 | -------------------------------------------------------------------------------- /packages/test-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as focusTrap } from './modifiers/focus-trap.js'; 2 | -------------------------------------------------------------------------------- /packages/test-app/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /packages/test-app/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{page-title "TestApp"}} 2 | 3 |

Welcome to Ember

4 | 5 | {{outlet}} -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*", 4 | "site" 5 | ], 6 | "version": "1.1.1", 7 | "npmClient": "pnpm" 8 | } 9 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | {"compilerOptions":{"target":"es6","experimentalDecorators":true},"exclude":["node_modules","bower_components","tmp","vendor",".git","dist"]} -------------------------------------------------------------------------------- /packages/test-app/app/styles/app.css: -------------------------------------------------------------------------------- 1 | /* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */ 2 | -------------------------------------------------------------------------------- /packages/test-app/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'], 5 | }; 6 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/addon-main.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { addonV1Shim } = require('@embroider/addon-shim'); 4 | module.exports = addonV1Shim(__dirname); 5 | -------------------------------------------------------------------------------- /packages/test-app/.stylelintignore: -------------------------------------------------------------------------------- 1 | # unconventional files 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # addons 8 | /.node_modules.ember-try/ 9 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | /declarations/ 7 | 8 | # misc 9 | /coverage/ 10 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 4 3 | --- 4 | 5 | # Examples 6 | 7 | Check the demos from the underlying library here. 8 | -------------------------------------------------------------------------------- /.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | rules: { 6 | 'no-curly-component-invocation': { allow: ['docs-demo', 'demo.example'] } 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /site/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["**/*.ts", "**/*.js", "*.js", ".*.js", "**/.*.js"], 4 | "exclude": ["node_modules", "dist", "**/node_modules", "**/dist"] 5 | } 6 | -------------------------------------------------------------------------------- /packages/test-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # misc 8 | /coverage/ 9 | !.* 10 | .*/ 11 | 12 | # ember-try 13 | /.node_modules.ember-try/ 14 | -------------------------------------------------------------------------------- /site/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | module.exports = { 10 | browsers 11 | }; 12 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | --- 4 | 5 | # Installation 6 | 7 | This is just a regular Ember addon, so you can just install your way into focus 8 | trap. 9 | 10 | ``` 11 | ember install ember-focus-trap 12 | ``` 13 | -------------------------------------------------------------------------------- /packages/test-app/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | module.exports = { 10 | browsers, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/test-app/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true, 6 | "no-implicit-route-model": true 7 | } 8 | -------------------------------------------------------------------------------- /packages/test-app/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 4 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 5 | */ 6 | "isTypeScriptProject": false 7 | } 8 | -------------------------------------------------------------------------------- /packages/test-app/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'test-app/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /site/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /site/tests/unit/routes/index-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'ember-qunit'; 3 | 4 | module('Unit | Route | index', function (hooks) { 5 | setupTest(hooks); 6 | 7 | test('it exists', function (assert) { 8 | let route = this.owner.lookup('route:index'); 9 | assert.ok(route); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | plugins: ['prettier-plugin-ember-template-tag'], 5 | overrides: [ 6 | { 7 | files: '*.{js,gjs,ts,gts,mjs,mts,cjs,cts}', 8 | options: { 9 | singleQuote: true, 10 | templateSingleQuote: false, 11 | }, 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /site/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'site/config/environment'; 3 | import { addDocfyRoutes } from '@docfy/ember'; 4 | 5 | export default class Router extends EmberRouter { 6 | location = config.locationType; 7 | rootURL = config.rootURL; 8 | } 9 | 10 | Router.map(function () { 11 | addDocfyRoutes(this); 12 | }); 13 | -------------------------------------------------------------------------------- /site/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'site/app'; 2 | import config from 'site/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /packages/test-app/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function (defaults) { 6 | let app = new EmberApp(defaults, { 7 | autoImport: { 8 | watchDependencies: ['ember-focus-trap'], 9 | }, 10 | }); 11 | 12 | const { maybeEmbroider } = require('@embroider/test-setup'); 13 | return maybeEmbroider(app); 14 | }; 15 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: Release Drafter 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | draftRelease: 10 | runs-on: ubuntu-latest 11 | 12 | name: Draft Release 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Draft Release 16 | uses: toolmantim/release-drafter@v5.2.0 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | -------------------------------------------------------------------------------- /site/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'site/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /site/app/routes/application.js: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | import { action } from '@ember/object'; 3 | import config from 'site/config/environment'; 4 | 5 | export default class Application extends Route { 6 | @action 7 | didTransition() { 8 | if ( 9 | config.environment !== 'test' && 10 | window && 11 | typeof window.scrollTo === 'function' 12 | ) { 13 | window.scrollTo(0, 0); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/.gitignore: -------------------------------------------------------------------------------- 1 | # The authoritative copies of these live in the monorepo root (because they're 2 | # more useful on github that way), but the build copies them into here so they 3 | # will also appear in published NPM packages. 4 | /README.md 5 | /LICENSE.md 6 | 7 | # compiled output 8 | dist/ 9 | declarations/ 10 | 11 | # npm/pnpm/yarn pack output 12 | *.tgz 13 | 14 | # deps & caches 15 | node_modules/ 16 | .eslintcache 17 | .prettiercache 18 | -------------------------------------------------------------------------------- /packages/test-app/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'test-app/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /site/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /site/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "4.1.0", 7 | "blueprints": [ 8 | { 9 | "name": "app", 10 | "outputRepo": "https://github.com/ember-cli/ember-new-output", 11 | "codemodsSource": "ember-app-codemods-manifest@1", 12 | "isBaseBlueprint": true 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | .lint-todo/ 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /packages/test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /declarations/ 4 | 5 | # dependencies 6 | /node_modules/ 7 | 8 | # misc 9 | /.env* 10 | /.pnp* 11 | /.eslintcache 12 | /coverage/ 13 | /npm-debug.log* 14 | /testem.log 15 | /yarn-error.log 16 | 17 | # ember-try 18 | /.node_modules.ember-try/ 19 | /npm-shrinkwrap.json.ember-try 20 | /package.json.ember-try 21 | /package-lock.json.ember-try 22 | /yarn.lock.ember-try 23 | 24 | # broccoli-debug 25 | /DEBUG/ 26 | -------------------------------------------------------------------------------- /packages/test-app/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | plugins: ['prettier-plugin-ember-template-tag'], 5 | overrides: [ 6 | { 7 | files: '*.{js,gjs,ts,gts,mjs,mts,cjs,cts}', 8 | options: { 9 | singleQuote: true, 10 | }, 11 | }, 12 | { 13 | files: '*.{gjs,gts}', 14 | options: { 15 | singleQuote: true, 16 | templateSingleQuote: false, 17 | }, 18 | }, 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /packages/test-app/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /site/app/components/demo/example1.js: -------------------------------------------------------------------------------- 1 | // BEGIN-SNIPPET docs-demo-basic.js 2 | import Component from '@glimmer/component'; 3 | import { action } from '@ember/object'; 4 | import { tracked } from '@glimmer/tracking'; 5 | 6 | export default class Demo extends Component { 7 | @tracked isActive = false; 8 | 9 | @action 10 | activate() { 11 | this.isActive = true; 12 | } 13 | 14 | @action 15 | deactivate() { 16 | this.isActive = false; 17 | } 18 | } 19 | // END-SNIPPET 20 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | packages/*/dist/ 8 | /tmp/ 9 | 10 | # dependencies 11 | /bower_components/ 12 | /node_modules/ 13 | /packages/*/node_modules/ 14 | 15 | # misc 16 | /coverage/ 17 | !.* 18 | .prettierrc.js 19 | .*/ 20 | .eslintcache 21 | 22 | # ember-try 23 | /.node_modules.ember-try/ 24 | /bower.json.ember-try 25 | /npm-shrinkwrap.json.ember-try 26 | /package.json.ember-try 27 | /package-lock.json.ember-try 28 | /yarn.lock.ember-try 29 | -------------------------------------------------------------------------------- /packages/test-app/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'test-app/app'; 2 | import config from 'test-app/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { loadTests } from 'ember-qunit/test-loader'; 7 | import { start, setupEmberOnerrorValidation } from 'ember-qunit'; 8 | 9 | setApplication(Application.create(config.APP)); 10 | 11 | setup(QUnit.assert); 12 | setupEmberOnerrorValidation(); 13 | loadTests(); 14 | start(); 15 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/src/template-registry.ts: -------------------------------------------------------------------------------- 1 | // Easily allow apps, which are not yet using strict mode templates, to consume your Glint types, by importing this file. 2 | // Add all your components, helpers and modifiers to the template registry here, so apps don't have to do this. 3 | // See https://typed-ember.gitbook.io/glint/environments/ember/authoring-addons 4 | 5 | import type FocusTrap from './modifiers/focus-trap'; 6 | 7 | // Uncomment this once entries have been added! 👇 8 | export default interface Registry { 9 | 'focus-trap': typeof FocusTrap; 10 | } 11 | -------------------------------------------------------------------------------- /packages/test-app/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "6.2.2", 7 | "blueprints": [ 8 | { 9 | "name": "app", 10 | "outputRepo": "https://github.com/ember-cli/ember-new-output", 11 | "codemodsSource": "ember-app-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--no-welcome", 15 | "--pnpm" 16 | ] 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /docs/acknowledgments.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 5 3 | --- 4 | 5 | # Acknowledgments 6 | 7 | Thanks to [@davidtheclark](https://github.com/davidtheclark) for creating [focus-trap](https://github.com/davidtheclark/focus-trap). 8 | This addon was only made possible by his work on the underlying library we use. 9 | 10 | I acknowledgments that most of the examples and wording of this documentation 11 | was inspired/copied from his work. In addition, I used his React component to 12 | guide the implementation of the modifier. 13 | 14 | Thanks to all the Ember folks as well for making modifiers possible. 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | .DS_Store 3 | 4 | # compiled output 5 | /dist/ 6 | /tmp/ 7 | 8 | packages/**/dist/ 9 | site/dist/ 10 | 11 | # dependencies 12 | /node_modules/ 13 | /packages/*/node_modules/ 14 | /site/node_modules/ 15 | 16 | # misc 17 | /.env* 18 | /.pnp* 19 | /.sass-cache 20 | /.eslintcache 21 | /connect.lock 22 | /coverage/ 23 | /libpeerconnection.log 24 | /npm-debug.log* 25 | /testem.log 26 | /yarn-error.log 27 | 28 | # ember-try 29 | /.node_modules.ember-try/ 30 | /bower.json.ember-try 31 | /package.json.ember-try 32 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | [ 4 | "@babel/plugin-transform-typescript", 5 | { 6 | "allExtensions": true, 7 | "onlyRemoveTypeImports": true, 8 | "allowDeclareFields": true 9 | } 10 | ], 11 | "@embroider/addon-dev/template-colocation-plugin", 12 | [ 13 | "babel-plugin-ember-template-compilation", 14 | { 15 | "targetFormat": "hbs", 16 | "transforms": [] 17 | } 18 | ], 19 | [ 20 | "module:decorator-transforms", 21 | { "runtime": { "import": "decorator-transforms/runtime" } } 22 | ] 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /site/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900' 20 | ].filter(Boolean) 21 | } 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /packages/test-app/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /site/app/components/demo/example1.hbs: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | 7 |
14 |

15 | Here is a focus trap 16 | with 17 | some 18 | focusable 19 | parts. 20 |

21 | 22 | 25 |
-------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist/ 3 | /tmp/ 4 | 5 | # dependencies 6 | /bower_components/ 7 | 8 | # misc 9 | /.bowerrc 10 | /.editorconfig 11 | /.ember-cli 12 | /.env* 13 | /.eslintcache 14 | /.eslintignore 15 | /.eslintrc.js 16 | /.gitignore 17 | /.git 18 | /.prettierignore 19 | /.prettierrc.js 20 | /.template-lintrc.js 21 | /.travis.yml 22 | /.watchmanconfig 23 | /bower.json 24 | /config/ember-try.js 25 | /CONTRIBUTING.md 26 | /ember-cli-build.js 27 | /testem.js 28 | /tests/ 29 | /yarn-error.log 30 | /yarn.lock 31 | .gitkeep 32 | .prettierrc.js 33 | jsconfig.json 34 | .github 35 | /config/deploy.js 36 | /config/addon-docs.js 37 | 38 | # ember-try 39 | /.node_modules.ember-try/ 40 | /bower.json.ember-try 41 | /package.json.ember-try 42 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | * `git clone https://github.com/josemarluedke/ember-focus-trap.git` 6 | * `cd ember-focus-trap` 7 | * `yarn install` 8 | 9 | ## Linting 10 | 11 | * `yarn lint` 12 | * `yarn lint:fix` 13 | 14 | ## Running tests 15 | 16 | * `ember test` – Runs the test suite on the current Ember version 17 | * `ember test --server` – Runs the test suite in "watch mode" 18 | * `ember try:each` – Runs the test suite against multiple Ember versions 19 | 20 | ## Running the dummy application 21 | 22 | * `ember serve` 23 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 24 | 25 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). 26 | -------------------------------------------------------------------------------- /packages/test-app/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | {{content-for "head-footer"}} 15 | 16 | 17 | {{content-for "body"}} 18 | 19 | 20 | 21 | 22 | {{content-for "body-footer"}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "allowJs": true, 5 | "moduleResolution": "node", 6 | "allowSyntheticDefaultImports": true, 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "alwaysStrict": true, 10 | "strictNullChecks": true, 11 | "strictPropertyInitialization": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noUnusedLocals": false, 14 | "noUnusedParameters": true, 15 | "noImplicitReturns": true, 16 | "noEmitOnError": false, 17 | "skipLibCheck": true, 18 | "noEmit": true, 19 | "inlineSourceMap": true, 20 | "inlineSources": true, 21 | "module": "es6", 22 | "experimentalDecorators": true, 23 | "stripInternal": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/unpublished-development-types/index.d.ts: -------------------------------------------------------------------------------- 1 | // Add any types here that you need for local development only. 2 | // These will *not* be published as part of your addon, so be careful that your published code does not rely on them! 3 | 4 | import '@glint/environment-ember-loose'; 5 | import '@glint/environment-ember-template-imports'; 6 | 7 | // Uncomment if you need to support consuming projects in loose mode 8 | // 9 | // declare module '@glint/environment-ember-loose/registry' { 10 | // export default interface Registry { 11 | // // Add any registry entries from other addons here that your addon itself uses (in non-strict mode templates) 12 | // // See https://typed-ember.gitbook.io/glint/using-glint/ember/using-addons 13 | // } 14 | // } 15 | -------------------------------------------------------------------------------- /packages/test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember/tsconfig.json", 3 | "glint": { 4 | "environment": ["ember-loose", "ember-template-imports"] 5 | }, 6 | "compilerOptions": { 7 | "skipLibCheck": true, 8 | "noEmit": true, 9 | "noEmitOnError": false, 10 | "declaration": false, 11 | "declarationMap": false, 12 | // The combination of `baseUrl` with `paths` allows Ember's classic package 13 | // layout, which is not resolvable with the Node resolution algorithm, to 14 | // work with TypeScript. 15 | "baseUrl": ".", 16 | "paths": { 17 | "test-app/tests/*": ["tests/*"], 18 | "test-app/*": ["app/*"], 19 | "*": ["types/*"] 20 | }, 21 | "types": [ 22 | "ember-source/types", 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | #################### 2 | # super strict mode 3 | #################### 4 | auto-install-peers=true 5 | strict-peer-dependents=true 6 | resolve-peers-from-workspace-root=false 7 | 8 | ################ 9 | # Optimizations 10 | ################ 11 | # Less strict, but required for tooling to not barf on duplicate peer trees. 12 | # (many libraries declare the same peers, which resolve to the same 13 | # versions) 14 | dedupe-peer-dependents=true 15 | public-hoist-pattern[]=ember-source 16 | 17 | ################ 18 | # Compatibility 19 | ################ 20 | # highest is what everyone is used to, but 21 | # not ensuring folks are actually compatible with declared ranges. 22 | resolution-mode=highest 23 | 24 | ################ 25 | # Misc 26 | ################ 27 | verify-deps-before-run=install # always verify deps before running any scripts 28 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | --- 4 | 5 | # What it does? 6 | 7 | Please read the [focus-trap](https://github.com/focus-trap/focus-trap) documentation to understand what a focus trap is, what happens when a focus trap is activated, and what happens when one is deactivated. 8 | 9 | This project is a simple Ember modifier that allow you to trap DOM nodes in 10 | your applications. 11 | 12 | - The focus trap automatically activates when element is rendered. 13 | - The focus trap automatically deactivates when element is removed. 14 | - The focus trap can be activated and deactivated, paused and unpaused via 15 | named arguments. 16 | 17 | ## Example 18 | 19 | Below is a simple demo if a focus trap working. Click below to active the trap. You will notice that 20 | you can't tab to elements out of the box. 21 | 22 | > When a focus trap is active, the box will be highlighted. 23 | 24 | 25 | -------------------------------------------------------------------------------- /site/.docfy-config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const autolinkHeadings = require('remark-autolink-headings'); 3 | const withProse = require('@docfy/plugin-with-prose'); 4 | const codeTitle = require('remark-code-titles'); 5 | const highlight = require('rehype-highlight'); 6 | 7 | /** 8 | * @type {import('@docfy/core/lib/types').DocfyConfig} 9 | */ 10 | module.exports = { 11 | repository: { 12 | url: 'https://github.com/josemarluedke/ember-focus-trap', 13 | editBranch: 'main' 14 | }, 15 | tocMaxDepth: 3, 16 | plugins: [withProse({ className: 'prose dark:prose-light' })], 17 | remarkPlugins: [codeTitle, autolinkHeadings], 18 | rehypePlugins: [highlight], 19 | sources: [ 20 | { 21 | root: path.resolve(__dirname, '../docs'), 22 | pattern: '**/*.md', 23 | urlPrefix: 'docs' 24 | } 25 | ], 26 | labels: { 27 | docs: 'Docs', 28 | fetching: 'Fetching' 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /site/.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: {} 9 | 10 | concurrency: 11 | group: ci-${{ github.head_ref || github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | lint: 16 | name: "Lint" 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - uses: actions/checkout@v2 21 | - name: Install Node 22 | uses: actions/setup-node@v2 23 | with: 24 | node-version: 12.x 25 | cache: npm 26 | - name: Install Dependencies 27 | run: npm ci 28 | - name: Lint 29 | run: npm run lint 30 | 31 | test: 32 | name: "Test" 33 | runs-on: ubuntu-latest 34 | 35 | steps: 36 | - uses: actions/checkout@v2 37 | - name: Install Node 38 | uses: actions/setup-node@v2 39 | with: 40 | node-version: 12.x 41 | cache: npm 42 | - name: Install Dependencies 43 | run: npm ci 44 | - name: Run Tests 45 | run: npm test 46 | -------------------------------------------------------------------------------- /site/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{page-title 'Ember Focus Trap'}} 2 | 3 | 9 | <:title> 10 | Ember Focus Trap 11 | 12 | <:left> 13 | 14 | 15 | 16 | <:right as |linkClass linkClassActive|> 17 | 24 | 25 | 26 | 27 | {{outlet}} 28 | 29 |
32 | Released under MIT License - Created by 33 | 39 | Josemar Luedke 40 | 41 |
-------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2025 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. 10 | -------------------------------------------------------------------------------- /site/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Enable FastBoot Rehydration 4 | process.env.EXPERIMENTAL_RENDER_MODE_SERIALIZE = true; 5 | 6 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 7 | const env = EmberApp.env(); 8 | 9 | const postcssPlugins = [ 10 | require('postcss-import')({ path: ['../node_modules'] }), 11 | require('tailwindcss')('./tailwind.config.js'), 12 | require('postcss-nested'), 13 | require('autoprefixer')({ 14 | overrideBrowserslist: require('./config/targets').browsers 15 | }) 16 | ]; 17 | 18 | if (env !== 'development') { 19 | process.env.PURGE_CSS = 'true'; 20 | } 21 | if (env === 'production') { 22 | // Tailwind JIT 23 | process.env.TAILWIND_MODE = 'build'; 24 | } 25 | 26 | module.exports = function (defaults) { 27 | let app = new EmberApp(defaults, { 28 | prember: { 29 | urls: ['/'] 30 | }, 31 | fastboot: { 32 | hostWhitelist: [/^localhost:\d+$/] 33 | }, 34 | postcssOptions: { 35 | compile: { 36 | enabled: true, 37 | cacheInclude: [/.*\.css$/, /tailwind\.config\.js$/], 38 | plugins: postcssPlugins 39 | } 40 | } 41 | }); 42 | return app.toTree(); 43 | }; 44 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Release Drafter - https://github.com/toolmantim/release-drafter 2 | 3 | name-template: v$NEXT_MINOR_VERSION 4 | tag-template: v$NEXT_MINOR_VERSION 5 | categories: 6 | - title: ':boom: Breaking Change' 7 | label: 'Type: Breaking Change' 8 | 9 | - title: ':rocket: Enhancement' 10 | label: 'Type: Enhancement' 11 | 12 | - title: ':bug: Bug Fix' 13 | label: 'Type: Bug' 14 | 15 | - title: ':nail_care: Refactor' 16 | label: 'Type: Refactor' 17 | 18 | - title: ':memo: Documentation' 19 | label: 'Type: Documentation' 20 | 21 | - title: ':house: Internal' 22 | label: 'Type: Internal' 23 | 24 | - title: ':wrench: Tooling' 25 | label: 'Type: Tooling' 26 | 27 | - title: ':package: Dependencies' 28 | label: 'Type: Dependencies' 29 | 30 | change-template: '- $TITLE (#$NUMBER) @$AUTHOR' 31 | no-changes-template: '- No changes' 32 | template: | 33 | $CHANGES 34 | 35 | *** 36 | 37 | ### Contributors 38 | 39 | $CONTRIBUTORS 40 | 41 | *** 42 | 43 | For full changes, see the [comparison between $PREVIOUS_TAG and v$NEXT_MINOR_VERSION](https://github.com/josemarluedke/ember-focus-trap/compare/$PREVIOUS_TAG...v$NEXT_MINOR_VERSION) 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "workspaces": [ 5 | "packages/*", 6 | "site" 7 | ], 8 | "scripts": { 9 | "prepare": "pnpm --filter ember-focus-trap prepare", 10 | "start": "npm-run-all --parallel start:*", 11 | "start:addon": "pnpm --filter ember-focus-trap start", 12 | "start:test-app": "pnpm --filter test-app start", 13 | "format": "pnpm -r --if-present format", 14 | "lint": "pnpm -r --if-present lint", 15 | "lint:fix": "pnpm -r --if-present lint:fix", 16 | "test": "pnpm --filter test-app run test:ember" 17 | }, 18 | "devDependencies": { 19 | "@underline/eslint-config-ember-typescript": "^0.12.0", 20 | "@underline/eslint-config-node": "^0.12.0", 21 | "@underline/eslint-config-typescript": "^0.12.0", 22 | "eslint": "^8.57.1", 23 | "lerna": "^4.0.0", 24 | "npm-run-all": "4.1.5", 25 | "typescript": "^4.9.5" 26 | }, 27 | "dependencies": { 28 | "eslint-plugin-qunit": "^7.3.4" 29 | }, 30 | "resolutions": { 31 | "@embroider/macros": "^1.0.0" 32 | }, 33 | "engines": { 34 | "node": "22.11.0" 35 | }, 36 | "packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b" 37 | } 38 | -------------------------------------------------------------------------------- /packages/test-app/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp Tests 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /site/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | let ENV = { 5 | modulePrefix: 'site', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 13 | }, 14 | EXTEND_PROTOTYPES: false 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | } 21 | }; 22 | 23 | if (environment === 'development') { 24 | // ENV.APP.LOG_RESOLVER = true; 25 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 26 | // ENV.APP.LOG_TRANSITIONS = true; 27 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 28 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 29 | } 30 | 31 | if (environment === 'test') { 32 | // Testem prefers this... 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | ENV.APP.autoboot = false; 41 | } 42 | 43 | if (environment === 'production') { 44 | // here you can enable a production-specific feature 45 | } 46 | 47 | return ENV; 48 | }; 49 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'v*' 8 | pull_request: {} 9 | 10 | env: 11 | CI: 'true' 12 | 13 | jobs: 14 | test: 15 | name: Tests 16 | runs-on: ubuntu-latest 17 | env: 18 | VOLTA_FEATURE_PNPM: true 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: wyvox/action-setup-pnpm@v3 23 | - name: Install dependencies 24 | run: pnpm install 25 | - name: Lint 26 | run: pnpm lint 27 | - name: Test 28 | run: pnpm test 29 | 30 | try-scenarios: 31 | name: ${{ matrix.ember-try-scenario }} 32 | runs-on: ubuntu-latest 33 | 34 | strategy: 35 | fail-fast: true 36 | matrix: 37 | ember-try-scenario: 38 | - ember-lts-4.12 39 | - ember-lts-5.4 40 | - ember-release 41 | - ember-beta 42 | - ember-canary 43 | - embroider-safe 44 | - embroider-optimized 45 | 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: wyvox/action-setup-pnpm@v3 49 | - name: Install dependencies 50 | run: pnpm install 51 | - name: Test 52 | env: 53 | EMBER_TRY_SCENARIO: ${{ matrix.ember-try-scenario }} 54 | run: pnpm ember try:one $EMBER_TRY_SCENARIO 55 | 56 | working-directory: packages/test-app 57 | -------------------------------------------------------------------------------- /packages/test-app/config/ember-try.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const getChannelURL = require('ember-source-channel-url'); 4 | const { embroiderSafe, embroiderOptimized } = require('@embroider/test-setup'); 5 | 6 | module.exports = async function () { 7 | return { 8 | usePnpm: true, 9 | scenarios: [ 10 | { 11 | name: 'ember-lts-4.12', 12 | npm: { 13 | devDependencies: { 14 | 'ember-source': '~4.12.0', 15 | }, 16 | }, 17 | }, 18 | { 19 | name: 'ember-lts-5.4', 20 | npm: { 21 | devDependencies: { 22 | 'ember-source': '~5.4.0', 23 | }, 24 | }, 25 | }, 26 | { 27 | name: 'ember-release', 28 | npm: { 29 | devDependencies: { 30 | 'ember-source': await getChannelURL('release'), 31 | }, 32 | }, 33 | }, 34 | { 35 | name: 'ember-beta', 36 | npm: { 37 | devDependencies: { 38 | 'ember-source': await getChannelURL('beta'), 39 | }, 40 | }, 41 | }, 42 | { 43 | name: 'ember-canary', 44 | npm: { 45 | devDependencies: { 46 | 'ember-source': await getChannelURL('canary'), 47 | }, 48 | }, 49 | }, 50 | embroiderSafe(), 51 | embroiderOptimized(), 52 | ], 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /packages/test-app/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'test-app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | EXTEND_PROTOTYPES: false, 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 14 | }, 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | }, 21 | }; 22 | 23 | if (environment === 'development') { 24 | // ENV.APP.LOG_RESOLVER = true; 25 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 26 | // ENV.APP.LOG_TRANSITIONS = true; 27 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 28 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 29 | } 30 | 31 | if (environment === 'test') { 32 | // Testem prefers this... 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | ENV.APP.autoboot = false; 41 | } 42 | 43 | if (environment === 'production') { 44 | // here you can enable a production-specific feature 45 | } 46 | 47 | return ENV; 48 | }; 49 | -------------------------------------------------------------------------------- /site/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Site Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{content-for "body-footer"}} 38 | {{content-for "test-body-footer"}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/examples-demo/initial-focus.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 1 3 | --- 4 | # Activated when rendered 5 | 6 | In this example, the focus trap is activated when the element is rendered. 7 | We also pass a function to `onDeactivate` to allow `Escape` key to deactivate 8 | the focus trap. 9 | 10 | 11 | ```hbs template 12 |
13 | 16 |
17 | 18 | {{#if this.isActive}} 19 |
27 |

28 | Here is a focus trap 29 | with 30 | some 31 | focusable 32 | parts. 33 |

34 | 37 |
38 | {{/if}} 39 | ``` 40 | 41 | ```js component 42 | import Component from '@glimmer/component'; 43 | import { tracked } from '@glimmer/tracking'; 44 | 45 | export default class Demo extends Component { 46 | @tracked isActive = false; 47 | 48 | activate = () => { 49 | this.isActive = true; 50 | }; 51 | 52 | deactivate = () => { 53 | this.isActive = false; 54 | }; 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /docs/examples-demo/container-self-focus.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 2 3 | --- 4 | 5 | # Initial Focus 6 | 7 | When this focus trap activates, focus jumps to a specific, manually specified element. 8 | 9 | ```hbs template 10 |
11 | 14 |
15 | 16 |
28 |

29 | Here is a focus trap 30 | with 31 | some 32 | focusable 33 | parts. 34 |

35 | 38 |
39 | ``` 40 | 41 | ```js component 42 | import Component from '@glimmer/component'; 43 | import { tracked } from '@glimmer/tracking'; 44 | 45 | export default class Demo extends Component { 46 | @tracked isActive = false; 47 | 48 | activate = () => { 49 | this.isActive = true; 50 | }; 51 | 52 | deactivate = () => { 53 | if (this.isActive) { 54 | this.isActive = false; 55 | } 56 | }; 57 | } 58 | ``` 59 | -------------------------------------------------------------------------------- /packages/test-app/tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | } from 'ember-qunit'; 6 | 7 | // This file exists to provide wrappers around ember-qunit's 8 | // test setup functions. This way, you can easily extend the setup that is 9 | // needed per test type. 10 | 11 | function setupApplicationTest(hooks, options) { 12 | upstreamSetupApplicationTest(hooks, options); 13 | 14 | // Additional setup for application tests can be done here. 15 | // 16 | // For example, if you need an authenticated session for each 17 | // application test, you could do: 18 | // 19 | // hooks.beforeEach(async function () { 20 | // await authenticateSession(); // ember-simple-auth 21 | // }); 22 | // 23 | // This is also a good place to call test setup functions coming 24 | // from other addons: 25 | // 26 | // setupIntl(hooks, 'en-us'); // ember-intl 27 | // setupMirage(hooks); // ember-cli-mirage 28 | } 29 | 30 | function setupRenderingTest(hooks, options) { 31 | upstreamSetupRenderingTest(hooks, options); 32 | 33 | // Additional setup for rendering tests can be done here. 34 | } 35 | 36 | function setupTest(hooks, options) { 37 | upstreamSetupTest(hooks, options); 38 | 39 | // Additional setup for unit tests can be done here. 40 | } 41 | 42 | export { setupApplicationTest, setupRenderingTest, setupTest }; 43 | -------------------------------------------------------------------------------- /site/app/styles/docfy-demo.css: -------------------------------------------------------------------------------- 1 | .docfy-demo { 2 | @apply mt-8 mb-12; 3 | } 4 | 5 | .docfy-demo__example { 6 | @apply p-4 border-t border-l border-r rounded-t; 7 | } 8 | 9 | .docfy-demo__description { 10 | @apply pb-5; 11 | 12 | &__header { 13 | @apply flex justify-between items-center mb-4; 14 | 15 | &__title { 16 | @apply text-xl font-semibold; 17 | } 18 | 19 | &__edit-url { 20 | @apply text-xs; 21 | &:hover { 22 | @apply text-primary-600; 23 | } 24 | } 25 | } 26 | } 27 | 28 | .docfy-demo__snippets { 29 | &__tabs { 30 | @apply px-2 border-t border-l border-r; 31 | 32 | &__button { 33 | @apply p-2 mr-2 border-b-4 border-transparent; 34 | 35 | &--active, 36 | &:hover { 37 | @apply border-primary-700; 38 | } 39 | } 40 | } 41 | } 42 | 43 | .docfy-demo__snippet { 44 | @apply border rounded-b; 45 | 46 | pre { 47 | @apply p-4; 48 | @apply flex text-gray-200 bg-gray-800; 49 | @apply text-sm leading-normal; 50 | @apply font-mono; 51 | @apply rounded-b; 52 | @apply overflow-auto; 53 | scrollbar-width: none; 54 | } 55 | } 56 | 57 | .dark { 58 | .docfy-demo__example, 59 | .docfy-demo__description, 60 | .docfy-demo__snippets__tabs, 61 | .docfy-demo__snippet { 62 | @apply border-gray-700; 63 | } 64 | 65 | .docfy-demo__snippet pre { 66 | @apply bg-gray-900; 67 | @apply border-gray-700; 68 | } 69 | 70 | .docfy-demo__description__header__title { 71 | @apply text-primary-500; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | project: join(__dirname, './tsconfig.eslint.json') 7 | }, 8 | extends: ['@underline/eslint-config-ember-typescript'], 9 | rules: { 10 | '@typescript-eslint/no-empty-interface': 'off', 11 | 'ember/no-empty-glimmer-component-classes': 'off', 12 | '@typescript-eslint/ban-types': ['off', { types: { object: null } }] 13 | }, 14 | overrides: [ 15 | { 16 | files: ['{packages,examples}/**/tests/**/*.ts'], 17 | rules: { 18 | '@typescript-eslint/explicit-function-return-type': 'off', 19 | '@typescript-eslint/no-non-null-assertion': 'off' 20 | } 21 | }, 22 | 23 | // node files 24 | { 25 | files: [ 26 | 'addon-main.js', 27 | '.babelrc.js', 28 | 'babel.config.js', 29 | '.docfy-config.js', 30 | '.ember-cli.js', 31 | '.eslintrc.js', 32 | '.prettierrc.js', 33 | '.template-lintrc.js', 34 | 'config/**/*.js', 35 | 'ember-addon-main.js', 36 | 'ember-cli-build.js', 37 | 'postcss.config.js', 38 | 'tailwind.config.js', 39 | 'testem.js', 40 | 'webpack.config.js', 41 | 'scripts/**/*.js', 42 | 'packages/*/config/**/*.js', 43 | 'packages/*/tests/dummy/config/**/*.js', 44 | 'packages/*/config/**/*.js', 45 | 'site/config/**/*.js', 46 | 'site/tests/dummy/config/**/*.js' 47 | ], 48 | extends: ['@underline/eslint-config-node'], 49 | rules: {} 50 | } 51 | ] 52 | }; 53 | -------------------------------------------------------------------------------- /site/README.md: -------------------------------------------------------------------------------- 1 | # site 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](https://git-scm.com/) 11 | * [Node.js](https://nodejs.org/) (with npm) 12 | * [Ember CLI](https://ember-cli.com/) 13 | * [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd site` 19 | * `npm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `npm run lint` 39 | * `npm run lint:fix` 40 | 41 | ### Building 42 | 43 | * `ember build` (development) 44 | * `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | * [ember.js](https://emberjs.com/) 53 | * [ember-cli](https://ember-cli.com/) 54 | * Development Browser Extensions 55 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 56 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 57 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | v0.3.0 2 | 3 | - Upgrade to Ember v3.10 & Drop node v6 (#11) @josemarluedke 4 | 5 | 6 | *** 7 | 8 | ### Contributors 9 | 10 | @josemarluedke 11 | 12 | *** 13 | 14 | For full changes, see the [comparison between v0.2.0 and v0.3.0](https://github.com/josemarluedke/ember-focus-trap/compare/v0.2.0...v0.3.0) 15 | 16 | 17 | v0.2.0 18 | 19 | ## :zap: New Feature 20 | 21 | - Add shouldSelfFocus option (#9) @josemarluedke 22 | 23 | *** 24 | 25 | ### Contributors 26 | 27 | @josemarluedke 28 | 29 | *** 30 | 31 | For full changes, see the [comparison between v0.1.1 and v0.2.0](https://github.com/josemarluedke/ember-focus-trap/compare/v0.1.1...v0.2.0) 32 | 33 | 34 | v0.1.1 35 | 36 | ## :memo: Documentation 37 | 38 | - Add more examples to documentation and reword a few things (#8) @josemarluedke 39 | 40 | 41 | 42 | *** 43 | 44 | ### Contributors 45 | 46 | @josemarluedke 47 | 48 | *** 49 | 50 | For full changes, see the [comparison between v0.1.0 and v0.2.0](https://github.com/josemarluedke/ember-focus-trap/compare/v0.1.0...v0.2.0) 51 | 52 | 53 | v0.1.0 54 | 55 | ## :zap: New Feature 56 | 57 | - Implement focus-trap modifier (#4) @josemarluedke 58 | 59 | ## :rocket: Enhancement 60 | 61 | - Activate focus trap by default (#6) @josemarluedke 62 | 63 | ## :memo: Documentation 64 | 65 | - Add Documentation (#7) @josemarluedke 66 | - Initial pass at README (#5) @josemarluedke 67 | 68 | ## :house: Internal 69 | 70 | - Setup prettier with ESLint (#2) @josemarluedke 71 | 72 | ## :wrench: Tooling 73 | 74 | - Add Release Drafter as GitHub Action (#3) @josemarluedke 75 | 76 | 77 | *** 78 | 79 | ### Contributors 80 | 81 | @ember-tomster and @josemarluedke 82 | -------------------------------------------------------------------------------- /packages/test-app/README.md: -------------------------------------------------------------------------------- 1 | # test-app 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | - [Git](https://git-scm.com/) 11 | - [Node.js](https://nodejs.org/) 12 | - [pnpm](https://pnpm.io/) 13 | - [Ember CLI](https://cli.emberjs.com/release/) 14 | - [Google Chrome](https://google.com/chrome/) 15 | 16 | ## Installation 17 | 18 | - `git clone ` this repository 19 | - `cd test-app` 20 | - `pnpm install` 21 | 22 | ## Running / Development 23 | 24 | - `pnpm start` 25 | - Visit your app at [http://localhost:4200](http://localhost:4200). 26 | - Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 27 | 28 | ### Code Generators 29 | 30 | Make use of the many generators for code, try `ember help generate` for more details 31 | 32 | ### Running Tests 33 | 34 | - `pnpm test` 35 | - `pnpm test:ember --server` 36 | 37 | ### Linting 38 | 39 | - `pnpm lint` 40 | - `pnpm lint:fix` 41 | 42 | ### Building 43 | 44 | - `pnpm ember build` (development) 45 | - `pnpm build` (production) 46 | 47 | ### Deploying 48 | 49 | Specify what it takes to deploy your app. 50 | 51 | ## Further Reading / Useful Links 52 | 53 | - [ember.js](https://emberjs.com/) 54 | - [ember-cli](https://cli.emberjs.com/release/) 55 | - Development Browser Extensions 56 | - [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 57 | - [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 58 | -------------------------------------------------------------------------------- /docs/arguments.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 3 3 | --- 4 | 5 | # Arguments 6 | 7 | ## focusTrapOptions 8 | 9 | Type: Object, optional 10 | 11 | Pass any of the options available in focus-trap's createOptions. [See 12 | documentation](https://github.com/davidtheclark/focus-trap#focustrap--createfocustrapelement-createoptions). 13 | 14 | ## isActive 15 | 16 | Type: Boolean, optional 17 | 18 | By default, the FocusTrap activates when the element renders. 19 | So you activate and deactivate it by the live cycle of element. 20 | If, however, you want to control when FocusTrap is activated or deactivated, 21 | you can use this option. 22 | 23 | ## isPaused 24 | 25 | Type: Boolean, optional 26 | 27 | If you would like to pause or unpause the focus trap (see [focus-trap's documentation](https://github.com/davidtheclark/focus-trap#focustrappause)), toggle this argument. 28 | 29 | ## shouldSelfFocus 30 | 31 | Type: Boolean, optional 32 | 33 | If you would like to initially focus in the element in which the modifier is 34 | being applied. 35 | 36 | **Important:** 37 | 38 | - For the focus in the container to work, you must make it focusable. You can 39 | accomplish that by adding `tabindex="-1"`. 40 | - This options has no effect if something is passed in `initialFocus` under `focusTrapOptions`. 41 | 42 | ## additionalElements 43 | 44 | Type: Array, optional 45 | 46 | If needed, additional elements or containers can be added where focus-trap needs to be applied to. For example, absolutely/fixed-positioned dropdowns or popovers placed within a wormhole/placeholder that need to be included in said focus-trap. 47 | 48 | As mentioned within [focus-trap's documentation](https://github.com/focus-trap/focus-trap#createfocustrapelement-createoptions), the order determines where the focus will go after the last tabbable element of a DOM node/selector is reached. 49 | -------------------------------------------------------------------------------- /site/app/styles/app.css: -------------------------------------------------------------------------------- 1 | /* purgecss start ignore */ 2 | @import 'tailwindcss/base'; 3 | @import 'tailwindcss/components'; 4 | 5 | /* purgecss end ignore */ 6 | 7 | @import 'tailwindcss/utilities'; 8 | 9 | /* purgecss start ignore */ 10 | @import './highlight.css'; 11 | @import './docfy-demo.css'; 12 | 13 | .remark-code-title { 14 | @apply py-2 px-4 bg-gray-900 border-b border-gray-800 text-white rounded-t-lg mt-5 text-sm; 15 | } 16 | 17 | .remark-code-title + .prose pre:first-child { 18 | @apply rounded-t-none; 19 | } 20 | 21 | .prose h2, 22 | .prose h3, 23 | .prose h4, 24 | .docfy-demo__description__header__title { 25 | & > a { 26 | @apply ml-6; 27 | 28 | &::before { 29 | content: '#'; 30 | @apply text-primary-600 dark:text-primary-400 -ml-6 pr-2 absolute opacity-100; 31 | } 32 | 33 | @screen lg { 34 | @apply ml-0; 35 | 36 | &::before { 37 | @apply opacity-0 pr-1; 38 | } 39 | } 40 | } 41 | &:hover { 42 | & > a::before { 43 | @apply opacity-100; 44 | } 45 | } 46 | } 47 | 48 | .button { 49 | background-color: #3d4852; 50 | color: white; 51 | padding: 0.65rem 1.2rem; 52 | border: 0.0625rem solid #3d4852; 53 | border-radius: 0.25rem; 54 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.12), 0 2px 4px 0 rgba(0, 0, 0, 0.08); 55 | 56 | &:focus { 57 | outline: 5px solid rgba(255, 215, 35, 0.6); 58 | } 59 | } 60 | 61 | .trap { 62 | border: 1px solid rgba(255, 215, 35, 0.5); 63 | border-radius: 0.25rem; 64 | padding: 1em 2em; 65 | 66 | &.is-active { 67 | background-color: rgba(255, 215, 35, 0.3); 68 | } 69 | 70 | *:focus, 71 | &:focus { 72 | outline: 5px solid rgba(255, 215, 35, 0.6); 73 | } 74 | 75 | input { 76 | border: 0.0625rem solid #ccc; 77 | padding: 0.25rem; 78 | margin: 0.125rem; 79 | border-radius: 0.25rem; 80 | } 81 | } 82 | 83 | /* purgecss end ignore */ 84 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember/tsconfig.json", 3 | "include": ["src/**/*", "unpublished-development-types/**/*"], 4 | "glint": { 5 | "environment": ["ember-loose", "ember-template-imports"] 6 | }, 7 | "compilerOptions": { 8 | "allowJs": true, 9 | "declarationDir": "declarations", 10 | /** 11 | https://www.typescriptlang.org/tsconfig#noEmit 12 | 13 | We want to emit declarations, so this option must be set to `false`. 14 | @tsconfig/ember sets this to `true`, which is incompatible with our need to set `emitDeclarationOnly`. 15 | @tsconfig/ember is more optimized for apps, which wouldn't emit anything, only type check. 16 | */ 17 | "noEmit": false, 18 | /** 19 | https://www.typescriptlang.org/tsconfig#emitDeclarationOnly 20 | We want to only emit declarations as we use Rollup to emit JavaScript. 21 | */ 22 | "emitDeclarationOnly": true, 23 | 24 | /** 25 | https://www.typescriptlang.org/tsconfig#noEmitOnError 26 | Do not block emit on TS errors. 27 | */ 28 | "noEmitOnError": false, 29 | 30 | /** 31 | https://www.typescriptlang.org/tsconfig#rootDir 32 | "Default: The longest common path of all non-declaration input files." 33 | 34 | Because we want our declarations' structure to match our rollup output, 35 | we need this "rootDir" to match the "srcDir" in the rollup.config.mjs. 36 | 37 | This way, we can have simpler `package.json#exports` that matches 38 | imports to files on disk 39 | */ 40 | "rootDir": "./src", 41 | 42 | /** 43 | https://www.typescriptlang.org/tsconfig#allowImportingTsExtensions 44 | 45 | We want our tooling to know how to resolve our custom files so the appropriate plugins 46 | can do the proper transformations on those files. 47 | */ 48 | "allowImportingTsExtensions": true, 49 | 50 | "types": ["ember-source/types"] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/examples-demo/activated-on-render.md: -------------------------------------------------------------------------------- 1 | --- 2 | order: 3 3 | --- 4 | # Self focus on the container 5 | 6 | Initial focus is on the containing element, which has `tabindex="-1"`; so when you tab through the trap focus does not return to the container. 7 | 8 | In this example, clicking outside of the container will deactivate the focus 9 | trap. Note the option `clickOutsideDeactivates`. 10 | 11 | This example uses the modifier argument `shouldSelfFocus`. The same could be 12 | achieved by adding an `id` to the container and passing the selector to `initialFocus` 13 | in `focusTrapOptions`. However, `shouldSelfFocus` is a short hand for passing 14 | the element directly to `focusTrapOptions`. 15 | 16 | 17 | 18 | 19 | ```hbs template 20 |
21 | 24 |
25 | 26 |
36 |

37 | Here is a focus trap 38 | with 39 | some 40 | focusable 41 | parts. 42 | 43 |
44 | Initially focused input 45 | 46 |

47 | 50 |
51 | ``` 52 | 53 | ```js component 54 | import Component from '@glimmer/component'; 55 | import { tracked } from '@glimmer/tracking'; 56 | 57 | export default class Demo extends Component { 58 | @tracked isActive = false; 59 | 60 | activate = () => { 61 | this.isActive = true; 62 | }; 63 | 64 | deactivate = () => { 65 | if (this.isActive) { 66 | this.isActive = false; 67 | } 68 | }; 69 | } 70 | ``` 71 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "site", 3 | "version": "1.1.1", 4 | "private": true, 5 | "description": "Small description for site goes here", 6 | "repository": "", 7 | "license": "MIT", 8 | "author": "", 9 | "directories": { 10 | "doc": "doc", 11 | "test": "tests" 12 | }, 13 | "scripts": { 14 | "build": "ember build --environment=production", 15 | "lint:hbs": "ember-template-lint .", 16 | "start": "ember serve", 17 | "test": "ember test", 18 | "test:types": "echo 'no type tests'", 19 | "test:ember": "ember test" 20 | }, 21 | "devDependencies": { 22 | "@docfy/ember": "^0.4.4", 23 | "@docfy/plugin-with-prose": "^0.4.4", 24 | "@ember/optional-features": "^2.0.0", 25 | "@ember/test-helpers": "^2.6.0", 26 | "@frontile/core": "^0.12.2", 27 | "@frontile/overlays": "^0.12.2", 28 | "@glimmer/component": "^1.0.4", 29 | "@glimmer/tracking": "^1.0.3", 30 | "@tailwindcss/typography": "^0.4.1", 31 | "autoprefixer": "^10.3.5", 32 | "broccoli-asset-rev": "^3.0.0", 33 | "docfy-theme-ember": "josemarluedke/docfy-theme-ember", 34 | "ember-auto-import": "^2.4.0", 35 | "ember-cli": "~6.0.0", 36 | "ember-cli-app-version": "^7.0.0", 37 | "ember-cli-babel": "^8.2.0", 38 | "ember-cli-dependency-checker": "^3.2.0", 39 | "ember-cli-fastboot": "^4.1.5", 40 | "ember-cli-htmlbars": "^6.0.1", 41 | "ember-cli-inject-live-reload": "^2.1.0", 42 | "ember-cli-postcss": "^8.0.0", 43 | "ember-cli-sri": "^2.1.1", 44 | "ember-cli-terser": "^4.0.2", 45 | "ember-focus-trap": "workspace:*", 46 | "ember-load-initializers": "^3.0.1", 47 | "ember-page-title": "^8.2.3", 48 | "ember-qunit": "^8.1.1", 49 | "ember-resolver": "^13.0.2", 50 | "ember-source": "~6.0.0", 51 | "ember-template-lint": "^3.15.0", 52 | "eslint-plugin-qunit": "^7.2.0", 53 | "loader.js": "^4.7.0", 54 | "postcss-import": "^16.1.0", 55 | "postcss-nested": "^7.0.2", 56 | "prember": "^2.1.0", 57 | "prettier": "^2.5.1", 58 | "qunit": "^2.17.2", 59 | "qunit-dom": "^3.3.0", 60 | "rehype-highlight": "^4.1.0", 61 | "remark-autolink-headings": "^6.0.1", 62 | "remark-code-titles": "^0.1.2", 63 | "tailwindcss": "^2.2.15", 64 | "webpack": "^5.67.0" 65 | }, 66 | "engines": { 67 | "node": "12.* || 14.* || >= 16" 68 | }, 69 | "ember": { 70 | "edition": "octane" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 |

4 | 5 |

6 | Build Status 7 | Ember Observer Score 8 | NPM version 9 |

10 | 11 | **Ember Focus Trap**: A Ember modifier to trap your focus. 12 | 13 | [View the docs here](https://ember-focus-trap.netlify.app/). 14 | 15 | We use [focus-trap](https://github.com/focus-trap/focus-trap) as a lower-level implementation. 16 | It is super lightweight and has minimal dependencies. 17 | 18 | Trap focus within a DOM node. 19 | 20 | There may come a time when you find it important to trap focus within a DOM node — so that when a user hits `Tab` or `Shift+Tab` or clicks around, she can"t escape a certain cycle of focusable elements. 21 | 22 | Please read the [focus-trap](https://github.com/focus-trap/focus-trap) documentation to understand what a focus trap is, what happens when a focus trap is activated, and what happens when one is deactivated. 23 | 24 | ## Compatibility 25 | 26 | * Ember.js v4.16 or above (Ember v4 compatible) 27 | * Ember CLI v4.12 or above 28 | * Node.js v12 or above 29 | 30 | ## Installation 31 | 32 | ``` 33 | ember install ember-focus-trap 34 | ``` 35 | 36 | ## Usage 37 | 38 | [See demos and read the documentation here](https://josemarluedke.github.io/ember-focus-trap). 39 | 40 | ```hbs 41 |
42 |

43 | Here is a focus trap 44 | with 45 | some 46 | focusable 47 | parts. 48 |

49 |

50 | 51 |

52 |
53 | ``` 54 | 55 | ### With Focus Trap Options 56 | 57 | ```hbs 58 |
66 |

67 | Here is a focus trap 68 | with 69 | some 70 | focusable 71 | parts. 72 |

73 |

74 | 75 |

76 |
77 | ``` 78 | 79 | ## Contributing 80 | 81 | See the [Contributing](CONTRIBUTING.md) guide for details. 82 | 83 | ## License 84 | 85 | This project is licensed under the [MIT License](LICENSE.md). 86 | -------------------------------------------------------------------------------- /site/app/templates/index.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 |

10 | Ember Focus Trap 11 |

12 |

13 | A Ember modifier to trap your focus. 14 |

15 | 16 | 20 | Read the Docs 21 | 27 | 33 | 34 | 35 |
36 |
37 | 38 |
41 |
42 |

We use 43 | focus-trap 44 | as a lower-level implementation. It is super lightweight and has minimal 45 | dependencies.

46 |

Trap focus within a DOM node.

47 |

There may come a time when you find it important to trap focus within a 48 | DOM node — so that when a user hits 49 | Tab 50 | or 51 | Shift+Tab 52 | or clicks around, she can"t escape a certain cycle of focusable elements.

53 |

Please read the 54 | focus-trap 55 | documentation to understand what a focus trap is, what happens when a 56 | focus trap is activated, and what happens when one is deactivated.

57 |
58 |
-------------------------------------------------------------------------------- /site/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 32 | {{content-for "head"}} 33 | 34 | 35 | 36 | 40 | 41 | 42 | 43 | 44 | {{content-for "head-footer"}} 45 | 68 | 69 | 70 | 71 | {{content-for "body"}} 72 | 73 | 74 | 75 | 76 | {{content-for "body-footer"}} 77 | 78 | 79 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { babel } from '@rollup/plugin-babel'; 2 | import copy from 'rollup-plugin-copy'; 3 | import { Addon } from '@embroider/addon-dev/rollup'; 4 | 5 | const addon = new Addon({ 6 | srcDir: 'src', 7 | destDir: 'dist', 8 | }); 9 | 10 | export default { 11 | // This provides defaults that work well alongside `publicEntrypoints` below. 12 | // You can augment this if you need to. 13 | output: addon.output(), 14 | 15 | plugins: [ 16 | // These are the modules that users should be able to import from your 17 | // addon. Anything not listed here may get optimized away. 18 | // By default all your JavaScript modules (**/*.js) will be importable. 19 | // But you are encouraged to tweak this to only cover the modules that make 20 | // up your addon's public API. Also make sure your package.json#exports 21 | // is aligned to the config here. 22 | // See https://github.com/embroider-build/embroider/blob/main/docs/v2-faq.md#how-can-i-define-the-public-exports-of-my-addon 23 | addon.publicEntrypoints(['**/*.js', 'index.js', 'template-registry.js']), 24 | 25 | // These are the modules that should get reexported into the traditional 26 | // "app" tree. Things in here should also be in publicEntrypoints above, but 27 | // not everything in publicEntrypoints necessarily needs to go here. 28 | addon.appReexports([ 29 | 'components/**/*.js', 30 | 'helpers/**/*.js', 31 | 'modifiers/**/*.js', 32 | 'services/**/*.js', 33 | ]), 34 | 35 | // Follow the V2 Addon rules about dependencies. Your code can import from 36 | // `dependencies` and `peerDependencies` as well as standard Ember-provided 37 | // package names. 38 | addon.dependencies(), 39 | 40 | // This babel config should *not* apply presets or compile away ES modules. 41 | // It exists only to provide development niceties for you, like automatic 42 | // template colocation. 43 | // 44 | // By default, this will load the actual babel config from the file 45 | // babel.config.json. 46 | babel({ 47 | extensions: ['.js', '.gjs', '.ts', '.gts'], 48 | babelHelpers: 'bundled', 49 | }), 50 | 51 | // Ensure that standalone .hbs files are properly integrated as Javascript. 52 | addon.hbs(), 53 | 54 | // Ensure that .gjs files are properly integrated as Javascript 55 | addon.gjs(), 56 | 57 | // Emit .d.ts declaration files 58 | addon.declarations('declarations'), 59 | 60 | // addons are allowed to contain imports of .css files, which we want rollup 61 | // to leave alone and keep in the published output. 62 | addon.keepAssets(['**/*.css']), 63 | 64 | // Remove leftover build artifacts when starting a new build. 65 | addon.clean(), 66 | 67 | // Copy Readme and License into published package 68 | copy({ 69 | targets: [ 70 | { src: '../../README.md', dest: '.' }, 71 | { src: '../../LICENSE.md', dest: '.' }, 72 | ], 73 | }), 74 | ], 75 | }; 76 | -------------------------------------------------------------------------------- /packages/test-app/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Debugging: 3 | * https://eslint.org/docs/latest/use/configure/debug 4 | * ---------------------------------------------------- 5 | * 6 | * Print a file's calculated configuration 7 | * 8 | * npx eslint --print-config path/to/file.js 9 | * 10 | * Inspecting the config 11 | * 12 | * npx eslint --inspect-config 13 | * 14 | */ 15 | import globals from 'globals'; 16 | import js from '@eslint/js'; 17 | 18 | import ember from 'eslint-plugin-ember/recommended'; 19 | import prettier from 'eslint-plugin-prettier/recommended'; 20 | import qunit from 'eslint-plugin-qunit'; 21 | import n from 'eslint-plugin-n'; 22 | 23 | import babelParser from '@babel/eslint-parser'; 24 | 25 | const esmParserOptions = { 26 | ecmaFeatures: { modules: true }, 27 | ecmaVersion: 'latest', 28 | requireConfigFile: false, 29 | babelOptions: { 30 | plugins: [ 31 | ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }], 32 | ], 33 | }, 34 | }; 35 | 36 | export default [ 37 | js.configs.recommended, 38 | prettier, 39 | ember.configs.base, 40 | ember.configs.gjs, 41 | /** 42 | * Ignores must be in their own object 43 | * https://eslint.org/docs/latest/use/configure/ignore 44 | */ 45 | { 46 | ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'], 47 | }, 48 | /** 49 | * https://eslint.org/docs/latest/use/configure/configuration-files#configuring-linter-options 50 | */ 51 | { 52 | linterOptions: { 53 | reportUnusedDisableDirectives: 'error', 54 | }, 55 | }, 56 | { 57 | files: ['**/*.js'], 58 | languageOptions: { 59 | parser: babelParser, 60 | }, 61 | }, 62 | { 63 | files: ['**/*.{js,gjs}'], 64 | languageOptions: { 65 | parserOptions: esmParserOptions, 66 | globals: { 67 | ...globals.browser, 68 | }, 69 | }, 70 | }, 71 | { 72 | files: ['tests/**/*-test.{js,gjs}'], 73 | plugins: { 74 | qunit, 75 | }, 76 | }, 77 | /** 78 | * CJS node files 79 | */ 80 | { 81 | files: [ 82 | '**/*.cjs', 83 | 'config/**/*.js', 84 | 'testem.js', 85 | 'testem*.js', 86 | '.prettierrc.js', 87 | '.stylelintrc.js', 88 | '.template-lintrc.js', 89 | 'ember-cli-build.js', 90 | ], 91 | plugins: { 92 | n, 93 | }, 94 | 95 | languageOptions: { 96 | sourceType: 'script', 97 | ecmaVersion: 'latest', 98 | globals: { 99 | ...globals.node, 100 | }, 101 | }, 102 | }, 103 | /** 104 | * ESM node files 105 | */ 106 | { 107 | files: ['**/*.mjs'], 108 | plugins: { 109 | n, 110 | }, 111 | 112 | languageOptions: { 113 | sourceType: 'module', 114 | ecmaVersion: 'latest', 115 | parserOptions: esmParserOptions, 116 | globals: { 117 | ...globals.node, 118 | }, 119 | }, 120 | }, 121 | ]; 122 | -------------------------------------------------------------------------------- /packages/test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Small description for test-app goes here", 6 | "repository": "", 7 | "license": "MIT", 8 | "author": "", 9 | "directories": { 10 | "doc": "doc", 11 | "test": "tests" 12 | }, 13 | "scripts": { 14 | "build": "ember build --environment=production", 15 | "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto", 16 | "lint:css": "stylelint \"**/*.css\"", 17 | "lint:css:fix": "concurrently \"pnpm:lint:css -- --fix\"", 18 | "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto", 19 | "lint:hbs": "ember-template-lint .", 20 | "lint:hbs:fix": "ember-template-lint . --fix", 21 | "lint:js": "eslint . --cache", 22 | "lint:js:fix": "eslint . --fix", 23 | "start": "ember serve", 24 | "test": "concurrently \"pnpm:lint\" \"pnpm:test:*\" --names \"lint,test:\" --prefixColors auto", 25 | "test:ember": "ember test" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.26.9", 29 | "@babel/eslint-parser": "^7.26.5", 30 | "@babel/plugin-proposal-decorators": "^7.25.9", 31 | "@ember/optional-features": "^2.2.0", 32 | "@ember/test-helpers": "^5.1.0", 33 | "@embroider/test-setup": "^4.0.0", 34 | "@eslint/js": "^9.21.0", 35 | "@glimmer/component": "^2.0.0", 36 | "@glimmer/tracking": "^1.1.2", 37 | "broccoli-asset-rev": "^3.0.0", 38 | "concurrently": "^9.1.2", 39 | "ember-auto-import": "^2.10.0", 40 | "ember-cli": "~6.2.2", 41 | "ember-cli-app-version": "^7.0.0", 42 | "ember-cli-babel": "^8.2.0", 43 | "ember-cli-clean-css": "^3.0.0", 44 | "ember-cli-dependency-checker": "^3.3.3", 45 | "ember-cli-htmlbars": "^6.3.0", 46 | "ember-cli-inject-live-reload": "^2.1.0", 47 | "ember-cli-sri": "^2.1.1", 48 | "ember-cli-terser": "^4.0.2", 49 | "ember-data": "~5.3.11", 50 | "ember-fetch": "^8.1.2", 51 | "ember-focus-trap": "workspace:*", 52 | "ember-load-initializers": "^3.0.1", 53 | "ember-modifier": "^4.2.0", 54 | "ember-page-title": "^9.0.1", 55 | "ember-qunit": "^9.0.1", 56 | "ember-resolver": "^13.1.0", 57 | "ember-source": "~6.2.0", 58 | "ember-source-channel-url": "^3.0.0", 59 | "ember-template-imports": "^4.3.0", 60 | "ember-template-lint": "^7.0.0", 61 | "ember-try": "^3.0.0", 62 | "eslint": "^9.21.0", 63 | "eslint-config-prettier": "^10.0.2", 64 | "eslint-plugin-ember": "^12.5.0", 65 | "eslint-plugin-n": "^17.16.1", 66 | "eslint-plugin-prettier": "^5.2.3", 67 | "eslint-plugin-qunit": "^8.1.2", 68 | "globals": "^16.0.0", 69 | "loader.js": "^4.7.0", 70 | "prettier": "^3.5.3", 71 | "prettier-plugin-ember-template-tag": "^2.0.4", 72 | "qunit": "^2.24.1", 73 | "qunit-dom": "^3.4.0", 74 | "sinon": "^19.0.2", 75 | "stylelint": "^16.15.0", 76 | "stylelint-config-standard": "^37.0.0", 77 | "stylelint-prettier": "^5.0.3", 78 | "tracked-built-ins": "^4.0.0", 79 | "webpack": "^5.98.0" 80 | }, 81 | "engines": { 82 | "node": ">= 18" 83 | }, 84 | "ember": { 85 | "edition": "octane" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-focus-trap", 3 | "version": "1.1.1", 4 | "description": "A Ember modifier to trap your focus.", 5 | "keywords": [ 6 | "ember-addon" 7 | ], 8 | "homepage": "https://josemarluedke.github.io/ember-focus-trap", 9 | "repository": "https://github.com/josemarluedke/ember-focus-trap", 10 | "license": "MIT", 11 | "author": "Josemar Luedke ", 12 | "exports": { 13 | ".": { 14 | "types": "./declarations/index.d.ts", 15 | "default": "./dist/index.js" 16 | }, 17 | "./*": { 18 | "types": "./declarations/*.d.ts", 19 | "default": "./dist/*.js" 20 | }, 21 | "./addon-main.js": "./addon-main.cjs" 22 | }, 23 | "typesVersions": { 24 | "*": { 25 | "*": [ 26 | "declarations/*" 27 | ] 28 | } 29 | }, 30 | "files": [ 31 | "addon-main.cjs", 32 | "declarations", 33 | "dist" 34 | ], 35 | "scripts": { 36 | "build": "rollup --config", 37 | "format": "prettier . --cache --write", 38 | "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto", 39 | "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto && pnpm run format", 40 | "lint:format": "prettier . --cache --check", 41 | "lint:js": "eslint . --cache", 42 | "lint:js:fix": "eslint . --fix", 43 | "lint:types": "glint", 44 | "prepack": "rollup --config", 45 | "prepare": "pnpm build", 46 | "start": "rollup --config --watch", 47 | "test": "echo 'A v2 addon does not have tests, run tests in test-app'" 48 | }, 49 | "dependencies": { 50 | "@embroider/addon-shim": "^1.9.0", 51 | "decorator-transforms": "^2.3.0", 52 | "focus-trap": "^7.6.4" 53 | }, 54 | "devDependencies": { 55 | "@babel/core": "^7.26.9", 56 | "@babel/eslint-parser": "^7.26.8", 57 | "@babel/plugin-transform-typescript": "^7.26.8", 58 | "@babel/runtime": "^7.26.9", 59 | "@embroider/addon-dev": "^7.1.1", 60 | "@eslint/js": "^9.21.0", 61 | "@glint/core": "^1.5.2", 62 | "@glint/environment-ember-loose": "^1.5.2", 63 | "@glint/environment-ember-template-imports": "^1.5.2", 64 | "@glint/template": "^1.5.2", 65 | "@rollup/plugin-babel": "^6.0.4", 66 | "@tsconfig/ember": "^3.0.9", 67 | "babel-plugin-ember-template-compilation": "^2.3.0", 68 | "concurrently": "^9.1.2", 69 | "ember-source": "^6.2.0", 70 | "ember-template-lint": "^7.0.0", 71 | "eslint": "^9.21.0", 72 | "eslint-config-prettier": "^10.0.2", 73 | "eslint-plugin-ember": "^12.5.0", 74 | "eslint-plugin-import": "^2.31.0", 75 | "eslint-plugin-n": "^17.15.1", 76 | "globals": "^16.0.0", 77 | "prettier": "^3.5.2", 78 | "prettier-plugin-ember-template-tag": "^2.0.4", 79 | "rollup": "^4.34.9", 80 | "rollup-plugin-copy": "^3.5.0", 81 | "typescript": "~5.8.2", 82 | "typescript-eslint": "^8.25.0" 83 | }, 84 | "publishConfig": { 85 | "access": "public" 86 | }, 87 | "ember": { 88 | "edition": "octane" 89 | }, 90 | "ember-addon": { 91 | "version": 2, 92 | "type": "addon", 93 | "main": "addon-main.cjs", 94 | "app-js": { 95 | "./modifiers/focus-trap.js": "./dist/_app_/modifiers/focus-trap.js" 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | /** 2 | * Debugging: 3 | * https://eslint.org/docs/latest/use/configure/debug 4 | * ---------------------------------------------------- 5 | * 6 | * Print a file's calculated configuration 7 | * 8 | * npx eslint --print-config path/to/file.js 9 | * 10 | * Inspecting the config 11 | * 12 | * npx eslint --inspect-config 13 | * 14 | */ 15 | import babelParser from '@babel/eslint-parser'; 16 | import js from '@eslint/js'; 17 | import prettier from 'eslint-config-prettier'; 18 | import ember from 'eslint-plugin-ember/recommended'; 19 | import importPlugin from 'eslint-plugin-import'; 20 | import n from 'eslint-plugin-n'; 21 | import globals from 'globals'; 22 | import ts from 'typescript-eslint'; 23 | 24 | const parserOptions = { 25 | esm: { 26 | js: { 27 | ecmaFeatures: { modules: true }, 28 | ecmaVersion: 'latest', 29 | }, 30 | ts: { 31 | projectService: true, 32 | project: true, 33 | tsconfigRootDir: import.meta.dirname, 34 | }, 35 | }, 36 | }; 37 | 38 | export default ts.config( 39 | js.configs.recommended, 40 | ember.configs.base, 41 | ember.configs.gjs, 42 | ember.configs.gts, 43 | prettier, 44 | /** 45 | * Ignores must be in their own object 46 | * https://eslint.org/docs/latest/use/configure/ignore 47 | */ 48 | { 49 | ignores: ['dist/', 'declarations/', 'node_modules/', 'coverage/', '!**/.*'], 50 | }, 51 | /** 52 | * https://eslint.org/docs/latest/use/configure/configuration-files#configuring-linter-options 53 | */ 54 | { 55 | linterOptions: { 56 | reportUnusedDisableDirectives: 'error', 57 | }, 58 | }, 59 | { 60 | files: ['**/*.js'], 61 | languageOptions: { 62 | parser: babelParser, 63 | }, 64 | }, 65 | { 66 | files: ['**/*.{js,gjs}'], 67 | languageOptions: { 68 | parserOptions: parserOptions.esm.js, 69 | globals: { 70 | ...globals.browser, 71 | }, 72 | }, 73 | }, 74 | { 75 | files: ['**/*.{ts,gts}'], 76 | languageOptions: { 77 | parser: ember.parser, 78 | parserOptions: parserOptions.esm.ts, 79 | }, 80 | extends: [...ts.configs.recommendedTypeChecked, ember.configs.gts], 81 | }, 82 | { 83 | files: ['src/**/*'], 84 | plugins: { 85 | import: importPlugin, 86 | }, 87 | rules: { 88 | // require relative imports use full extensions 89 | 'import/extensions': ['error', 'always', { ignorePackages: true }], 90 | }, 91 | }, 92 | /** 93 | * CJS node files 94 | */ 95 | { 96 | files: [ 97 | '**/*.cjs', 98 | '.prettierrc.js', 99 | '.stylelintrc.js', 100 | '.template-lintrc.js', 101 | 'addon-main.cjs', 102 | ], 103 | plugins: { 104 | n, 105 | }, 106 | 107 | languageOptions: { 108 | sourceType: 'script', 109 | ecmaVersion: 'latest', 110 | globals: { 111 | ...globals.node, 112 | }, 113 | }, 114 | }, 115 | /** 116 | * ESM node files 117 | */ 118 | { 119 | files: ['**/*.mjs'], 120 | plugins: { 121 | n, 122 | }, 123 | 124 | languageOptions: { 125 | sourceType: 'module', 126 | ecmaVersion: 'latest', 127 | parserOptions: parserOptions.esm.js, 128 | globals: { 129 | ...globals.node, 130 | }, 131 | }, 132 | }, 133 | ); 134 | -------------------------------------------------------------------------------- /packages/ember-focus-trap/src/modifiers/focus-trap.js: -------------------------------------------------------------------------------- 1 | import { setModifierManager, capabilities } from '@ember/modifier'; 2 | import { createFocusTrap as CreateFocusTrap } from 'focus-trap'; 3 | 4 | let cap; 5 | try { 6 | cap = capabilities('3.22'); 7 | } catch { 8 | cap = capabilities('3.13'); 9 | } 10 | 11 | export default setModifierManager( 12 | () => { 13 | return { 14 | capabilities: cap, 15 | 16 | createModifier() { 17 | return { 18 | focusTrapOptions: undefined, 19 | isActive: true, 20 | isPaused: false, 21 | shouldSelfFocus: false, 22 | focusTrap: undefined, 23 | }; 24 | }, 25 | 26 | installModifier( 27 | state, 28 | element, 29 | { 30 | named: { 31 | isActive, 32 | isPaused, 33 | shouldSelfFocus, 34 | focusTrapOptions, 35 | additionalElements, 36 | _createFocusTrap, 37 | }, 38 | }, 39 | ) { 40 | // treat the original focusTrapOptions as immutable, so do a shallow copy here 41 | state.focusTrapOptions = { ...focusTrapOptions }; 42 | if (typeof isActive !== 'undefined') { 43 | state.isActive = isActive; 44 | } 45 | 46 | if (typeof isPaused !== 'undefined') { 47 | state.isPaused = isPaused; 48 | } 49 | if ( 50 | state.focusTrapOptions && 51 | typeof state.focusTrapOptions.initialFocus === 'undefined' && 52 | shouldSelfFocus 53 | ) { 54 | state.focusTrapOptions.initialFocus = element; 55 | } 56 | 57 | // Private to allow mocking FocusTrap in tests 58 | let createFocusTrap = CreateFocusTrap; 59 | if (_createFocusTrap) { 60 | createFocusTrap = _createFocusTrap; 61 | } 62 | 63 | if (state.focusTrapOptions.returnFocusOnDeactivate !== false) { 64 | state.focusTrapOptions.returnFocusOnDeactivate = true; 65 | } 66 | 67 | state.focusTrap = createFocusTrap( 68 | typeof additionalElements !== 'undefined' 69 | ? [element, ...additionalElements] 70 | : element, 71 | state.focusTrapOptions, 72 | ); 73 | 74 | if (state.isActive) { 75 | state.focusTrap.activate(); 76 | } 77 | 78 | if (state.isPaused) { 79 | state.focusTrap.pause(); 80 | } 81 | }, 82 | 83 | updateModifier(state, { named: params }) { 84 | const focusTrapOptions = params.focusTrapOptions || {}; 85 | 86 | if (state.isActive && !params.isActive) { 87 | const { returnFocusOnDeactivate } = focusTrapOptions; 88 | const returnFocus = 89 | typeof returnFocusOnDeactivate === 'undefined' ? true : false; 90 | state.focusTrap.deactivate({ returnFocus }); 91 | } else if (!state.isActive && params.isActive) { 92 | state.focusTrap.activate(); 93 | } 94 | 95 | if (state.isPaused && !params.isPaused) { 96 | state.focusTrap.unpause(); 97 | } else if (!state.isPaused && params.isPaused) { 98 | state.focusTrap.pause(); 99 | } 100 | 101 | // Update state 102 | state.focusTrapOptions = focusTrapOptions; 103 | 104 | if (typeof params.isActive !== 'undefined') { 105 | state.isActive = params.isActive; 106 | } 107 | 108 | if (typeof params.isPaused !== 'undefined') { 109 | state.isPaused = params.isPaused; 110 | } 111 | }, 112 | 113 | destroyModifier({ focusTrap }) { 114 | focusTrap.deactivate(); 115 | }, 116 | }; 117 | }, 118 | class FocusTrapModifier {}, 119 | ); 120 | -------------------------------------------------------------------------------- /site/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const defaultTheme = require('tailwindcss/defaultTheme'); 2 | const { orange } = require('tailwindcss/colors'); 3 | 4 | // backdrop-filter backdrop-blur bg-opacity-30 5 | 6 | module.exports = { 7 | darkMode: 'class', 8 | 9 | purge: { 10 | enabled: process.env.PURGE_CSS === 'true', 11 | layers: ['base', 'utilities'], 12 | 13 | content: [ 14 | './app/index.html', 15 | './app/**/*.hbs', 16 | '../docs/**/*.md', 17 | './node_modules/**/*.hbs', 18 | '../node_modules/**/*.hbs' 19 | ], 20 | options: { 21 | safelist: [/^_/, /js-/] 22 | } 23 | }, 24 | 25 | theme: { 26 | extend: { 27 | fontFamily: { 28 | sans: ['Inter', ...defaultTheme.fontFamily.sans] 29 | }, 30 | colors: { 31 | gray: { 32 | 1000: '#12161f' 33 | }, 34 | primary: orange, 35 | brand: '#f97316' 36 | }, 37 | zIndex: { 38 | 1: '1' 39 | }, 40 | maxHeight: { 41 | '(screen-16)': 'calc(100vh - 4rem)' 42 | }, 43 | typography: (theme) => ({ 44 | DEFAULT: { 45 | css: { 46 | maxWidth: 'none', 47 | color: theme('colors.gray.500'), 48 | fontFamily: ['Inter', ...defaultTheme.fontFamily.sans].join(', '), 49 | a: { 50 | color: theme('colors.primary.700'), 51 | fontWeight: theme('fontWeight.medium'), 52 | textDecoration: 'none' 53 | }, 54 | 'a code': { 55 | color: 'inherit', 56 | fontWeight: 'inherit' 57 | }, 58 | strong: { 59 | color: theme('colors.gray.900'), 60 | fontWeight: theme('fontWeight.medium') 61 | }, 62 | 'a strong': { 63 | color: 'inherit', 64 | fontWeight: 'inherit' 65 | }, 66 | code: { 67 | fontWeight: theme('fontWeight.normal'), 68 | padding: theme('padding.1'), 69 | backgroundColor: theme('colors.gray.100'), 70 | borderRadius: theme('borderRadius.sm') 71 | }, 72 | 'code::before': null, 73 | 'code::after': null 74 | } 75 | }, 76 | light: { 77 | css: [ 78 | { 79 | color: theme('colors.gray.300'), 80 | '[class~="lead"]': { 81 | color: theme('colors.gray.200') 82 | }, 83 | a: { 84 | color: theme('colors.white') 85 | }, 86 | strong: { 87 | color: theme('colors.white') 88 | }, 89 | 'ol > li::before': { 90 | color: theme('colors.gray.400') 91 | }, 92 | 'ul > li::before': { 93 | backgroundColor: theme('colors.gray.600') 94 | }, 95 | hr: { 96 | borderColor: theme('colors.gray.200') 97 | }, 98 | blockquote: { 99 | color: theme('colors.gray.200'), 100 | borderLeftColor: theme('colors.gray.600') 101 | }, 102 | h1: { 103 | color: theme('colors.white') 104 | }, 105 | h2: { 106 | color: theme('colors.white') 107 | }, 108 | h3: { 109 | color: theme('colors.white') 110 | }, 111 | h4: { 112 | color: theme('colors.white') 113 | }, 114 | 'figure figcaption': { 115 | color: theme('colors.gray.400') 116 | }, 117 | code: { 118 | backgroundColor: theme('colors.gray.900'), 119 | color: theme('colors.white') 120 | }, 121 | 'a code': { 122 | color: theme('colors.white') 123 | }, 124 | pre: { 125 | color: theme('colors.gray.200'), 126 | backgroundColor: theme('colors.gray.900') 127 | }, 128 | thead: { 129 | color: theme('colors.white'), 130 | borderBottomColor: theme('colors.gray.400') 131 | }, 132 | 'tbody tr': { 133 | borderBottomColor: theme('colors.gray.600') 134 | } 135 | } 136 | ] 137 | } 138 | }) 139 | }, 140 | frontile: () => { 141 | return { 142 | overlays: { 143 | config: { 144 | backdropColor: 'rgba(17,24,39, 0.5)' 145 | }, 146 | 147 | extend: { 148 | overlay: { 149 | parts: { 150 | backdrop: { 151 | backdropFilter: 'blur(8px)' 152 | } 153 | } 154 | } 155 | } 156 | } 157 | }; 158 | } 159 | }, 160 | variants: { 161 | extend: { 162 | typography: ['dark'], 163 | boxShadow: ['focus-visible'], 164 | ringWidth: ['focus-visible'] 165 | } 166 | }, 167 | plugins: [ 168 | require('@tailwindcss/typography'), 169 | require('@frontile/core/tailwind'), 170 | require('@frontile/overlays/tailwind') 171 | ] 172 | }; 173 | -------------------------------------------------------------------------------- /site/app/styles/highlight.css: -------------------------------------------------------------------------------- 1 | /*! 2 | Theme: GitHub Dark 3 | Description: Dark theme as seen on github.com 4 | Author: github.com 5 | Maintainer: @Hirse 6 | Updated: 2021-05-15 7 | Outdated base version: https://github.com/primer/github-syntax-dark 8 | Current colors taken from GitHub's CSS 9 | */ 10 | 11 | .hljs { 12 | color: #c9d1d9; 13 | } 14 | 15 | .hljs-doctag, 16 | .hljs-keyword, 17 | .hljs-meta .hljs-keyword, 18 | .hljs-template-tag, 19 | .hljs-template-variable, 20 | .hljs-type, 21 | .hljs-variable.language_ { 22 | /* prettylights-syntax-keyword */ 23 | color: #ff7b72; 24 | } 25 | 26 | .hljs-title, 27 | .hljs-title.class_, 28 | .hljs-title.class_.inherited__, 29 | .hljs-title.function_ { 30 | /* prettylights-syntax-entity */ 31 | color: #d2a8ff; 32 | } 33 | 34 | .hljs-attr, 35 | .hljs-attribute, 36 | .hljs-literal, 37 | .hljs-meta, 38 | .hljs-number, 39 | .hljs-operator, 40 | .hljs-variable, 41 | .hljs-selector-attr, 42 | .hljs-selector-class, 43 | .hljs-selector-id { 44 | /* prettylights-syntax-constant */ 45 | color: #79c0ff; 46 | } 47 | 48 | .hljs-regexp, 49 | .hljs-string, 50 | .hljs-meta .hljs-string { 51 | /* prettylights-syntax-string */ 52 | color: #a5d6ff; 53 | } 54 | 55 | .hljs-built_in, 56 | .hljs-symbol { 57 | /* prettylights-syntax-variable */ 58 | color: #ffa657; 59 | } 60 | 61 | .hljs-comment, 62 | .hljs-code, 63 | .hljs-formula { 64 | /* prettylights-syntax-comment */ 65 | color: #8b949e; 66 | } 67 | 68 | .hljs-name, 69 | .hljs-quote, 70 | .hljs-selector-tag, 71 | .hljs-selector-pseudo { 72 | /* prettylights-syntax-entity-tag */ 73 | color: #7ee787; 74 | } 75 | 76 | .hljs-subst { 77 | /* prettylights-syntax-storage-modifier-import */ 78 | color: #c9d1d9; 79 | } 80 | 81 | .hljs-section { 82 | /* prettylights-syntax-markup-heading */ 83 | color: #1f6feb; 84 | font-weight: bold; 85 | } 86 | 87 | .hljs-bullet { 88 | /* prettylights-syntax-markup-list */ 89 | color: #f2cc60; 90 | } 91 | 92 | .hljs-emphasis { 93 | /* prettylights-syntax-markup-italic */ 94 | color: #c9d1d9; 95 | font-style: italic; 96 | } 97 | 98 | .hljs-strong { 99 | /* prettylights-syntax-markup-bold */ 100 | color: #c9d1d9; 101 | font-weight: bold; 102 | } 103 | 104 | .hljs-addition { 105 | /* prettylights-syntax-markup-inserted */ 106 | color: #aff5b4; 107 | background-color: #033a16; 108 | } 109 | 110 | .hljs-deletion { 111 | /* prettylights-syntax-markup-deleted */ 112 | color: #ffdcd7; 113 | background-color: #67060c; 114 | } 115 | 116 | .hljs-char.escape_, 117 | .hljs-link, 118 | .hljs-params, 119 | .hljs-property, 120 | .hljs-punctuation, 121 | .hljs-tag { 122 | /* purposely ignored */ 123 | } 124 | 125 | .prose code.code-transparent { 126 | background-color: transparent; 127 | padding: 0; 128 | } 129 | 130 | .hljs-light-theme { 131 | .hljs { 132 | color: #24292e; 133 | } 134 | 135 | .hljs-doctag, 136 | .hljs-keyword, 137 | .hljs-meta .hljs-keyword, 138 | .hljs-template-tag, 139 | .hljs-template-variable, 140 | .hljs-type, 141 | .hljs-variable.language_ { 142 | /* prettylights-syntax-keyword */ 143 | color: #d73a49; 144 | } 145 | 146 | .hljs-title, 147 | .hljs-title.class_, 148 | .hljs-title.class_.inherited__, 149 | .hljs-title.function_ { 150 | /* prettylights-syntax-entity */ 151 | color: #6f42c1; 152 | } 153 | 154 | .hljs-attr, 155 | .hljs-attribute, 156 | .hljs-literal, 157 | .hljs-meta, 158 | .hljs-number, 159 | .hljs-operator, 160 | .hljs-variable, 161 | .hljs-selector-attr, 162 | .hljs-selector-class, 163 | .hljs-selector-id { 164 | /* prettylights-syntax-constant */ 165 | color: #005cc5; 166 | } 167 | 168 | .hljs-regexp, 169 | .hljs-string, 170 | .hljs-meta .hljs-string { 171 | /* prettylights-syntax-string */ 172 | color: #032f62; 173 | } 174 | 175 | .hljs-built_in, 176 | .hljs-symbol { 177 | /* prettylights-syntax-variable */ 178 | color: #e36209; 179 | } 180 | 181 | .hljs-comment, 182 | .hljs-code, 183 | .hljs-formula { 184 | /* prettylights-syntax-comment */ 185 | color: #6a737d; 186 | } 187 | 188 | .hljs-name, 189 | .hljs-quote, 190 | .hljs-selector-tag, 191 | .hljs-selector-pseudo { 192 | /* prettylights-syntax-entity-tag */ 193 | color: #22863a; 194 | } 195 | 196 | .hljs-subst { 197 | /* prettylights-syntax-storage-modifier-import */ 198 | color: #24292e; 199 | } 200 | 201 | .hljs-section { 202 | /* prettylights-syntax-markup-heading */ 203 | color: #005cc5; 204 | font-weight: bold; 205 | } 206 | 207 | .hljs-bullet { 208 | /* prettylights-syntax-markup-list */ 209 | color: #735c0f; 210 | } 211 | 212 | .hljs-emphasis { 213 | /* prettylights-syntax-markup-italic */ 214 | color: #24292e; 215 | font-style: italic; 216 | } 217 | 218 | .hljs-strong { 219 | /* prettylights-syntax-markup-bold */ 220 | color: #24292e; 221 | font-weight: bold; 222 | } 223 | 224 | .hljs-addition { 225 | /* prettylights-syntax-markup-inserted */ 226 | color: #22863a; 227 | background-color: #f0fff4; 228 | } 229 | 230 | .hljs-deletion { 231 | /* prettylights-syntax-markup-deleted */ 232 | color: #b31d28; 233 | background-color: #ffeef0; 234 | } 235 | 236 | .hljs-char.escape_, 237 | .hljs-link, 238 | .hljs-params, 239 | .hljs-property, 240 | .hljs-punctuation, 241 | .hljs-tag { 242 | /* purposely ignored */ 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /packages/test-app/tests/integration/modifiers/focus-trap-test.js: -------------------------------------------------------------------------------- 1 | import { hbs } from 'ember-cli-htmlbars'; 2 | import { module, test } from 'qunit'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { render, find } from '@ember/test-helpers'; 5 | import sinon from 'sinon'; 6 | import { focusTrap } from 'ember-focus-trap'; 7 | let noop = () => { 8 | // empty 9 | }; 10 | 11 | module('Integration | Modifier | focus-trap', function (hooks) { 12 | setupRenderingTest(hooks); 13 | 14 | hooks.beforeEach(function () { 15 | const instance = { 16 | activate: sinon.fake(), 17 | deactivate: sinon.fake(), 18 | pause: sinon.fake(), 19 | unpause: sinon.fake(), 20 | }; 21 | 22 | const fakeFocusTrap = sinon.fake.returns(instance); 23 | 24 | this.set('instance', instance); 25 | this.set('fakeFocusTrap', fakeFocusTrap); 26 | this.set('noop', noop); 27 | }); 28 | 29 | hooks.afterEach(() => { 30 | sinon.restore(); 31 | }); 32 | 33 | module('installModifier', function () { 34 | test('default activation (explicit usage (no global resolver))', async function (assert) { 35 | this.setProperties({ focusTrap }); 36 | await render( 37 | hbs`
44 | 45 |
`, 46 | ); 47 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 48 | 49 | assert.ok( 50 | this.fakeFocusTrap.calledWithExactly(find('[data-test]'), { 51 | onDeactivate: noop, 52 | returnFocusOnDeactivate: true, 53 | }), 54 | 'should have called with the element and options', 55 | ); 56 | assert.equal(this.instance.activate.callCount, 1, 'should have called'); 57 | }); 58 | 59 | test('default activation ', async function (assert) { 60 | await render( 61 | hbs`
68 | 69 |
`, 70 | ); 71 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 72 | 73 | assert.ok( 74 | this.fakeFocusTrap.calledWithExactly(find('[data-test]'), { 75 | onDeactivate: noop, 76 | returnFocusOnDeactivate: true, 77 | }), 78 | 'should have called with the element and options', 79 | ); 80 | assert.equal(this.instance.activate.callCount, 1, 'should have called'); 81 | }); 82 | 83 | test('activation with additional elements', async function (assert) { 84 | await render( 85 | hbs`
93 | 94 |
`, 95 | ); 96 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 97 | 98 | assert.ok( 99 | this.fakeFocusTrap.calledWithExactly( 100 | [find('[data-test]'), '#foo', '#bar'], 101 | { 102 | onDeactivate: noop, 103 | returnFocusOnDeactivate: true, 104 | }, 105 | ), 106 | 'should have called with the element and options', 107 | ); 108 | assert.equal(this.instance.activate.callCount, 1, 'should have called'); 109 | }); 110 | 111 | test('activation with initialFocus as selector', async function (assert) { 112 | await render( 113 | hbs`
123 | 124 |
`, 125 | ); 126 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 127 | assert.ok( 128 | this.fakeFocusTrap.calledWithExactly(find('[data-test]'), { 129 | onDeactivate: noop, 130 | initialFocus: '#initial-focusee', 131 | returnFocusOnDeactivate: true, 132 | }), 133 | 'should have called with the element and options', 134 | ); 135 | }); 136 | 137 | test('when passing shouldSelfFocus', async function (assert) { 138 | await render( 139 | hbs`
146 | 147 |
`, 148 | ); 149 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 150 | 151 | assert.ok( 152 | this.fakeFocusTrap.calledWithExactly(find('[data-test]'), { 153 | initialFocus: find('[data-test]'), 154 | returnFocusOnDeactivate: true, 155 | }), 156 | 'should have called with the element and options', 157 | ); 158 | assert.equal(this.instance.activate.callCount, 1, 'should have called'); 159 | }); 160 | 161 | test('when passing isActive as false', async function (assert) { 162 | await render( 163 | hbs`
170 | 171 |
`, 172 | ); 173 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 174 | assert.ok( 175 | this.fakeFocusTrap.calledWithExactly(find('[data-test]'), { 176 | returnFocusOnDeactivate: true, 177 | }), 178 | 'should have called with the element and options', 179 | ); 180 | assert.equal( 181 | this.instance.activate.callCount, 182 | 0, 183 | 'should not have called', 184 | ); 185 | }); 186 | 187 | test('when passign isActive as true', async function (assert) { 188 | await render( 189 | hbs`
196 | 197 |
`, 198 | ); 199 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 200 | assert.equal( 201 | this.instance.activate.callCount, 202 | 1, 203 | 'should have called activate', 204 | ); 205 | }); 206 | 207 | test('when passign isPaused as true', async function (assert) { 208 | await render( 209 | hbs`
216 | 217 |
`, 218 | ); 219 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 220 | assert.equal( 221 | this.instance.pause.callCount, 222 | 1, 223 | 'should have called activate', 224 | ); 225 | }); 226 | }); 227 | 228 | module('updateModifier', function () { 229 | test('it acivates when isActive is updated from false to true', async function (assert) { 230 | this.set('isActive', false); 231 | await render( 232 | hbs`
239 | 240 |
`, 241 | ); 242 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 243 | assert.equal( 244 | this.instance.activate.callCount, 245 | 0, 246 | 'should not have called', 247 | ); 248 | 249 | this.set('isActive', true); 250 | 251 | assert.equal( 252 | this.instance.activate.callCount, 253 | 1, 254 | 'should have called activate', 255 | ); 256 | }); 257 | 258 | test('it deactivates when isActive is updated from true to false', async function (assert) { 259 | this.set('isActive', true); 260 | await render( 261 | hbs`
268 | 269 |
`, 270 | ); 271 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 272 | assert.equal( 273 | this.instance.deactivate.callCount, 274 | 0, 275 | 'should not have called diactive', 276 | ); 277 | 278 | this.set('isActive', false); 279 | 280 | assert.equal( 281 | this.instance.deactivate.callCount, 282 | 1, 283 | 'should have called diactive', 284 | ); 285 | }); 286 | 287 | test('it deactivates when isActive is updated from true to false and back again', async function (assert) { 288 | this.set('isActive', true); 289 | await render( 290 | hbs`
297 | 298 |
`, 299 | ); 300 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 301 | assert.equal( 302 | this.instance.deactivate.callCount, 303 | 0, 304 | 'should not have called deactivate', 305 | ); 306 | 307 | this.set('isActive', false); 308 | 309 | assert.equal( 310 | this.instance.deactivate.callCount, 311 | 1, 312 | 'should have called deactivate', 313 | ); 314 | 315 | this.set('isActive', true); 316 | 317 | assert.equal( 318 | this.instance.activate.callCount, 319 | 2, 320 | 'should have called activate', 321 | ); 322 | }); 323 | 324 | test('it respects returnFocusOnDeactivate default option', async function (assert) { 325 | this.set('isActive', true); 326 | await render( 327 | hbs`
334 | 335 |
`, 336 | ); 337 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 338 | assert.ok( 339 | this.fakeFocusTrap.calledWithExactly(find('[data-test]'), { 340 | returnFocusOnDeactivate: true, 341 | }), 342 | 'should have called with the element and options', 343 | ); 344 | assert.equal( 345 | this.instance.deactivate.callCount, 346 | 0, 347 | 'should not have called diactive', 348 | ); 349 | 350 | this.set('isActive', false); 351 | 352 | assert.ok( 353 | this.instance.deactivate.calledWithExactly({ returnFocus: true }), 354 | 'should have called diactive with options', 355 | ); 356 | }); 357 | 358 | test('it respects returnFocusOnDeactivate when false', async function (assert) { 359 | this.set('isActive', true); 360 | await render( 361 | hbs`
369 | 370 |
`, 371 | ); 372 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 373 | assert.ok( 374 | this.fakeFocusTrap.calledWithExactly(find('[data-test]'), { 375 | returnFocusOnDeactivate: false, 376 | }), 377 | 'should have called with the element and options', 378 | ); 379 | assert.equal( 380 | this.instance.deactivate.callCount, 381 | 0, 382 | 'should not have called diactive', 383 | ); 384 | 385 | this.set('isActive', false); 386 | 387 | assert.ok( 388 | this.instance.deactivate.calledWithExactly({ returnFocus: false }), 389 | 'should have called diactive with options', 390 | ); 391 | }); 392 | 393 | test('it pauses when isPaused is updated from false to true', async function (assert) { 394 | this.set('isPaused', false); 395 | await render( 396 | hbs`
404 | 405 |
`, 406 | ); 407 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 408 | assert.equal(this.instance.pause.callCount, 0, 'should not have called'); 409 | 410 | this.set('isPaused', true); 411 | 412 | assert.equal( 413 | this.instance.pause.callCount, 414 | 1, 415 | 'should have called pause', 416 | ); 417 | }); 418 | 419 | test('it unpauses when isPaused is updated from true to false', async function (assert) { 420 | this.set('isPaused', true); 421 | await render( 422 | hbs`
429 | 430 |
`, 431 | ); 432 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 433 | assert.equal( 434 | this.instance.unpause.callCount, 435 | 0, 436 | 'should not have called unpause', 437 | ); 438 | 439 | this.set('isPaused', false); 440 | 441 | assert.equal( 442 | this.instance.unpause.callCount, 443 | 1, 444 | 'should have called unpause', 445 | ); 446 | }); 447 | 448 | test('it unpauses when isPaused is updated from true to false and back again', async function (assert) { 449 | this.set('isPaused', true); 450 | await render( 451 | hbs`
458 | 459 |
`, 460 | ); 461 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 462 | assert.equal( 463 | this.instance.unpause.callCount, 464 | 0, 465 | 'should not have called unpause', 466 | ); 467 | 468 | this.set('isPaused', false); 469 | 470 | assert.equal( 471 | this.instance.unpause.callCount, 472 | 1, 473 | 'should have called unpause', 474 | ); 475 | 476 | this.set('isPaused', true); 477 | 478 | assert.equal( 479 | this.instance.pause.callCount, 480 | 2, 481 | 'should not have called pause', 482 | ); 483 | }); 484 | }); 485 | 486 | module('destroyModifier', function () { 487 | test('it diactives when removing focus-trap element', async function (assert) { 488 | this.set('isEnabled', true); 489 | 490 | await render( 491 | hbs`{{#if this.isEnabled}} 492 |
499 | 500 |
501 | {{/if}}`, 502 | ); 503 | assert.equal(this.fakeFocusTrap.callCount, 1, 'should have called once'); 504 | assert.equal( 505 | this.instance.deactivate.callCount, 506 | 0, 507 | 'should not have called deactivate', 508 | ); 509 | 510 | this.set('isEnabled', false); 511 | 512 | assert.equal( 513 | this.instance.deactivate.callCount, 514 | 1, 515 | 'should have called deactivate', 516 | ); 517 | }); 518 | }); 519 | }); 520 | --------------------------------------------------------------------------------