├── docs ├── app │ ├── components │ │ ├── .gitkeep │ │ ├── snippets │ │ │ ├── installation-1.js │ │ │ ├── installation-1.css.txt │ │ │ ├── test-helpers-1-js-snippet.js │ │ │ ├── installation-1.scss │ │ │ ├── installation-0.gts │ │ │ ├── installation-2-snippet.js │ │ │ ├── test-helpers-2-js-snippet.js │ │ │ ├── test-helpers-3-js-snippet.js │ │ │ ├── how-to-use-it-1.gts │ │ │ ├── styles-1.scss │ │ │ ├── disabled-1.gts │ │ │ ├── position-2.gts │ │ │ ├── position-3.gts │ │ │ ├── animations-1.gts │ │ │ ├── how-to-use-it-3.gts │ │ │ ├── overlays-1.gts │ │ │ ├── how-to-use-it-2.gts │ │ │ ├── styles-2.scss │ │ │ ├── no-trigger-2.gts │ │ │ ├── installation-2.js.txt │ │ │ ├── how-to-use-it-2-css.scss │ │ │ ├── no-trigger-1.gts │ │ │ ├── how-to-use-it-3-css.scss │ │ │ ├── trigger-events-2.gts │ │ │ ├── trigger-events-0.gts │ │ │ ├── scrollable-container-1.gts │ │ │ ├── animations-1-css.scss │ │ │ ├── trigger-events-1.gts │ │ │ ├── dropdown-events-1.gts │ │ │ ├── custom-position-1.gts │ │ │ ├── trigger-events-4.gts │ │ │ ├── dropdown-events-2.gts │ │ │ ├── dropdown-events-3.gts │ │ │ ├── trigger-events-3.gts │ │ │ ├── custom-position-2.gts │ │ │ └── content-events-1.gts │ │ ├── code-block.gts │ │ └── code-inline.gts │ ├── helpers │ │ └── .gitkeep │ ├── models │ │ └── .gitkeep │ ├── routes │ │ ├── .gitkeep │ │ └── public-pages │ │ │ └── docs │ │ │ ├── migrate-7-0-to-8-0.ts │ │ │ └── migrate-8-0-to-9-0.ts │ ├── controllers │ │ └── .gitkeep │ ├── styles │ │ ├── components │ │ │ ├── cookbook.scss │ │ │ ├── code-block.scss │ │ │ ├── link-to-other-version.scss │ │ │ ├── main-footer.scss │ │ │ ├── docs.scss │ │ │ ├── spinners.scss │ │ │ ├── side-nav.scss │ │ │ ├── index.scss │ │ │ └── main-header.scss │ │ ├── demos │ │ │ ├── position.scss │ │ │ ├── scrollable-container.scss │ │ │ └── content-mouse-enter-leave.scss │ │ ├── _layout.scss │ │ ├── docs │ │ │ └── code-example.scss │ │ ├── _utilities.scss │ │ ├── app.scss │ │ ├── look-and-feels │ │ │ ├── material.scss │ │ │ ├── animations.scss │ │ │ └── bootstrap.scss │ │ ├── _variables.scss │ │ ├── _brandification.scss │ │ └── _base.scss │ ├── templates │ │ ├── application.gts │ │ ├── public-pages │ │ │ ├── cookbook.gts │ │ │ ├── docs │ │ │ │ ├── disabled.gts │ │ │ │ ├── styles.gts │ │ │ │ ├── overlays.gts │ │ │ │ ├── migrate-7-0-to-8-0.gts │ │ │ │ ├── animations.gts │ │ │ │ ├── test-helpers.gts │ │ │ │ ├── content-events.gts │ │ │ │ ├── installation.gts │ │ │ │ ├── index.gts │ │ │ │ └── dropdown-events.gts │ │ │ └── cookbook │ │ │ │ └── index.gts │ │ ├── helpers-testing.gts │ │ ├── scrolling-container.gts │ │ └── public-pages.gts │ ├── deprecation-workflow.ts │ ├── app.ts │ ├── config │ │ └── environment.ts │ └── router.ts ├── tests │ ├── unit │ │ ├── .gitkeep │ │ └── routes │ │ │ └── public-pages │ │ │ └── docs │ │ │ ├── migrate-7-0-to-8-0-test.ts │ │ │ └── migrate-8-0-to-9-0-test.ts │ ├── integration │ │ └── .gitkeep │ ├── test-helper.ts │ ├── index.html │ └── helpers │ │ └── index.ts ├── .watchmanconfig ├── types │ └── global.d.ts ├── public │ ├── robots.txt │ ├── lts.png │ ├── ember_logo.png │ └── some-image.gif ├── .stylelintignore ├── .stylelintrc.js ├── config │ ├── targets.js │ ├── optional-features.json │ ├── ember-cli-update.json │ └── environment.js ├── .prettierignore ├── .gitignore ├── .prettierrc.js ├── vite.config.mjs ├── .editorconfig ├── .template-lintrc.js ├── tsconfig.json ├── ember-cli-build.js ├── .ember-cli ├── testem.js ├── babel.config.cjs ├── README.md ├── 404.html ├── index.html ├── eslint.config.mjs └── package.json ├── test-app ├── app │ ├── models │ │ └── .gitkeep │ ├── routes │ │ ├── .gitkeep │ │ └── shadow-root.ts │ ├── components │ │ ├── .gitkeep │ │ ├── my-custom-content.gts │ │ ├── my-custom-trigger.gts │ │ ├── shadow.gts │ │ └── shadow-root.gts │ ├── controllers │ │ ├── .gitkeep │ │ └── application.ts │ ├── helpers │ │ └── .gitkeep │ ├── styles │ │ └── app.scss │ ├── router.ts │ ├── templates │ │ ├── application.gts │ │ └── shadow-root.gts │ ├── config │ │ └── environment.d.ts │ ├── index.html │ ├── deprecation-workflow.ts │ ├── app.ts │ └── instance-initializers │ │ └── shadow-root.ts ├── tests │ ├── unit │ │ ├── .gitkeep │ │ ├── routes │ │ │ └── shadow-root-test.ts │ │ └── utils │ │ │ ├── calculate-position-test.ts │ │ │ └── scroll-helpers-test.ts │ ├── integration │ │ ├── .gitkeep │ │ └── components │ │ │ └── basic-dropdown-wormhole-test.gts │ ├── test-helper.ts │ ├── index.html │ └── helpers │ │ └── index.ts ├── types │ └── global.d.ts ├── .watchmanconfig ├── public │ └── robots.txt ├── .stylelintignore ├── .stylelintrc.js ├── .template-lintrc.js ├── .prettierignore ├── config │ ├── targets.js │ ├── optional-features.json │ ├── ember-cli-update.json │ ├── environment.js │ └── ember-try.js ├── .gitignore ├── .prettierrc.js ├── .editorconfig ├── ember-cli-build.js ├── tsconfig.json ├── testem.js ├── .ember-cli ├── .github │ └── workflows │ │ └── ci.yml ├── README.md ├── eslint.config.mjs └── package.json ├── CNAME ├── ember-basic-dropdown ├── src │ ├── .gitkeep │ ├── styles.ts │ ├── types │ │ └── global.d.ts │ ├── config.ts │ ├── test-support │ │ └── helpers.ts │ ├── utils │ │ └── has-moved.ts │ ├── template-registry.ts │ ├── types.ts │ └── components │ │ └── basic-dropdown-wormhole.gts ├── _index.scss ├── ember-basic-dropdown.scss ├── ember-basic-dropdown.less ├── .template-lintrc.cjs ├── addon-main.cjs ├── .prettierignore ├── less │ ├── variables.less │ └── base.less ├── scss │ ├── variables.scss │ └── base.scss ├── .prettierrc.cjs ├── .gitignore ├── babel.config.json ├── LICENSE.md ├── tsconfig.json ├── eslint.config.mjs └── rollup.config.mjs ├── LICENSE.md ├── README.md ├── CHANGELOG.md ├── pnpm-workspace.yaml ├── .prettierrc.cjs ├── .github ├── dependabot.yml └── workflows │ ├── push-dist.yml │ ├── docs.yml │ └── ci.yml ├── .vscode └── settings.json ├── .npmrc ├── .gitignore ├── .editorconfig ├── config └── ember-cli-update.json ├── CONTRIBUTING.md └── package.json /docs/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /CNAME: -------------------------------------------------------------------------------- 1 | ember-basic-dropdown.com -------------------------------------------------------------------------------- /docs/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ember-basic-dropdown/src/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test-app/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ember-basic-dropdown/LICENSE.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ember-basic-dropdown/README.md -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ember-basic-dropdown/CHANGELOG.md -------------------------------------------------------------------------------- /docs/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["dist"] 3 | } 4 | -------------------------------------------------------------------------------- /docs/types/global.d.ts: -------------------------------------------------------------------------------- 1 | import '@glint/ember-tsc/types'; 2 | -------------------------------------------------------------------------------- /test-app/types/global.d.ts: -------------------------------------------------------------------------------- 1 | import '@glint/ember-tsc/types'; 2 | -------------------------------------------------------------------------------- /test-app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["dist"] 3 | } 4 | -------------------------------------------------------------------------------- /ember-basic-dropdown/_index.scss: -------------------------------------------------------------------------------- 1 | @use "./ember-basic-dropdown.scss"; 2 | -------------------------------------------------------------------------------- /ember-basic-dropdown/ember-basic-dropdown.scss: -------------------------------------------------------------------------------- 1 | @use "scss/base.scss"; 2 | -------------------------------------------------------------------------------- /ember-basic-dropdown/src/styles.ts: -------------------------------------------------------------------------------- 1 | import './vendor/ember-basic-dropdown.css'; 2 | -------------------------------------------------------------------------------- /ember-basic-dropdown/src/types/global.d.ts: -------------------------------------------------------------------------------- 1 | import '@glint/ember-tsc/types'; 2 | -------------------------------------------------------------------------------- /test-app/app/styles/app.scss: -------------------------------------------------------------------------------- 1 | // Basic dropdown 2 | @use "ember-basic-dropdown"; 3 | -------------------------------------------------------------------------------- /docs/app/components/snippets/installation-1.js: -------------------------------------------------------------------------------- 1 | import 'ember-basic-dropdown/styles'; 2 | -------------------------------------------------------------------------------- /docs/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'ember-basic-dropdown' 3 | - 'test-app' 4 | - 'docs' 5 | -------------------------------------------------------------------------------- /docs/app/styles/components/cookbook.scss: -------------------------------------------------------------------------------- 1 | // Cookbook 2 | .cookbook { 3 | @extend %wrapper; 4 | } 5 | -------------------------------------------------------------------------------- /docs/public/lts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cibernox/ember-basic-dropdown/HEAD/docs/public/lts.png -------------------------------------------------------------------------------- /docs/.stylelintignore: -------------------------------------------------------------------------------- 1 | # unconventional files 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | -------------------------------------------------------------------------------- /docs/public/ember_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cibernox/ember-basic-dropdown/HEAD/docs/public/ember_logo.png -------------------------------------------------------------------------------- /docs/public/some-image.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cibernox/ember-basic-dropdown/HEAD/docs/public/some-image.gif -------------------------------------------------------------------------------- /ember-basic-dropdown/ember-basic-dropdown.less: -------------------------------------------------------------------------------- 1 | @import "./less/variables.less"; 2 | @import "./less/base.less"; 3 | -------------------------------------------------------------------------------- /test-app/.stylelintignore: -------------------------------------------------------------------------------- 1 | # unconventional files 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | -------------------------------------------------------------------------------- /docs/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: ['stylelint-config-standard'], 5 | }; 6 | -------------------------------------------------------------------------------- /docs/app/components/snippets/installation-1.css.txt: -------------------------------------------------------------------------------- 1 | @import 'ember-basic-dropdown/vendor/ember-basic-dropdown.css'; 2 | -------------------------------------------------------------------------------- /test-app/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: ['stylelint-config-standard'], 5 | }; 6 | -------------------------------------------------------------------------------- /ember-basic-dropdown/.template-lintrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /test-app/app/routes/shadow-root.ts: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class ShadowRootRoute extends Route {} 4 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | plugins: ['prettier-plugin-ember-template-tag'], 5 | singleQuote: true, 6 | }; 7 | -------------------------------------------------------------------------------- /docs/app/components/snippets/test-helpers-1-js-snippet.js: -------------------------------------------------------------------------------- 1 | import { 2 | clickTrigger, 3 | tapTrigger, 4 | } from 'ember-basic-dropdown/test-support/helpers'; 5 | -------------------------------------------------------------------------------- /ember-basic-dropdown/addon-main.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { addonV1Shim } = require('@embroider/addon-shim'); 4 | module.exports = addonV1Shim(__dirname); 5 | -------------------------------------------------------------------------------- /docs/app/routes/public-pages/docs/migrate-7-0-to-8-0.ts: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class PublicPagesDocsMigrate70To80Route extends Route {} 4 | -------------------------------------------------------------------------------- /docs/app/routes/public-pages/docs/migrate-8-0-to-9-0.ts: -------------------------------------------------------------------------------- 1 | import Route from '@ember/routing/route'; 2 | 3 | export default class PublicPagesDocsMigrate80To90Route extends Route {} 4 | -------------------------------------------------------------------------------- /docs/app/components/snippets/installation-1.scss: -------------------------------------------------------------------------------- 1 | // ember-basic-dropdown v8.x 2 | @import "ember-basic-dropdown"; 3 | 4 | // ember-basic-dropdown v9.x+ 5 | @use "ember-basic-dropdown"; 6 | -------------------------------------------------------------------------------- /docs/app/components/snippets/installation-0.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdownWormhole from 'ember-basic-dropdown/components/basic-dropdown-wormhole'; 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/app/components/snippets/installation-2-snippet.js: -------------------------------------------------------------------------------- 1 | // ember-cli-build.js 2 | let app = new EmberApp({ 3 | lessOptions: { 4 | paths: ['node_modules/ember-basic-dropdown/'], 5 | }, 6 | }); 7 | -------------------------------------------------------------------------------- /docs/app/components/snippets/test-helpers-2-js-snippet.js: -------------------------------------------------------------------------------- 1 | clickTrigger(); 2 | assert.equal( 3 | document.querySelectorAll('.ember-basic-dropdown-content').length, 4 | 1, 5 | 'The content is shown now', 6 | ); 7 | -------------------------------------------------------------------------------- /docs/app/components/snippets/test-helpers-3-js-snippet.js: -------------------------------------------------------------------------------- 1 | tapTrigger(); 2 | assert.equal( 3 | document.querySelectorAll('.ember-basic-dropdown-content').length, 4 | 1, 5 | 'The content is shown now', 6 | ); 7 | -------------------------------------------------------------------------------- /ember-basic-dropdown/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | /declarations/ 7 | /vendor/ 8 | 9 | # misc 10 | /coverage/ 11 | 12 | CHANGELOG.md -------------------------------------------------------------------------------- /docs/app/templates/application.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdownWormhole from 'ember-basic-dropdown/components/basic-dropdown-wormhole'; 2 | 3 | 8 | -------------------------------------------------------------------------------- /test-app/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | rules: { 6 | 'no-inline-styles': false, 7 | 'no-positive-tabindex': false, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /test-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # misc 8 | /coverage/ 9 | !.* 10 | .*/ 11 | /pnpm-lock.yaml 12 | ember-cli-update.json 13 | *.html 14 | -------------------------------------------------------------------------------- /docs/app/styles/components/code-block.scss: -------------------------------------------------------------------------------- 1 | // Cookbook 2 | .code-block { 3 | background: #f5f2f0; 4 | padding: 1rem; 5 | 6 | pre { 7 | background: transparent !important; 8 | margin-bottom: 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/.prettierignore: -------------------------------------------------------------------------------- 1 | # Prettier is also run from each package, so the ignores here 2 | # protect against files that may not be within a package 3 | 4 | # misc 5 | !.* 6 | .*/ 7 | /pnpm-lock.yaml 8 | ember-cli-update.json 9 | *.html 10 | -------------------------------------------------------------------------------- /docs/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ember-basic-dropdown/less/variables.less: -------------------------------------------------------------------------------- 1 | @ember-basic-dropdown-content-background-color: #fff; 2 | @ember-basic-dropdown-content-z-index: 1000; 3 | @ember-basic-dropdown-overlay-background: rgba(0, 0, 0, 0.5); 4 | @ember-basic-dropdown-overlay-pointer-events: none; 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | time: "04:00" 8 | open-pull-requests-limit: 15 9 | labels: 10 | - dependencies 11 | versioning-strategy: increase-if-necessary -------------------------------------------------------------------------------- /docs/app/styles/components/link-to-other-version.scss: -------------------------------------------------------------------------------- 1 | .link-to-other-version { 2 | color: white; 3 | background-color: red; 4 | text-align: center; 5 | padding-left: 1rem; 6 | padding-right: 1rem; 7 | a { 8 | font-weight: bold; 9 | color: inherit; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /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 | # broccoli-debug 18 | /DEBUG/ 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.probe": [ 3 | "javascript", 4 | "typescript", 5 | "html", 6 | "markdown", 7 | "glimmer-js", 8 | "glimmer-ts" 9 | ], 10 | "eslint.validate": [ 11 | "javascript", 12 | "glimmer-ts", 13 | "glimmer-js" 14 | ] 15 | } -------------------------------------------------------------------------------- /docs/app/components/snippets/how-to-use-it-1.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 9 | -------------------------------------------------------------------------------- /docs/app/components/snippets/styles-1.scss: -------------------------------------------------------------------------------- 1 | $ember-basic-dropdown-content-background-color: #fff !default; 2 | $ember-basic-dropdown-content-z-index: 1000 !default; 3 | $ember-basic-dropdown-overlay-background: rgba(0, 0, 0, 0.5) !default; 4 | $ember-basic-dropdown-overlay-pointer-events: none !default; 5 | -------------------------------------------------------------------------------- /docs/app/styles/demos/position.scss: -------------------------------------------------------------------------------- 1 | .flex { 2 | display: flex; 3 | } 4 | 5 | .content-pull-right { 6 | justify-content: flex-end; 7 | } 8 | 9 | .content-bootstrap-feel [alt="tomster"] { 10 | height: 168px; 11 | width: 127px; 12 | max-width: 127px; 13 | min-width: 127px; 14 | } 15 | -------------------------------------------------------------------------------- /ember-basic-dropdown/scss/variables.scss: -------------------------------------------------------------------------------- 1 | $ember-basic-dropdown-content-background-color: #fff !default; 2 | $ember-basic-dropdown-content-z-index: 1000 !default; 3 | $ember-basic-dropdown-overlay-background: rgba(0, 0, 0, 0.5) !default; 4 | $ember-basic-dropdown-overlay-pointer-events: none !default; 5 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | /tmp/ 2 | 3 | # compiled output 4 | /dist/ 5 | /declarations/ 6 | 7 | # dependencies 8 | /node_modules/ 9 | 10 | # misc 11 | /.env* 12 | /.pnp* 13 | /.eslintcache 14 | /coverage/ 15 | /npm-debug.log* 16 | /testem.log 17 | /yarn-error.log 18 | 19 | # broccoli-debug 20 | /DEBUG/ 21 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # Docs: https://pnpm.io/npmrc 2 | # https://github.com/emberjs/rfcs/pull/907 3 | 4 | # we don't want addons to be bad citizens of the ecosystem 5 | auto-install-peers=false 6 | 7 | # we want true isolation, 8 | # if a dependency is not declared, we want an error 9 | resolve-peers-from-workspace-root=false 10 | -------------------------------------------------------------------------------- /docs/.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 | templateSingleQuote: false, 11 | }, 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /test-app/app/router.ts: -------------------------------------------------------------------------------- 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 | this.route('shadow-root'); 11 | }); 12 | -------------------------------------------------------------------------------- /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 | templateSingleQuote: false, 11 | }, 12 | }, 13 | ], 14 | }; 15 | -------------------------------------------------------------------------------- /ember-basic-dropdown/.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 | -------------------------------------------------------------------------------- /ember-basic-dropdown/.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 | 5 | # compiled output 6 | /dist 7 | /declarations 8 | /src/vendor 9 | 10 | # npm/pnpm/yarn pack output 11 | *.tgz 12 | -------------------------------------------------------------------------------- /test-app/tests/unit/routes/shadow-root-test.ts: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'test-app/tests/helpers'; 3 | 4 | module('Unit | Route | shadow-root', function (hooks) { 5 | setupTest(hooks); 6 | 7 | test('it exists', function (assert) { 8 | const route = this.owner.lookup('route:shadow-root'); 9 | assert.ok(route); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /test-app/tests/unit/utils/calculate-position-test.ts: -------------------------------------------------------------------------------- 1 | import calculatePosition from 'ember-basic-dropdown/utils/calculate-position'; 2 | import { module, test } from 'qunit'; 3 | 4 | module('Unit | Utility | Calculate Position', function () { 5 | test('calculatePosition is defined', function (assert) { 6 | assert.strictEqual(typeof calculatePosition, 'function', 'Not a function'); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /docs/app/styles/_layout.scss: -------------------------------------------------------------------------------- 1 | body, 2 | body > .ember-view { 3 | display: flex; 4 | min-height: 100vh; 5 | flex-direction: column; 6 | } 7 | 8 | .main-content { 9 | flex-grow: 1; 10 | display: flex; 11 | flex-direction: column; 12 | } 13 | .important-announcement { 14 | padding: 5px; 15 | color: #8a6d3b; 16 | background-color: #fcf8e3; 17 | border-top: 1px solid #faebcc; 18 | } 19 | -------------------------------------------------------------------------------- /docs/app/components/snippets/disabled-1.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 12 | -------------------------------------------------------------------------------- /test-app/app/components/my-custom-content.gts: -------------------------------------------------------------------------------- 1 | import type { BasicDropdownContentSignature } from 'ember-basic-dropdown/components/basic-dropdown-content'; 2 | import type { TemplateOnlyComponent } from '@ember/component/template-only'; 3 | 4 | export default satisfies TemplateOnlyComponent; 7 | -------------------------------------------------------------------------------- /test-app/app/components/my-custom-trigger.gts: -------------------------------------------------------------------------------- 1 | import type { BasicDropdownTriggerSignature } from 'ember-basic-dropdown/components/basic-dropdown-trigger'; 2 | import type { TemplateOnlyComponent } from '@ember/component/template-only'; 3 | 4 | export default satisfies TemplateOnlyComponent; 7 | -------------------------------------------------------------------------------- /docs/app/components/snippets/position-2.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 12 | -------------------------------------------------------------------------------- /docs/vite.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { extensions, classicEmberSupport, ember } from '@embroider/vite'; 3 | import { babel } from '@rollup/plugin-babel'; 4 | 5 | export default defineConfig({ 6 | plugins: [ 7 | classicEmberSupport(), 8 | ember(), 9 | // extra plugins here 10 | babel({ 11 | babelHelpers: 'runtime', 12 | extensions, 13 | }), 14 | ], 15 | }); 16 | -------------------------------------------------------------------------------- /docs/app/components/snippets/position-3.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 12 | -------------------------------------------------------------------------------- /test-app/app/templates/application.gts: -------------------------------------------------------------------------------- 1 | import RouteTemplate from 'ember-route-template'; 2 | import ShadowRoot from '../components/shadow-root'; 3 | import BasicDropdownWormhole from 'ember-basic-dropdown/components/basic-dropdown-wormhole'; 4 | 5 | export default RouteTemplate( 6 | , 13 | ); 14 | -------------------------------------------------------------------------------- /ember-basic-dropdown/src/config.ts: -------------------------------------------------------------------------------- 1 | export interface Config { 2 | destination?: string; 3 | rootElement?: string; 4 | } 5 | 6 | let _config: Config = {}; 7 | 8 | // This will be removed in next major, don't use this outside the package! 9 | let _configSet = false; 10 | 11 | export function setConfig(config: Config) { 12 | _config = config; 13 | _configSet = true; 14 | } 15 | 16 | export { _config as config, _configSet }; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # misc 7 | .env* 8 | .pnp* 9 | .pnpm-debug.log 10 | .sass-cache 11 | .eslintcache 12 | coverage/ 13 | npm-debug.log* 14 | yarn-error.log 15 | 16 | # ember-try 17 | /.node_modules.ember-try/ 18 | /package.json.ember-try 19 | /package-lock.json.ember-try 20 | /yarn.lock.ember-try 21 | /pnpm-lock.ember-try.yaml 22 | -------------------------------------------------------------------------------- /test-app/app/config/environment.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Type declarations for 3 | * import config from 'test-app/config/environment' 4 | */ 5 | declare const config: { 6 | environment: string; 7 | modulePrefix: string; 8 | podModulePrefix: string; 9 | locationType: 'history' | 'hash' | 'none'; 10 | rootURL: string; 11 | APP: Record; 12 | 'ember-basic-dropdown': Record; 13 | }; 14 | 15 | export default config; 16 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /docs/.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 | -------------------------------------------------------------------------------- /docs/app/components/snippets/animations-1.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 14 | -------------------------------------------------------------------------------- /docs/app/components/snippets/how-to-use-it-3.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 12 | -------------------------------------------------------------------------------- /docs/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | rules: { 6 | 'attribute-indentation': { 'open-invocation-max-len': 110 }, 7 | 'no-inline-styles': false, 8 | 'no-partial': false, 9 | 'no-positive-tabindex': false, 10 | 'no-quoteless-attributes': false, 11 | 'require-input-label': false, 12 | 'no-unbalanced-curlies': false, 13 | 'no-duplicate-landmark-elements': false, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/app/components/snippets/overlays-1.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 12 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/cookbook.gts: -------------------------------------------------------------------------------- 1 | import { LinkTo } from '@ember/routing'; 2 | 3 | 15 | -------------------------------------------------------------------------------- /docs/tests/unit/routes/public-pages/docs/migrate-7-0-to-8-0-test.ts: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'docs/tests/helpers'; 3 | 4 | module('Unit | Route | public-pages/docs/migrate-7-0-to-8-0', function (hooks) { 5 | setupTest(hooks); 6 | 7 | test('it exists', function (assert) { 8 | const route = this.owner.lookup( 9 | 'route:public-pages/docs/migrate-7-0-to-8-0', 10 | ); 11 | assert.ok(route); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /docs/tests/unit/routes/public-pages/docs/migrate-8-0-to-9-0-test.ts: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupTest } from 'docs/tests/helpers'; 3 | 4 | module('Unit | Route | public-pages/docs/migrate-8-0-to-9-0', function (hooks) { 5 | setupTest(hooks); 6 | 7 | test('it exists', function (assert) { 8 | const route = this.owner.lookup( 9 | 'route:public-pages/docs/migrate-8-0-to-9-0', 10 | ); 11 | assert.ok(route); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /test-app/app/templates/shadow-root.gts: -------------------------------------------------------------------------------- 1 | import RouteTemplate from 'ember-route-template'; 2 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 3 | 4 | export default RouteTemplate( 5 | , 11 | ); 12 | -------------------------------------------------------------------------------- /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 | const app = new EmberApp(defaults, { 7 | 'ember-cli-babel': { enableTypeScriptTransform: true }, 8 | autoImport: { 9 | watchDependencies: ['ember-basic-dropdown'], 10 | }, 11 | }); 12 | 13 | const { maybeEmbroider } = require('@embroider/test-setup'); 14 | return maybeEmbroider(app); 15 | }; 16 | -------------------------------------------------------------------------------- /config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "projectName": "ember-basic-dropdown", 4 | "packages": [ 5 | { 6 | "name": "@embroider/addon-blueprint", 7 | "version": "4.1.2", 8 | "blueprints": [ 9 | { 10 | "name": "@embroider/addon-blueprint", 11 | "isBaseBlueprint": true, 12 | "options": [ 13 | "--pnpm", 14 | "--typescript" 15 | ] 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /docs/tests/test-helper.ts: -------------------------------------------------------------------------------- 1 | import Application from 'docs/app'; 2 | import config from 'docs/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start as qunitStart, setupEmberOnerrorValidation } from 'ember-qunit'; 7 | 8 | export function start() { 9 | setApplication(Application.create(config.APP)); 10 | 11 | setup(QUnit.assert); 12 | setupEmberOnerrorValidation(); 13 | qunitStart(); 14 | } 15 | -------------------------------------------------------------------------------- /docs/app/components/snippets/how-to-use-it-2.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 16 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember", 3 | "compilerOptions": { 4 | // The combination of `baseUrl` with `paths` allows Ember's classic package 5 | // layout, which is not resolvable with the Node resolution algorithm, to 6 | // work with TypeScript. 7 | "baseUrl": ".", 8 | "paths": { 9 | "docs/tests/*": ["tests/*"], 10 | "docs/*": ["app/*"], 11 | "*": ["types/*"] 12 | }, 13 | "types": ["ember-source/types", "@embroider/core/virtual", "vite/client"] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test-app/tests/test-helper.ts: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /docs/app/components/snippets/styles-2.scss: -------------------------------------------------------------------------------- 1 | // ember-basic-dropdown v8.x 2 | // Define your variables first 3 | $ember-basic-dropdown-overlay-background: rgba(#3b5998, 0.5); 4 | 5 | // Then import the styles, which will use your variable 6 | @import "ember-basic-dropdown"; 7 | 8 | // ember-basic-dropdown v9.x+ 9 | // Define your variables first 10 | @use "ember-basic-dropdown/scss/variables.scss" with ( 11 | $ember-basic-dropdown-overlay-background: rgba(#3b5998, 0.5) 12 | ); 13 | 14 | // Then use the styles, which will use your variable 15 | @use "ember-basic-dropdown"; 16 | -------------------------------------------------------------------------------- /docs/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "6.7.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 | "options": [ 14 | "--no-welcome", 15 | "--pnpm", 16 | "--typescript" 17 | ] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /test-app/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "6.7.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 | "options": [ 14 | "--no-welcome", 15 | "--pnpm", 16 | "--typescript" 17 | ] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /docs/app/components/snippets/no-trigger-2.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | import basicDropdownTrigger from 'ember-basic-dropdown/modifiers/basic-dropdown-trigger'; 3 | 4 | 19 | -------------------------------------------------------------------------------- /docs/app/components/snippets/installation-2.js.txt: -------------------------------------------------------------------------------- 1 | import config from './config/environment'; // this line should already be present 2 | import { setConfig } from 'ember-basic-dropdown/config'; 3 | 4 | // Basic usage: 5 | setConfig({ 6 | rootElement: config.APP.rootElement, // Default is 'body' (or '#ember-testing' in tests) 7 | }); 8 | 9 | // Advanced: If you need to override globally the destination element ID (BasicDropdownWormhole), you can do: 10 | setConfig({ 11 | destination: 'my-custom-destination-id', 12 | rootElement: config.APP.rootElement, // Default is 'body' (or '#ember-testing' in tests) 13 | }); 14 | -------------------------------------------------------------------------------- /ember-basic-dropdown/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 | -------------------------------------------------------------------------------- /test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember", 3 | "compilerOptions": { 4 | "skipLibCheck": true, 5 | "noEmit": true, 6 | "noEmitOnError": false, 7 | "declaration": false, 8 | "declarationMap": false, 9 | // The combination of `baseUrl` with `paths` allows Ember's classic package 10 | // layout, which is not resolvable with the Node resolution algorithm, to 11 | // work with TypeScript. 12 | "baseUrl": ".", 13 | "allowJs": true, 14 | "paths": { 15 | "test-app/tests/*": ["tests/*"], 16 | "test-app/*": ["app/*"], 17 | "*": ["types/*"] 18 | }, 19 | "types": ["ember-source/types"] 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /docs/app/styles/demos/scrollable-container.scss: -------------------------------------------------------------------------------- 1 | .scrolling-container-temporal-demo { 2 | padding: 100px; 3 | } 4 | .scroll { 5 | &__container { 6 | max-height: 600px; 7 | overflow: auto; 8 | padding: 20px; 9 | } 10 | 11 | &__scrolling-container { 12 | position: relative; 13 | height: 1000px; 14 | margin: 50px; 15 | } 16 | 17 | &__filler { 18 | width: 100%; 19 | height: 200px; 20 | background-color: $blue-color; 21 | } 22 | 23 | &__content-container { 24 | max-height: 200px; 25 | overflow: auto; 26 | } 27 | 28 | &__content { 29 | padding: 100px 200px; 30 | height: 300px; 31 | width: 130%; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docs/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 3 | 4 | const { compatBuild } = require('@embroider/compat'); 5 | 6 | module.exports = async function (defaults) { 7 | const { buildOnce } = await import('@embroider/vite'); 8 | 9 | const app = new EmberApp(defaults, { 10 | 'ember-cli-babel': { enableTypeScriptTransform: true }, 11 | autoImport: { 12 | watchDependencies: ['ember-basic-dropdown'], 13 | }, 14 | babel: { 15 | plugins: [ 16 | require.resolve('ember-concurrency/async-arrow-task-transform'), 17 | ], 18 | }, 19 | }); 20 | 21 | return compatBuild(app, buildOnce); 22 | }; 23 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/app/components/code-block.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import CodeInline from './code-inline'; 3 | 4 | interface CodeBlockSignature { 5 | Element: HTMLElement; 6 | Args: { 7 | fileName: string; 8 | language?: string; 9 | }; 10 | } 11 | 12 | export default class CodeBlock extends Component { 13 | get language() { 14 | return this.args.language ?? 'markup'; 15 | } 16 | 17 | 28 | } 29 | -------------------------------------------------------------------------------- /docs/app/templates/helpers-testing.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 28 | -------------------------------------------------------------------------------- /docs/app/styles/docs/code-example.scss: -------------------------------------------------------------------------------- 1 | // Docs 2 | .code-example { 3 | margin-bottom: 1rem; 4 | } 5 | 6 | .code-example-tabs { 7 | display: flex; 8 | } 9 | .code-example-tab { 10 | padding: 0.2rem 0.5rem; 11 | background-color: white; 12 | text-transform: capitalize; 13 | &:not(.active) { 14 | color: #999; 15 | cursor: pointer; 16 | } 17 | &.active { 18 | cursor: normal; 19 | } 20 | } 21 | .code-example-snippet { 22 | margin-bottom: 0; 23 | background-color: #f9f9f9; 24 | padding: 1rem; 25 | &:not(.active) { 26 | display: none; 27 | } 28 | } 29 | 30 | @mixin ember-code-example($color) { 31 | .code-example-tab.active { 32 | border-bottom: 2px solid $color; 33 | color: $color; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /docs/app/components/snippets/how-to-use-it-2-css.scss: -------------------------------------------------------------------------------- 1 | .trigger-bootstrap-feel { 2 | display: inline-block; 3 | text-align: center; 4 | vertical-align: middle; 5 | cursor: pointer; 6 | border: 1px solid #ccc; 7 | padding: 6px 12px; 8 | line-height: 1.42857143; 9 | border-radius: 4px; 10 | color: #333; 11 | background-color: #fff; 12 | outline: none; 13 | } 14 | 15 | .trigger-bootstrap-feel:focus, 16 | .trigger-bootstrap-feel:hover { 17 | background-color: #d4d4d4; 18 | border-color: #8c8c8c; 19 | } 20 | 21 | .content-bootstrap-feel { 22 | background-color: #fff; 23 | border: 1px solid rgba(0, 0, 0, 0.15); 24 | border-radius: 4px; 25 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); 26 | padding: 5px; 27 | margin: 2px 0 0; 28 | } 29 | -------------------------------------------------------------------------------- /docs/app/styles/components/main-footer.scss: -------------------------------------------------------------------------------- 1 | // Main footer 2 | .main-footer { 3 | color: $gray; 4 | background-color: $light-gray; 5 | border-top: 1px solid #bbb; 6 | padding-left: 1rem; 7 | padding-right: 1rem; 8 | } 9 | .main-footer-content { 10 | @extend %wrapper; 11 | padding: 1rem 0; 12 | } 13 | @media only screen and (min-width: $medium-breakpoint) { 14 | .icons-attributions { 15 | float: right; 16 | } 17 | } 18 | 19 | body .contribute-call-to-action a { 20 | border-radius: 4px; 21 | padding: 5px 10px; 22 | text-align: center; 23 | color: white; 24 | /* more styles in brandification */ 25 | } 26 | @media only screen and (min-width: $small-breakpoint) { 27 | .contribute-call-to-action { 28 | float: right; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /docs/app/components/snippets/no-trigger-1.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | import { on } from '@ember/modifier'; 3 | 4 | 26 | -------------------------------------------------------------------------------- /docs/.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": true, 7 | 8 | /** 9 | Setting `componentAuthoringFormat` to "strict" will force the blueprint generators to generate GJS 10 | or GTS files for the component and the component rendering test. "loose" is the default. 11 | */ 12 | "componentAuthoringFormat": "loose", 13 | 14 | /** 15 | Setting `routeAuthoringFormat` to "strict" will force the blueprint generators to generate GJS 16 | or GTS templates for routes. "loose" is the default 17 | */ 18 | "routeAuthoringFormat": "loose" 19 | } 20 | -------------------------------------------------------------------------------- /docs/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | if (typeof module !== 'undefined') { 4 | module.exports = { 5 | test_page: 'tests/index.html?hidepassed', 6 | disable_watching: true, 7 | launch_in_ci: ['Chrome'], 8 | launch_in_dev: ['Chrome'], 9 | browser_start_timeout: 120, 10 | browser_args: { 11 | Chrome: { 12 | ci: [ 13 | // --no-sandbox is needed when running Chrome inside a container 14 | process.env.CI ? '--no-sandbox' : null, 15 | '--headless', 16 | '--disable-dev-shm-usage', 17 | '--disable-software-rasterizer', 18 | '--mute-audio', 19 | '--remote-debugging-port=0', 20 | '--window-size=1440,900', 21 | ].filter(Boolean), 22 | }, 23 | }, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /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": true, 7 | 8 | /** 9 | Setting `componentAuthoringFormat` to "strict" will force the blueprint generators to generate GJS 10 | or GTS files for the component and the component rendering test. "loose" is the default. 11 | */ 12 | "componentAuthoringFormat": "loose", 13 | 14 | /** 15 | Setting `routeAuthoringFormat` to "strict" will force the blueprint generators to generate GJS 16 | or GTS templates for routes. "loose" is the default 17 | */ 18 | "routeAuthoringFormat": "loose" 19 | } 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | - `git clone ` 6 | - `cd ember-basic-dropdown` 7 | - `pnpm install` 8 | 9 | ## Linting 10 | 11 | - `pnpm lint` 12 | - `pnpm lint:fix` 13 | 14 | ## Building the addon 15 | 16 | - `cd ember-basic-dropdown` 17 | - `pnpm build` 18 | 19 | ## Running tests 20 | 21 | - `cd test-app` 22 | - `pnpm test` – Runs the test suite on the current Ember version 23 | - `pnpm test:watch` – Runs the test suite in "watch mode" 24 | 25 | ## Running the test application 26 | 27 | - `cd test-app` 28 | - `pnpm start` 29 | - Visit the test application at [http://localhost:4200](http://localhost:4200). 30 | 31 | For more information on using ember-cli, visit [https://cli.emberjs.com/release/](https://cli.emberjs.com/release/). 32 | -------------------------------------------------------------------------------- /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 | 23 | 24 | {{content-for "body-footer"}} 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/app/styles/components/docs.scss: -------------------------------------------------------------------------------- 1 | // Docs 2 | .docs { 3 | @extend %wrapper; 4 | flex: 1; 5 | display: flex; 6 | padding-top: 1rem; 7 | } 8 | // Doc page 9 | .doc-page { 10 | flex: 1; 11 | min-width: 0; // Flexbox issue in chrome?? 12 | } 13 | .doc-page-title { 14 | margin-top: 0; 15 | } 16 | .doc-page-nav { 17 | @extend %clearfix; 18 | margin-bottom: 1rem; 19 | } 20 | .doc-page-nav-link-next, 21 | .doc-page-nav-link-prev { 22 | /* more styles in brandification */ 23 | padding: 0.2rem 0.5rem; 24 | font-weight: 600; 25 | border-radius: 4px; 26 | } 27 | .doc-page-nav-link-next { 28 | float: right; 29 | } 30 | .doc-page-nav-link-prev { 31 | float: left; 32 | } 33 | .section-selector { 34 | margin-bottom: 1rem; 35 | @media only screen and (min-width: $medium-breakpoint) { 36 | display: none; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/app/deprecation-workflow.ts: -------------------------------------------------------------------------------- 1 | import setupDeprecationWorkflow from 'ember-cli-deprecation-workflow'; 2 | 3 | /** 4 | * Docs: https://github.com/ember-cli/ember-cli-deprecation-workflow 5 | */ 6 | setupDeprecationWorkflow({ 7 | /** 8 | false by default, but if a developer / team wants to be more aggressive about being proactive with 9 | handling their deprecations, this should be set to "true" 10 | */ 11 | throwOnUnhandled: false, 12 | workflow: [ 13 | /* ... handlers ... */ 14 | /* to generate this list, run your app for a while (or run the test suite), 15 | * and then run in the browser console: 16 | * 17 | * deprecationWorkflow.flushDeprecations() 18 | * 19 | * And copy the handlers here 20 | */ 21 | /* example: */ 22 | /* { handler: 'silence', matchId: 'template-action' }, */ 23 | ], 24 | }); 25 | -------------------------------------------------------------------------------- /test-app/app/deprecation-workflow.ts: -------------------------------------------------------------------------------- 1 | import setupDeprecationWorkflow from 'ember-cli-deprecation-workflow'; 2 | 3 | /** 4 | * Docs: https://github.com/ember-cli/ember-cli-deprecation-workflow 5 | */ 6 | setupDeprecationWorkflow({ 7 | /** 8 | false by default, but if a developer / team wants to be more aggressive about being proactive with 9 | handling their deprecations, this should be set to "true" 10 | */ 11 | throwOnUnhandled: false, 12 | workflow: [ 13 | /* ... handlers ... */ 14 | /* to generate this list, run your app for a while (or run the test suite), 15 | * and then run in the browser console: 16 | * 17 | * deprecationWorkflow.flushDeprecations() 18 | * 19 | * And copy the handlers here 20 | */ 21 | /* example: */ 22 | /* { handler: 'silence', matchId: 'template-action' }, */ 23 | ], 24 | }); 25 | -------------------------------------------------------------------------------- /docs/app/styles/_utilities.scss: -------------------------------------------------------------------------------- 1 | // Utility classes 2 | .text-center { 3 | text-align: center; 4 | } 5 | 6 | .width-300 { 7 | width: 300px; 8 | } 9 | 10 | // My own placeholders 11 | %clearfix { 12 | display: block; 13 | &::after { 14 | visibility: hidden; 15 | display: block; 16 | font-size: 0; 17 | content: " "; 18 | clear: both; 19 | height: 0; 20 | } 21 | } 22 | %wrapper { 23 | max-width: 1024px; 24 | width: 100%; 25 | padding-left: $wrapper-padding; 26 | padding-right: $wrapper-padding; 27 | margin-left: auto; 28 | margin-right: auto; 29 | } 30 | 31 | .caret { 32 | display: inline-block; 33 | width: 0; 34 | height: 0; 35 | margin-left: 2px; 36 | vertical-align: middle; 37 | border-top: 4px dashed; 38 | border-top: 4px solid; 39 | border-right: 4px solid transparent; 40 | border-left: 4px solid transparent; 41 | } 42 | -------------------------------------------------------------------------------- /docs/app/app.ts: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | import { importSync, isDevelopingApp, macroCondition } from '@embroider/macros'; 6 | import compatModules from '@embroider/virtual/compat-modules'; 7 | import { setConfig } from 'ember-basic-dropdown/config'; 8 | 9 | if (macroCondition(isDevelopingApp())) { 10 | importSync('./deprecation-workflow'); 11 | } 12 | 13 | setConfig({ 14 | rootElement: config.APP['rootElement'] as string | undefined, 15 | }); 16 | 17 | export default class App extends Application { 18 | modulePrefix = config.modulePrefix; 19 | podModulePrefix = config.podModulePrefix; 20 | Resolver = Resolver.withModules(compatModules); 21 | } 22 | 23 | loadInitializers(App, config.modulePrefix, compatModules); 24 | -------------------------------------------------------------------------------- /test-app/app/app.ts: -------------------------------------------------------------------------------- 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 | import { importSync, isDevelopingApp, macroCondition } from '@embroider/macros'; 6 | import { setConfig, type Config } from 'ember-basic-dropdown/config'; 7 | 8 | if (macroCondition(isDevelopingApp())) { 9 | importSync('./deprecation-workflow'); 10 | } 11 | 12 | export const defaultBasicDropdownConfig: Config = { 13 | rootElement: config.APP['rootElement'] as string | undefined, 14 | }; 15 | 16 | setConfig(defaultBasicDropdownConfig); 17 | 18 | export default class App extends Application { 19 | modulePrefix = config.modulePrefix; 20 | podModulePrefix = config.podModulePrefix; 21 | Resolver = Resolver; 22 | } 23 | 24 | loadInitializers(App, config.modulePrefix); 25 | -------------------------------------------------------------------------------- /docs/app/components/snippets/how-to-use-it-3-css.scss: -------------------------------------------------------------------------------- 1 | .trigger-material-round { 2 | display: inline-block; 3 | border-radius: 50%; 4 | text-align: center; 5 | font-size: 24px; 6 | background-color: #fafafa; 7 | line-height: 40px; 8 | width: 40px; 9 | height: 40px; 10 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.26); 11 | outline: none; 12 | user-select: none; 13 | transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); 14 | transition-property: background-color, box-shadow, transform; 15 | } 16 | 17 | .trigger-material-round:focus, 18 | .trigger-material-round:hover { 19 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4); 20 | } 21 | 22 | .content-material-round { 23 | box-shadow: 24 | 0 2px 4px -1px rgba(0, 0, 0, 0.2), 25 | 0 4px 5px 0 rgba(0, 0, 0, 0.14), 26 | 0 1px 10px 0 rgba(0, 0, 0, 0.12); 27 | border-radius: 50%; 28 | overflow: hidden; 29 | margin: 5px 0 0; 30 | } 31 | -------------------------------------------------------------------------------- /docs/app/styles/components/spinners.scss: -------------------------------------------------------------------------------- 1 | .circular-loader, 2 | .circular-loader:after { 3 | border-radius: 50%; 4 | width: 10em; 5 | height: 10em; 6 | } 7 | .circular-loader { 8 | margin: 60px auto; 9 | font-size: 10px; 10 | position: relative; 11 | text-indent: -9999em; 12 | /* more styles in brandification */ 13 | border-left: 1.1em solid #ffffff; 14 | transform: translateZ(0); 15 | animation: load8 0.8s infinite linear; 16 | } 17 | @-webkit-keyframes load8 { 18 | 0% { 19 | -webkit-transform: rotate(0deg); 20 | transform: rotate(0deg); 21 | } 22 | 100% { 23 | -webkit-transform: rotate(360deg); 24 | transform: rotate(360deg); 25 | } 26 | } 27 | @keyframes load8 { 28 | 0% { 29 | -webkit-transform: rotate(0deg); 30 | transform: rotate(0deg); 31 | } 32 | 100% { 33 | -webkit-transform: rotate(360deg); 34 | transform: rotate(360deg); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/app/components/snippets/trigger-events-2.gts: -------------------------------------------------------------------------------- 1 | import { on } from '@ember/modifier'; 2 | import Component from '@glimmer/component'; 3 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 4 | 5 | export default class extends Component { 6 | useTheKeyboard(e: Event) { 7 | alert('Use the keyboard!'); 8 | return e.stopImmediatePropagation(); 9 | } 10 | 11 | 30 | } 31 | -------------------------------------------------------------------------------- /ember-basic-dropdown/src/test-support/helpers.ts: -------------------------------------------------------------------------------- 1 | import { click, tap } from '@ember/test-helpers'; 2 | 3 | export function clickTrigger( 4 | scope: string | undefined = undefined, 5 | options: MouseEventInit = {}, 6 | ): Promise { 7 | let selector = '.ember-basic-dropdown-trigger'; 8 | if (scope) { 9 | const element = document.querySelector(scope); 10 | if (element && element.classList.contains('ember-basic-dropdown-trigger')) { 11 | selector = scope; 12 | } else { 13 | selector = scope + ' ' + selector; 14 | } 15 | } 16 | return click(selector, options); 17 | } 18 | 19 | export function tapTrigger( 20 | scope: string | undefined = undefined, 21 | options: TouchEventInit = {}, 22 | ): Promise { 23 | let selector = '.ember-basic-dropdown-trigger'; 24 | if (scope) { 25 | selector = scope + ' ' + selector; 26 | } 27 | return tap(selector, options); 28 | } 29 | -------------------------------------------------------------------------------- /docs/app/styles/app.scss: -------------------------------------------------------------------------------- 1 | // Basic dropdown 2 | @use "ember-basic-dropdown"; 3 | 4 | @import "variables"; 5 | @import "base"; 6 | @import "utilities"; 7 | @import "layout"; 8 | 9 | // Components 10 | @import "components/main-header"; 11 | @import "components/link-to-other-version"; 12 | @import "components/main-footer"; 13 | @import "components/index"; 14 | @import "components/side-nav"; 15 | @import "components/docs"; 16 | @import "components/cookbook"; 17 | @import "components/spinners"; 18 | @import "components/code-block"; 19 | 20 | // Looks and feels for examples 21 | @import "look-and-feels/animations"; 22 | @import "look-and-feels/bootstrap"; 23 | @import "look-and-feels/material"; 24 | 25 | // Demos 26 | @import "demos/content-mouse-enter-leave"; 27 | @import "demos/position"; 28 | @import "demos/scrollable-container"; 29 | 30 | // Docs 31 | @import "docs/code-example"; 32 | 33 | // Easter egg 34 | @import "brandification"; 35 | -------------------------------------------------------------------------------- /docs/app/templates/scrolling-container.gts: -------------------------------------------------------------------------------- 1 | import ScrollableContainer1Component from '../components/snippets/scrollable-container-1'; 2 | import CodeExample from 'docs/components/code-example'; 3 | 4 | 23 | -------------------------------------------------------------------------------- /ember-basic-dropdown/src/utils/has-moved.ts: -------------------------------------------------------------------------------- 1 | export default function hasMoved( 2 | endEvent: TouchEvent, 3 | moveEvent?: TouchEvent, 4 | ): boolean { 5 | if (!moveEvent) { 6 | return false; 7 | } 8 | 9 | if ( 10 | !endEvent.changedTouches?.[0] || 11 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access 12 | (moveEvent.changedTouches[0] as any).touchType !== 'stylus' 13 | ) { 14 | return true; 15 | } 16 | 17 | // Distinguish stylus scroll and tap: if touch "distance" < 5px, we consider it a tap 18 | const horizontalDistance = Math.abs( 19 | (moveEvent.changedTouches[0]?.pageX ?? 0) - 20 | endEvent.changedTouches[0].pageX, 21 | ); 22 | const verticalDistance = Math.abs( 23 | (moveEvent.changedTouches[0]?.pageY ?? 0) - 24 | endEvent.changedTouches[0].pageY, 25 | ); 26 | return horizontalDistance >= 5 || verticalDistance >= 5; 27 | } 28 | -------------------------------------------------------------------------------- /docs/app/components/snippets/trigger-events-0.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 27 | -------------------------------------------------------------------------------- /ember-basic-dropdown/less/base.less: -------------------------------------------------------------------------------- 1 | .ember-basic-dropdown { 2 | position: relative; 3 | } 4 | .ember-basic-dropdown, 5 | .ember-basic-dropdown-content, 6 | .ember-basic-dropdown-content * { 7 | box-sizing: border-box; 8 | } 9 | .ember-basic-dropdown-content { 10 | position: absolute; 11 | width: auto; 12 | z-index: @ember-basic-dropdown-content-z-index; 13 | background-color: @ember-basic-dropdown-content-background-color; 14 | } 15 | .ember-basic-dropdown-content--left { 16 | left: 0; 17 | } 18 | .ember-basic-dropdown-content--right { 19 | right: 0; 20 | } 21 | 22 | .ember-basic-dropdown-overlay { 23 | position: fixed; 24 | background: @ember-basic-dropdown-overlay-background; 25 | width: 100%; 26 | height: 100%; 27 | z-index: 10; 28 | top: 0; 29 | left: 0; 30 | pointer-events: @ember-basic-dropdown-overlay-pointer-events; 31 | } 32 | 33 | .ember-basic-dropdown-content-wormhole-origin { 34 | display: inline; 35 | } 36 | -------------------------------------------------------------------------------- /docs/app/components/snippets/scrollable-container-1.gts: -------------------------------------------------------------------------------- 1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 2 | 3 | 26 | -------------------------------------------------------------------------------- /docs/app/styles/look-and-feels/material.scss: -------------------------------------------------------------------------------- 1 | .trigger-material-round { 2 | display: inline-block; 3 | border-radius: 50%; 4 | text-align: center; 5 | font-size: 24px; 6 | background-color: #fafafa; 7 | line-height: 40px; 8 | width: 40px; 9 | height: 40px; 10 | box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.26); 11 | outline: none; 12 | user-select: none; 13 | transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); 14 | transition-property: background-color, box-shadow, transform; 15 | } 16 | 17 | .trigger-material-round:focus, 18 | .trigger-material-round:hover { 19 | background-color: #eee; 20 | box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.4); 21 | } 22 | 23 | .content-material-round { 24 | box-shadow: 25 | 0 2px 4px -1px rgba(0, 0, 0, 0.2), 26 | 0 4px 5px 0 rgba(0, 0, 0, 0.14), 27 | 0 1px 10px 0 rgba(0, 0, 0, 0.12); 28 | border-radius: 50%; 29 | overflow: hidden; 30 | margin: 5px 0 0; 31 | } 32 | 33 | .content-material-round img { 34 | display: block; 35 | } 36 | -------------------------------------------------------------------------------- /docs/app/styles/components/side-nav.scss: -------------------------------------------------------------------------------- 1 | $side-nav-color: #777; 2 | .side-nav { 3 | width: 200px; 4 | color: $side-nav-color; 5 | flex: 0 0 25%; 6 | margin-right: 2rem; 7 | @media only screen and (max-width: $medium-breakpoint) { 8 | display: none; 9 | } 10 | } 11 | .side-nav-header { 12 | font-size: 18px; 13 | font-weight: 300; 14 | color: #777; 15 | margin-bottom: 10px; 16 | border-bottom: 1px solid rgba(153, 153, 153, 0.2); 17 | &:not(:first-child) { 18 | margin-top: 1.5rem; 19 | } 20 | } 21 | .side-nav-link { 22 | display: block; 23 | color: #999; 24 | padding-left: 16px; 25 | &.active { 26 | /* more styles in brandification */ 27 | padding-left: 16px - 3px; 28 | } 29 | } 30 | 31 | .section-selector { 32 | border: 1px solid gray; 33 | width: 100%; 34 | border-radius: 3px; 35 | height: 30px; 36 | font-size: 16px; 37 | } 38 | @media only screen and (min-width: $medium-breakpoint) { 39 | .section-selector { 40 | display: none; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /ember-basic-dropdown/scss/base.scss: -------------------------------------------------------------------------------- 1 | @use "variables"; 2 | 3 | .ember-basic-dropdown { 4 | position: relative; 5 | } 6 | .ember-basic-dropdown, 7 | .ember-basic-dropdown-content, 8 | .ember-basic-dropdown-content * { 9 | box-sizing: border-box; 10 | } 11 | .ember-basic-dropdown-content { 12 | position: absolute; 13 | width: auto; 14 | z-index: variables.$ember-basic-dropdown-content-z-index; 15 | background-color: variables.$ember-basic-dropdown-content-background-color; 16 | } 17 | .ember-basic-dropdown-content--left { 18 | left: 0; 19 | } 20 | .ember-basic-dropdown-content--right { 21 | right: 0; 22 | } 23 | 24 | .ember-basic-dropdown-overlay { 25 | position: fixed; 26 | background: variables.$ember-basic-dropdown-overlay-background; 27 | width: 100%; 28 | height: 100%; 29 | z-index: 10; 30 | top: 0; 31 | left: 0; 32 | pointer-events: variables.$ember-basic-dropdown-overlay-pointer-events; 33 | } 34 | 35 | .ember-basic-dropdown-content-wormhole-origin { 36 | display: inline; 37 | } 38 | -------------------------------------------------------------------------------- /docs/app/components/snippets/animations-1-css.scss: -------------------------------------------------------------------------------- 1 | @keyframes drop-fade-below { 2 | 0% { 3 | opacity: 0; 4 | transform: translateY(-20px); 5 | } 6 | 100% { 7 | opacity: 1; 8 | transform: translateY(0px); 9 | } 10 | } 11 | @keyframes drop-fade-above { 12 | 0% { 13 | opacity: 0; 14 | transform: translateY(20px); 15 | } 16 | 100% { 17 | opacity: 1; 18 | transform: translateY(0px); 19 | } 20 | } 21 | 22 | .slide-fade { 23 | will-change: transform, opacity; 24 | &.ember-basic-dropdown-content--below.ember-basic-dropdown--transitioning-in { 25 | animation: drop-fade-below 0.15s; 26 | } 27 | &.ember-basic-dropdown-content--below.ember-basic-dropdown--transitioning-out { 28 | animation: drop-fade-below 0.15s reverse; 29 | } 30 | &.ember-basic-dropdown-content--above.ember-basic-dropdown--transitioning-in { 31 | animation: drop-fade-above 0.15s; 32 | } 33 | &.ember-basic-dropdown-content--above.ember-basic-dropdown--transitioning-out { 34 | animation: drop-fade-above 0.15s reverse; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /docs/app/components/snippets/trigger-events-1.gts: -------------------------------------------------------------------------------- 1 | import { fn } from '@ember/helper'; 2 | import { on } from '@ember/modifier'; 3 | import Component from '@glimmer/component'; 4 | import BasicDropdown, { 5 | type BasicDropdownDefaultBlock, 6 | } from 'ember-basic-dropdown/components/basic-dropdown'; 7 | 8 | export default class extends Component { 9 | openOnArrowDown(dropdown: BasicDropdownDefaultBlock, e: KeyboardEvent) { 10 | if (e.keyCode === 38) { 11 | dropdown.actions.open(e); 12 | } 13 | } 14 | 15 | 34 | } 35 | -------------------------------------------------------------------------------- /docs/app/styles/look-and-feels/animations.scss: -------------------------------------------------------------------------------- 1 | @keyframes drop-fade-below { 2 | 0% { 3 | opacity: 0; 4 | transform: translateY(-20px); 5 | } 6 | 100% { 7 | opacity: 1; 8 | transform: translateY(0px); 9 | } 10 | } 11 | @keyframes drop-fade-above { 12 | 0% { 13 | opacity: 0; 14 | transform: translateY(20px); 15 | } 16 | 100% { 17 | opacity: 1; 18 | transform: translateY(0px); 19 | } 20 | } 21 | 22 | %slide-fade, 23 | .slide-fade { 24 | will-change: transform, opacity; 25 | &.ember-basic-dropdown-content--below.ember-basic-dropdown--transitioning-in { 26 | animation: drop-fade-below 0.15s; 27 | } 28 | &.ember-basic-dropdown-content--below.ember-basic-dropdown--transitioning-out { 29 | animation: drop-fade-below 0.15s reverse; 30 | } 31 | &.ember-basic-dropdown-content--above.ember-basic-dropdown--transitioning-in { 32 | animation: drop-fade-above 0.15s; 33 | } 34 | &.ember-basic-dropdown-content--above.ember-basic-dropdown--transitioning-out { 35 | animation: drop-fade-above 0.15s reverse; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/app/config/environment.ts: -------------------------------------------------------------------------------- 1 | import loadConfigFromMeta from '@embroider/config-meta-loader'; 2 | import { assert } from '@ember/debug'; 3 | 4 | const config = loadConfigFromMeta('docs') as unknown; 5 | 6 | assert( 7 | 'config is not an object', 8 | typeof config === 'object' && config !== null, 9 | ); 10 | assert( 11 | 'modulePrefix was not detected on your config', 12 | 'modulePrefix' in config && typeof config.modulePrefix === 'string', 13 | ); 14 | assert( 15 | 'locationType was not detected on your config', 16 | 'locationType' in config && typeof config.locationType === 'string', 17 | ); 18 | assert( 19 | 'rootURL was not detected on your config', 20 | 'rootURL' in config && typeof config.rootURL === 'string', 21 | ); 22 | assert( 23 | 'APP was not detected on your config', 24 | 'APP' in config && typeof config.APP === 'object', 25 | ); 26 | 27 | export default config as { 28 | modulePrefix: string; 29 | podModulePrefix?: string; 30 | locationType: string; 31 | rootURL: string; 32 | APP: Record; 33 | } & Record; 34 | -------------------------------------------------------------------------------- /docs/app/styles/_variables.scss: -------------------------------------------------------------------------------- 1 | // Colors 2 | $text-color: #333; 3 | $headings-text-color: #313131; 4 | $code-text-color: $text-color; 5 | 6 | // Breakpoints 7 | $small-breakpoint: 678px; 8 | $medium-breakpoint: 748px; // 768px originally 9 | 10 | // Orange theme 11 | $orange-color: #d8563a; 12 | $orange-color-brighter: #e67c00; 13 | 14 | // Blue theme 15 | $blue-color: #1983ff; 16 | $blue-color-brighter: #27a9c5; 17 | 18 | // Purple theme 19 | $purple-color: #6d48a5; 20 | $purple-color-brighter: #8d66c8; 21 | 22 | // Lile theme 23 | $line-color: #a826ce; 24 | $line-color-brighter: #c07cd4; 25 | 26 | // Pink theme 27 | $pink-color: #ce269f; 28 | $pink-color-brighter: #dd82c3; 29 | 30 | // Red-ish theme 31 | $red-color: #ce264a; 32 | $red-color-brighter: #f77f7f; 33 | 34 | // Brown-ish theme 35 | $brown-color: #d27303; 36 | $brown-color-brighter: lighten($brown-color, 10%); 37 | 38 | // // Red 39 | // $brand-color: $brown-color; 40 | // $brand-color-brighter: $brown-color-brighter; 41 | 42 | $light-gray: #ddd; 43 | $gray: #888; 44 | 45 | // Paddings 46 | $wrapper-padding: 0.5rem; 47 | -------------------------------------------------------------------------------- /ember-basic-dropdown/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2023 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 | -------------------------------------------------------------------------------- /test-app/app/controllers/application.ts: -------------------------------------------------------------------------------- 1 | import type ApplicationInstance from '@ember/application/instance'; 2 | import Controller from '@ember/controller'; 3 | import type Owner from '@ember/owner'; 4 | import type environment from 'test-app/config/environment'; 5 | import { getOwner } from '@ember/owner'; 6 | 7 | // @ts-expect-error Cannot find name 'FastBoot'. 8 | const isFastBoot = typeof FastBoot !== 'undefined'; 9 | 10 | export default class extends Controller { 11 | shadowDom = false; 12 | 13 | constructor(owner: Owner) { 14 | super(owner); 15 | 16 | const config = (getOwner(this) as ApplicationInstance).resolveRegistration( 17 | 'config:environment', 18 | ) as typeof environment; 19 | 20 | this.shadowDom = (config.APP['shadowDom'] as boolean) ?? false; 21 | 22 | if (!this.shadowDom || isFastBoot) { 23 | return; 24 | } 25 | 26 | customElements.define( 27 | 'shadow-root', 28 | class extends HTMLElement { 29 | connectedCallback() { 30 | this.attachShadow({ mode: 'open' }); 31 | } 32 | }, 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/push-dist.yml: -------------------------------------------------------------------------------- 1 | # Because this library needs to be built, 2 | # we can't easily point package.json files at the git repo for easy cross-repo testing. 3 | # 4 | # This workflow brings back that capability by placing the compiled assets on a "dist" branch 5 | # (configurable via the "branch" option below) 6 | name: Push dist 7 | 8 | on: 9 | push: 10 | branches: 11 | - main 12 | - master 13 | 14 | jobs: 15 | push-dist: 16 | name: Push dist 17 | permissions: 18 | contents: write 19 | runs-on: ubuntu-latest 20 | timeout-minutes: 10 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | - uses: pnpm/action-setup@v4 25 | 26 | - uses: actions/setup-node@v4 27 | with: 28 | node-version: 20 29 | cache: 'pnpm' 30 | - name: Install Dependencies 31 | run: pnpm install --frozen-lockfile 32 | - uses: kategengler/put-built-npm-package-contents-on-branch@v2.0.0 33 | with: 34 | branch: dist 35 | token: ${{ secrets.GITHUB_TOKEN }} 36 | working-directory: 'ember-basic-dropdown' 37 | -------------------------------------------------------------------------------- /ember-basic-dropdown/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 BasicDropdownComponent from './components/basic-dropdown'; 6 | import type BasicDropdownWormholeComponent from './components/basic-dropdown-wormhole'; 7 | import type BasicDropdownContentTrigger from './components/basic-dropdown-trigger'; 8 | import type BasicDropdownContentComponent from './components/basic-dropdown-content'; 9 | import type DropdownTriggerModifier from './modifiers/basic-dropdown-trigger'; 10 | 11 | export default interface Registry { 12 | BasicDropdown: typeof BasicDropdownComponent; 13 | BasicDropdownWormhole: typeof BasicDropdownWormholeComponent; 14 | BasicDropdownTrigger: typeof BasicDropdownContentTrigger; 15 | BasicDropdownContent: typeof BasicDropdownContentComponent; 16 | 'basic-dropdown-trigger': typeof DropdownTriggerModifier; 17 | } 18 | -------------------------------------------------------------------------------- /ember-basic-dropdown/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@ember/library-tsconfig", 3 | "include": ["src/**/*", "unpublished-development-types/**/*"], 4 | "compilerOptions": { 5 | "allowJs": true, 6 | "declarationDir": "declarations", 7 | "allowSyntheticDefaultImports": true, 8 | "noImplicitAny": true, 9 | "noImplicitThis": true, 10 | "alwaysStrict": true, 11 | "exactOptionalPropertyTypes": true, 12 | "strictNullChecks": true, 13 | "strictPropertyInitialization": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "noUnusedLocals": true, 16 | "noUnusedParameters": true, 17 | "noImplicitOverride": true, 18 | "noImplicitReturns": true, 19 | /** 20 | https://www.typescriptlang.org/tsconfig#rootDir 21 | "Default: The longest common path of all non-declaration input files." 22 | 23 | Because we want our declarations' structure to match our rollup output, 24 | we need this "rootDir" to match the "srcDir" in the rollup.config.mjs. 25 | 26 | This way, we can have simpler `package.json#exports` that matches 27 | imports to files on disk 28 | */ 29 | "rootDir": "./src", 30 | "types": ["ember-source/types"] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /ember-basic-dropdown/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface DropdownActions { 2 | toggle: (e?: Event) => void; 3 | close: (e?: Event, skipFocus?: boolean) => void; 4 | open: (e?: Event) => void; 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 6 | reposition: (...args: any[]) => undefined | RepositionChanges; 7 | registerTriggerElement: (e: HTMLElement) => void; 8 | registerDropdownElement: (e: HTMLElement) => void; 9 | getTriggerElement: () => HTMLElement | null; 10 | } 11 | 12 | export interface Dropdown { 13 | uniqueId: string; 14 | disabled: boolean; 15 | isOpen: boolean; 16 | actions: DropdownActions; 17 | } 18 | 19 | export type TRootEventType = 'click' | 'mousedown'; 20 | 21 | export type RepositionChanges = { 22 | hPosition: HorizontalPosition; 23 | vPosition: VerticalPosition; 24 | otherStyles: Record; 25 | top?: string | undefined; 26 | left?: string | undefined; 27 | right?: string | undefined; 28 | width?: string | undefined; 29 | height?: string | undefined; 30 | }; 31 | 32 | export type VerticalPosition = 'auto' | 'above' | 'below'; 33 | export type HorizontalPosition = 34 | | 'auto' 35 | | 'auto-right' 36 | | 'auto-left' 37 | | 'left' 38 | | 'right' 39 | | 'center'; 40 | -------------------------------------------------------------------------------- /docs/app/components/snippets/dropdown-events-1.gts: -------------------------------------------------------------------------------- 1 | import { action } from '@ember/object'; 2 | import Component from '@glimmer/component'; 3 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 4 | import { task, timeout } from 'ember-concurrency'; 5 | 6 | const users = [ 7 | { name: 'Nathan', assignment: 'CLI' }, 8 | { name: 'Rober', assignment: 'Whatnot' }, 9 | { name: 'Leah', assignment: 'Community' }, 10 | ]; 11 | 12 | export default class extends Component { 13 | users: typeof users = []; 14 | 15 | loadUsers = task(async () => { 16 | await timeout(1000); 17 | this.users = users; 18 | }); 19 | 20 | @action 21 | open() { 22 | void this.loadUsers.perform(); 23 | } 24 | 25 | 40 | } 41 | -------------------------------------------------------------------------------- /test-app/app/components/shadow.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { modifier } from 'ember-modifier'; 4 | 5 | export default class Shadow extends Component<{ 6 | Element: HTMLDivElement; 7 | Blocks: { default: [] }; 8 | }> { 9 | @tracked shadow: Element | undefined; 10 | 11 | setShadow = (shadowRoot: HTMLDivElement) => { 12 | this.shadow = shadowRoot; 13 | }; 14 | 15 | get getStyles() { 16 | return [...document.head.querySelectorAll('link')].map((link) => link.href); 17 | } 18 | 19 | attachShadow = modifier( 20 | (element: Element, [set]: [(shadowRoot: HTMLDivElement) => void]) => { 21 | const shadowRoot = element.attachShadow({ mode: 'open' }); 22 | const div = document.createElement('div'); 23 | shadowRoot.appendChild(div); 24 | set(div); 25 | }, 26 | ); 27 | 39 | } 40 | -------------------------------------------------------------------------------- /docs/babel.config.cjs: -------------------------------------------------------------------------------- 1 | const { 2 | babelCompatSupport, 3 | templateCompatSupport, 4 | } = require('@embroider/compat/babel'); 5 | 6 | module.exports = { 7 | plugins: [ 8 | [ 9 | '@babel/plugin-transform-typescript', 10 | { 11 | allExtensions: true, 12 | onlyRemoveTypeImports: true, 13 | allowDeclareFields: true, 14 | }, 15 | ], 16 | [ 17 | 'babel-plugin-ember-template-compilation', 18 | { 19 | compilerPath: 'ember-source/dist/ember-template-compiler.js', 20 | enableLegacyModules: [ 21 | 'ember-cli-htmlbars', 22 | 'ember-cli-htmlbars-inline-precompile', 23 | 'htmlbars-inline-precompile', 24 | ], 25 | transforms: [...templateCompatSupport()], 26 | }, 27 | ], 28 | [ 29 | 'module:decorator-transforms', 30 | { 31 | runtime: { 32 | import: require.resolve('decorator-transforms/runtime-esm'), 33 | }, 34 | }, 35 | ], 36 | [ 37 | '@babel/plugin-transform-runtime', 38 | { 39 | absoluteRuntime: __dirname, 40 | useESModules: true, 41 | regenerator: false, 42 | }, 43 | ], 44 | ...babelCompatSupport(), 45 | ], 46 | 47 | generatorOpts: { 48 | compact: false, 49 | }, 50 | }; 51 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/docs/disabled.gts: -------------------------------------------------------------------------------- 1 | import CodeExample from 'docs/components/code-example'; 2 | import Disabled1Component from '../../../components/snippets/disabled-1'; 3 | import { LinkTo } from '@ember/routing'; 4 | 5 | 42 | -------------------------------------------------------------------------------- /test-app/.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 | timeout-minutes: 10 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - uses: pnpm/action-setup@v4 23 | with: 24 | version: 9 25 | - name: Install Node 26 | uses: actions/setup-node@v3 27 | with: 28 | node-version: 18 29 | cache: pnpm 30 | - name: Install Dependencies 31 | run: pnpm install --frozen-lockfile 32 | - name: Lint 33 | run: pnpm lint 34 | 35 | test: 36 | name: "Test" 37 | runs-on: ubuntu-latest 38 | timeout-minutes: 10 39 | 40 | steps: 41 | - uses: actions/checkout@v3 42 | - uses: pnpm/action-setup@v4 43 | with: 44 | version: 9 45 | - name: Install Node 46 | uses: actions/setup-node@v3 47 | with: 48 | node-version: 18 49 | cache: pnpm 50 | - name: Install Dependencies 51 | run: pnpm install --frozen-lockfile 52 | - name: Run Tests 53 | run: pnpm test 54 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docs/app/components/snippets/custom-position-1.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 3 | import type { 4 | CalculatePositionOptions, 5 | CalculatePositionResult, 6 | } from 'ember-basic-dropdown/utils/calculate-position'; 7 | 8 | export default class extends Component { 9 | calculatePosition( 10 | trigger: HTMLElement, 11 | content: HTMLElement, 12 | _destination: HTMLElement, 13 | { horizontalPosition, verticalPosition }: CalculatePositionOptions, 14 | ): CalculatePositionResult { 15 | const { top, left, width, height } = trigger.getBoundingClientRect(); 16 | const { height: contentHeight } = content.getBoundingClientRect(); 17 | const style = { 18 | left: left + width, 19 | top: top + window.pageYOffset + height / 2 - contentHeight / 2, 20 | }; 21 | 22 | return { horizontalPosition, verticalPosition, style }; 23 | } 24 | 25 | 36 | } 37 | -------------------------------------------------------------------------------- /docs/app/router.ts: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'docs/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 | this.route('public-pages', { path: '' }, function () { 11 | this.route('docs', function () { 12 | // Getting started 13 | // index.hbs is "Overview" 14 | this.route('installation'); 15 | this.route('how-to-use-it'); 16 | this.route('dropdown-events'); 17 | this.route('trigger-events'); 18 | this.route('content-events'); 19 | 20 | // Basic customization 21 | this.route('position'); 22 | this.route('disabled'); 23 | this.route('overlays'); 24 | this.route('styles'); 25 | 26 | // Advanced customization 27 | this.route('custom-position'); 28 | this.route('animations'); 29 | 30 | // Migrate 31 | this.route('migrate-7-0-to-8-0'); 32 | 33 | // Other 34 | this.route('test-helpers'); 35 | this.route('api-reference'); 36 | this.route('migrate-8-0-to-9-0'); 37 | }); 38 | 39 | this.route('cookbook', function () { 40 | this.route('no-trigger'); 41 | }); 42 | }); 43 | 44 | this.route('scrolling-container'); 45 | this.route('helpers-testing'); 46 | }); 47 | -------------------------------------------------------------------------------- /docs/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'docs', 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 | -------------------------------------------------------------------------------- /docs/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 | 40 | 41 | {{content-for "body-footer"}} 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /docs/app/components/snippets/trigger-events-4.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | import { on } from '@ember/modifier'; 4 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 5 | import { tracked } from '@glimmer/tracking'; 6 | 7 | export default class extends Component { 8 | @tracked parentDivClass: string | null = null; 9 | 10 | @action 11 | simulateFocusParent() { 12 | this.parentDivClass = 'input-group--focused'; 13 | } 14 | 15 | @action 16 | simulateBlurParent() { 17 | this.parentDivClass = null; 18 | } 19 | 20 | 46 | } 47 | -------------------------------------------------------------------------------- /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 (process.argv.includes('shadowDom')) { 24 | ENV.APP.rootElement = '#shadow-root'; 25 | ENV.APP.shadowDom = true; 26 | } 27 | 28 | if (environment === 'development') { 29 | // ENV.APP.LOG_RESOLVER = true; 30 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 31 | // ENV.APP.LOG_TRANSITIONS = true; 32 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 33 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 34 | } 35 | 36 | if (environment === 'test') { 37 | // Testem prefers this... 38 | ENV.locationType = 'none'; 39 | 40 | // keep test console output quieter 41 | ENV.APP.LOG_ACTIVE_GENERATION = false; 42 | ENV.APP.LOG_VIEW_LOOKUPS = false; 43 | 44 | ENV.APP.rootElement = '#ember-testing'; 45 | ENV.APP.autoboot = false; 46 | } 47 | 48 | if (environment === 'production') { 49 | // here you can enable a production-specific feature 50 | } 51 | 52 | return ENV; 53 | }; 54 | -------------------------------------------------------------------------------- /docs/app/components/snippets/dropdown-events-2.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | import { task, timeout } from 'ember-concurrency'; 4 | import BasicDropdown, { 5 | type Dropdown, 6 | } from 'ember-basic-dropdown/components/basic-dropdown'; 7 | 8 | const users = [ 9 | { name: 'Nathan', assignment: 'CLI' }, 10 | { name: 'Rober', assignment: 'Whatnot' }, 11 | { name: 'Leah', assignment: 'Community' }, 12 | ]; 13 | 14 | export default class extends Component { 15 | users = users; 16 | 17 | // Actions 18 | @action 19 | waitForUsers(dropdown: Dropdown, e?: Event) { 20 | if (e) { 21 | void this.loadUsersAndOpen.perform(dropdown); 22 | return false; 23 | } 24 | } 25 | 26 | // Tasks 27 | loadUsersAndOpen = task(async (dropdown: Dropdown) => { 28 | await timeout(1000); 29 | dropdown.actions.open(); // invoked without event 30 | return users; 31 | }); 32 | 33 | 50 | } 51 | -------------------------------------------------------------------------------- /docs/tests/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | type SetupTestOptions, 6 | } from 'ember-qunit'; 7 | 8 | // This file exists to provide wrappers around ember-qunit's 9 | // test setup functions. This way, you can easily extend the setup that is 10 | // needed per test type. 11 | 12 | function setupApplicationTest(hooks: NestedHooks, options?: SetupTestOptions) { 13 | upstreamSetupApplicationTest(hooks, options); 14 | 15 | // Additional setup for application tests can be done here. 16 | // 17 | // For example, if you need an authenticated session for each 18 | // application test, you could do: 19 | // 20 | // hooks.beforeEach(async function () { 21 | // await authenticateSession(); // ember-simple-auth 22 | // }); 23 | // 24 | // This is also a good place to call test setup functions coming 25 | // from other addons: 26 | // 27 | // setupIntl(hooks, 'en-us'); // ember-intl 28 | // setupMirage(hooks); // ember-cli-mirage 29 | } 30 | 31 | function setupRenderingTest(hooks: NestedHooks, options?: SetupTestOptions) { 32 | upstreamSetupRenderingTest(hooks, options); 33 | 34 | // Additional setup for rendering tests can be done here. 35 | } 36 | 37 | function setupTest(hooks: NestedHooks, options?: SetupTestOptions) { 38 | upstreamSetupTest(hooks, options); 39 | 40 | // Additional setup for unit tests can be done here. 41 | } 42 | 43 | export { setupApplicationTest, setupRenderingTest, setupTest }; 44 | -------------------------------------------------------------------------------- /test-app/tests/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | type SetupTestOptions, 6 | } from 'ember-qunit'; 7 | 8 | // This file exists to provide wrappers around ember-qunit's 9 | // test setup functions. This way, you can easily extend the setup that is 10 | // needed per test type. 11 | 12 | function setupApplicationTest(hooks: NestedHooks, options?: SetupTestOptions) { 13 | upstreamSetupApplicationTest(hooks, options); 14 | 15 | // Additional setup for application tests can be done here. 16 | // 17 | // For example, if you need an authenticated session for each 18 | // application test, you could do: 19 | // 20 | // hooks.beforeEach(async function () { 21 | // await authenticateSession(); // ember-simple-auth 22 | // }); 23 | // 24 | // This is also a good place to call test setup functions coming 25 | // from other addons: 26 | // 27 | // setupIntl(hooks, 'en-us'); // ember-intl 28 | // setupMirage(hooks); // ember-cli-mirage 29 | } 30 | 31 | function setupRenderingTest(hooks: NestedHooks, options?: SetupTestOptions) { 32 | upstreamSetupRenderingTest(hooks, options); 33 | 34 | // Additional setup for rendering tests can be done here. 35 | } 36 | 37 | function setupTest(hooks: NestedHooks, options?: SetupTestOptions) { 38 | upstreamSetupTest(hooks, options); 39 | 40 | // Additional setup for unit tests can be done here. 41 | } 42 | 43 | export { setupApplicationTest, setupRenderingTest, setupTest }; 44 | -------------------------------------------------------------------------------- /docs/app/components/snippets/dropdown-events-3.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 4 | import { on } from '@ember/modifier'; 5 | import { fn } from '@ember/helper'; 6 | import { tracked } from '@glimmer/tracking'; 7 | 8 | export default class extends Component { 9 | @tracked selected: string = ''; 10 | 11 | @action 12 | preventUntilSelected() { 13 | if (!this.selected) { 14 | alert('You have to choose!'); 15 | return false; 16 | } 17 | } 18 | 19 | 50 | } 51 | -------------------------------------------------------------------------------- /test-app/app/instance-initializers/shadow-root.ts: -------------------------------------------------------------------------------- 1 | import type ApplicationInstance from '@ember/application/instance'; 2 | import { setConfig } from 'ember-basic-dropdown/config'; 3 | import config from 'test-app/config/environment'; 4 | 5 | // @ts-expect-error Public property 'isFastBoot' of exported class 6 | const isFastBoot = typeof FastBoot !== 'undefined'; 7 | 8 | export function initialize(appInstance: ApplicationInstance) { 9 | if (config.environment !== 'test' || isFastBoot || !config.APP['shadowDom']) { 10 | return; 11 | } 12 | 13 | let appRootElement = appInstance.rootElement as HTMLElement | null; 14 | 15 | if (typeof appRootElement === 'string') { 16 | appRootElement = document.querySelector( 17 | appRootElement, 18 | ) as HTMLElement | null; 19 | } 20 | const targetElement = 21 | appRootElement || document.getElementsByTagName('body')[0]; 22 | 23 | const hostElement = document.createElement('div'); 24 | hostElement.attachShadow({ mode: 'open' }); 25 | 26 | const rootElement = document.createElement('div'); 27 | const wormhole = document.createElement('div'); 28 | wormhole.id = 'ember-basic-dropdown-wormhole'; 29 | 30 | hostElement.shadowRoot?.appendChild(wormhole); 31 | hostElement.shadowRoot?.appendChild(rootElement); 32 | targetElement?.appendChild(hostElement); 33 | 34 | config.APP['rootElement'] = '#ember-basic-dropdown-wormhole'; 35 | appInstance.set('rootElement', rootElement); 36 | 37 | setConfig({ 38 | rootElement: '#ember-basic-dropdown-wormhole', 39 | }); 40 | } 41 | 42 | export default { 43 | initialize, 44 | }; 45 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/docs/styles.gts: -------------------------------------------------------------------------------- 1 | import { LinkTo } from '@ember/routing'; 2 | import CodeExample from '../../../components/code-example'; 3 | 4 | 50 | -------------------------------------------------------------------------------- /docs/app/components/code-inline.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { modifier } from 'ember-modifier'; 3 | import { htmlSafe, type SafeString } from '@ember/template'; 4 | import { tracked } from '@glimmer/tracking'; 5 | import { codeToHtml } from 'shiki'; 6 | import { task } from 'ember-concurrency'; 7 | 8 | const files = import.meta.glob('./snippets/*', { 9 | query: '?raw', 10 | import: 'default', 11 | }) as Record Promise>; 12 | 13 | async function loadSnippet(name: string) { 14 | const path = `./snippets/${name}` as const; 15 | const loader = files[path]; 16 | if (!loader) throw new Error(`File not found: ${path}`); 17 | return await loader(); 18 | } 19 | 20 | interface CodeInlineSignature { 21 | Element: HTMLElement; 22 | Args: { 23 | fileName: string; 24 | language?: string; 25 | }; 26 | } 27 | 28 | export default class CodeInline extends Component { 29 | @tracked prismCode: string | SafeString = ''; 30 | 31 | get language() { 32 | return this.args.language ?? 'markup'; 33 | } 34 | 35 | setup = modifier(() => { 36 | void this.generateCode.perform(); 37 | }); 38 | 39 | generateCode = task(async () => { 40 | const language = this.language; 41 | 42 | const code = await loadSnippet(this.args.fileName); 43 | 44 | const htmlCode = await codeToHtml(code, { 45 | lang: language, 46 | theme: 'github-light-default', 47 | }); 48 | 49 | this.prismCode = htmlSafe(htmlCode); 50 | }); 51 | 52 | 57 | } 58 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # docs 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://cli.emberjs.com/release/) 13 | - [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | - `git clone ` this repository 18 | - `cd docs` 19 | - `npm install` 20 | 21 | ## Running / Development 22 | 23 | - `npm run start` 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 | - `npm run test` 34 | - `npm run test:ember -- --server` 35 | 36 | ### Linting 37 | 38 | - `npm run lint` 39 | - `npm run lint:fix` 40 | 41 | ### Building 42 | 43 | - `npm exec ember build` (development) 44 | - `npm run build` (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://cli.emberjs.com/release/) 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 | -------------------------------------------------------------------------------- /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/app/components/snippets/trigger-events-3.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | import { htmlSafe, type TrustedHTML } from '@ember/template'; 4 | import { not } from 'ember-truth-helpers'; 5 | import { on } from '@ember/modifier'; 6 | import { fn } from '@ember/helper'; 7 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 8 | 9 | export default class extends Component { 10 | dropdownDisabled = true; 11 | checkboxClass = null; 12 | checkboxLabelStyle: TrustedHTML | string = ''; 13 | 14 | // Actions 15 | @action 16 | highlightCheckboxIfDisabled() { 17 | if (this.dropdownDisabled) { 18 | this.checkboxLabelStyle = htmlSafe('color: red'); 19 | } 20 | } 21 | 22 | @action 23 | resetHighlight() { 24 | this.checkboxLabelStyle = ''; 25 | } 26 | 27 | 54 | } 55 | -------------------------------------------------------------------------------- /test-app/app/components/shadow-root.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { getOwner } from '@ember/owner'; 3 | import not from 'ember-truth-helpers/helpers/not'; 4 | 5 | // @ts-expect-error Public property 'isFastBoot' of exported class 6 | const isFastBoot = typeof FastBoot !== 'undefined'; 7 | 8 | export default class ShadowRoot extends Component<{ 9 | Element: HTMLDivElement; 10 | Blocks: { default: [] }; 11 | }> { 12 | get shadowDom() { 13 | if (this.isFastBoot) { 14 | return false; 15 | } 16 | 17 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 18 | // @ts-ignore 19 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call 20 | const config = getOwner(this).resolveRegistration('config:environment') as { 21 | APP: { 22 | shadowDom: boolean; 23 | }; 24 | }; 25 | 26 | return config.APP.shadowDom ?? false; 27 | } 28 | 29 | isFastBoot = isFastBoot; 30 | 31 | get shadowRootElement(): HTMLElement | null | undefined { 32 | const shadowRoot = document.getElementById('shadow-root')?.shadowRoot; 33 | const div = document.createElement('div'); 34 | shadowRoot?.appendChild(div); 35 | 36 | return div; 37 | } 38 | 39 | get getStyles() { 40 | return [...document.head.querySelectorAll('link')].map((link) => link.href); 41 | } 42 | 54 | } 55 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/docs/overlays.gts: -------------------------------------------------------------------------------- 1 | import CodeExample from '../../../components/code-example'; 2 | import { LinkTo } from '@ember/routing'; 3 | import Overlays1Component from '../../../components/snippets/overlays-1'; 4 | 5 | 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "repository": "https://github.com/cibernox/ember-basic-dropdown", 4 | "license": "MIT", 5 | "author": "Miguel Camba", 6 | "scripts": { 7 | "build": "pnpm run --filter ember-basic-dropdown build", 8 | "build:docs": "pnpm run --filter docs build", 9 | "lint": "pnpm run --filter '*' lint", 10 | "lint:fix": "pnpm run --filter '*' lint:fix", 11 | "start": "concurrently 'pnpm:start:*' --restart-after 5000 --prefixColors auto", 12 | "start:addon": "pnpm run --filter ember-basic-dropdown start --no-watch.clearScreen", 13 | "start:docs": "pnpm run --filter docs start --preserveWatchOutput", 14 | "start:test-app": "pnpm run --filter test-app start", 15 | "test:ember": "pnpm --filter '*' test:ember" 16 | }, 17 | "workspaces": [ 18 | "ember-basic-dropdown", 19 | "docs", 20 | "test-app" 21 | ], 22 | "engines": { 23 | "node": ">= 18" 24 | }, 25 | "release-it": { 26 | "plugins": { 27 | "@release-it-plugins/workspaces": { 28 | "workspaces": [ 29 | "ember-basic-dropdown" 30 | ] 31 | }, 32 | "@release-it-plugins/lerna-changelog": { 33 | "infile": "CHANGELOG.md" 34 | } 35 | }, 36 | "git": { 37 | "tagName": "v${version}" 38 | }, 39 | "github": { 40 | "release": true, 41 | "tokenRef": "GITHUB_AUTH" 42 | }, 43 | "npm": false 44 | }, 45 | "version": "8.11.0", 46 | "packageManager": "pnpm@10.22.0", 47 | "devDependencies": { 48 | "@release-it-plugins/lerna-changelog": "^8.0.1", 49 | "@release-it-plugins/workspaces": "^5.0.3", 50 | "concurrently": "^9.2.1", 51 | "prettier": "^3.7.4", 52 | "prettier-plugin-ember-template-tag": "^2.1.2", 53 | "release-it": "^19.0.6", 54 | "typescript": "^5.9.3" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/docs/migrate-7-0-to-8-0.gts: -------------------------------------------------------------------------------- 1 | import CodeExample from '../../../components/code-example'; 2 | import { LinkTo } from '@ember/routing'; 3 | 4 | 51 | -------------------------------------------------------------------------------- /docs/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ember Basic Dropdown 6 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/docs/animations.gts: -------------------------------------------------------------------------------- 1 | import { LinkTo } from '@ember/routing'; 2 | import CodeExample from '../../../components/code-example'; 3 | import Animations1Component from '../../../components/snippets/animations-1'; 4 | 5 | 55 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/docs/test-helpers.gts: -------------------------------------------------------------------------------- 1 | import { LinkTo } from '@ember/routing'; 2 | import CodeExample from '../../../components/code-example'; 3 | 4 | 61 | -------------------------------------------------------------------------------- /ember-basic-dropdown/src/components/basic-dropdown-wormhole.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { getOwner } from '@ember/application'; 3 | import { config as utilConfig, _configSet, type Config } from '../config.ts'; 4 | import { deprecate } from '@ember/debug'; 5 | 6 | export interface BasicDropdownWormholeSignature { 7 | Element: HTMLElement; 8 | } 9 | 10 | export default class BasicDropdownWormholeComponent extends Component { 11 | get getDestinationId(): string { 12 | let config = utilConfig; 13 | 14 | if (!_configSet) { 15 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 16 | // @ts-ignore 17 | // eslint-disable-next-line @typescript-eslint/no-unsafe-call 18 | const configEnvironment = getOwner(this).resolveRegistration( 19 | 'config:environment', 20 | ) as { 21 | 'ember-basic-dropdown'?: Config; 22 | }; 23 | 24 | if (configEnvironment['ember-basic-dropdown']) { 25 | const legacyConfigString = JSON.stringify( 26 | configEnvironment['ember-basic-dropdown'], 27 | ); 28 | deprecate( 29 | `You have configured \`ember-basic-dropdown\` in \`ember-cli-build.js\`. Remove that configuration and instead use \`import { setConfig } from 'ember-basic-dropdown/config'; setConfig(${legacyConfigString});`, 30 | false, 31 | { 32 | for: 'ember-basic-dropdown', 33 | id: 'ember-basic-dropdown.config-environment', 34 | since: { 35 | enabled: '8.9', 36 | available: '8.9', 37 | }, 38 | until: '9.0.0', 39 | }, 40 | ); 41 | 42 | config = configEnvironment['ember-basic-dropdown']; 43 | } 44 | } 45 | 46 | return config.destination || 'ember-basic-dropdown-wormhole'; 47 | } 48 | 49 | 52 | } 53 | -------------------------------------------------------------------------------- /docs/app/components/snippets/custom-position-2.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { tracked } from '@glimmer/tracking'; 3 | import { task, timeout } from 'ember-concurrency'; 4 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown'; 5 | import type { 6 | CalculatePositionOptions, 7 | CalculatePositionResult, 8 | } from 'ember-basic-dropdown/utils/calculate-position'; 9 | import { action } from '@ember/object'; 10 | 11 | const NAMES = ['Katie', 'Ricardo', 'Igor', 'Alex', 'Martin', 'Godfrey']; 12 | 13 | export default class extends Component { 14 | @tracked names: string[] = []; 15 | 16 | calculatePosition( 17 | trigger: Element, 18 | content: HTMLElement, 19 | _destination: HTMLElement, 20 | { horizontalPosition, verticalPosition }: CalculatePositionOptions, 21 | ): CalculatePositionResult { 22 | const { top, left, width, height } = trigger.getBoundingClientRect(); 23 | const { height: contentHeight } = content.getBoundingClientRect(); 24 | const style = { 25 | left: left + width, 26 | top: top + window.pageYOffset + height / 2 - contentHeight / 2, 27 | }; 28 | 29 | return { horizontalPosition, verticalPosition, style }; 30 | } 31 | 32 | addNames = task(async () => { 33 | this.names = []; 34 | for (const name of NAMES) { 35 | this.names = [...this.names, name]; 36 | await timeout(750); 37 | } 38 | }); 39 | 40 | @action 41 | open() { 42 | void this.addNames.perform(); 43 | } 44 | 45 | 61 | } 62 | -------------------------------------------------------------------------------- /docs/app/styles/demos/content-mouse-enter-leave.scss: -------------------------------------------------------------------------------- 1 | .demo-fake-body { 2 | background: white; 3 | height: 150px; 4 | } 5 | 6 | .demo-fake-nav { 7 | background-color: #0e79c9; 8 | height: 50px; 9 | display: flex; 10 | justify-content: space-between; 11 | align-items: center; 12 | padding: 10px; 13 | } 14 | 15 | .demo-fake-logo { 16 | color: white; 17 | font-size: 30px; 18 | } 19 | 20 | .demo-fake-notifications { 21 | .ember-basic-dropdown-trigger { 22 | display: flex; 23 | color: #fff; 24 | align-items: baseline; 25 | background-color: #0c6bb1; 26 | border-radius: 40px; 27 | padding: 5px 10px; 28 | cursor: pointer; 29 | height: 30px; 30 | &:focus, 31 | &[aria-expanded="true"] { 32 | outline: none; 33 | background-color: #1b94ef; 34 | } 35 | svg { 36 | padding-right: 10px; 37 | height: 16px; 38 | } 39 | } 40 | } 41 | 42 | .demo-fake-notifications-count { 43 | background-color: #fff; 44 | border-radius: 40px; 45 | padding: 0 5px; 46 | color: #333; 47 | font-weight: 600; 48 | width: 20px; 49 | height: 20px; 50 | line-height: 1.42; 51 | } 52 | 53 | .demo-fake-notifications-content { 54 | @extend %slide-fade; 55 | width: 300px; 56 | border: 1px solid #ccd1d9; 57 | box-shadow: rgba(0, 0, 0, 0.172549) 0px 6px 12px 0px; 58 | border-radius: 4px; 59 | margin-top: 10px; 60 | &::before { 61 | content: ""; 62 | display: block; 63 | position: absolute; 64 | width: 0; 65 | height: 0; 66 | border: 10px solid transparent; 67 | bottom: 100%; 68 | right: 10px; 69 | border-bottom-color: #ffffff; 70 | } 71 | } 72 | 73 | .demo-fake-notifications-list { 74 | padding: 0; 75 | margin: 0; 76 | li { 77 | list-style: none; 78 | border-bottom: 1px solid #ccd1d9; 79 | padding: 10px; 80 | display: flex; 81 | align-items: center; 82 | &:hover { 83 | background: #f5f5f5; 84 | } 85 | } 86 | svg { 87 | margin-right: 10px; 88 | color: green; 89 | height: 32px; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Ember Basic Dropdown 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | 15 | 37 | 38 | 39 | {{content-for "head-footer"}} 40 | 41 | 42 | {{content-for "body"}} 43 | 44 | 45 | 51 | 52 | {{content-for "body-footer"}} 53 | 54 | 55 | -------------------------------------------------------------------------------- /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 | 'ember-load-initializers': '^2.1.2', 16 | }, 17 | }, 18 | }, 19 | { 20 | name: 'ember-lts-5.4', 21 | npm: { 22 | devDependencies: { 23 | 'ember-source': '~5.4.0', 24 | }, 25 | }, 26 | }, 27 | { 28 | name: 'ember-lts-5.8', 29 | npm: { 30 | devDependencies: { 31 | 'ember-source': '~5.8.0', 32 | }, 33 | }, 34 | }, 35 | { 36 | name: 'ember-lts-5.12', 37 | npm: { 38 | devDependencies: { 39 | 'ember-source': '~5.12.0', 40 | }, 41 | }, 42 | }, 43 | { 44 | name: 'ember-lts-6.4', 45 | npm: { 46 | devDependencies: { 47 | 'ember-source': '~6.4.0', 48 | }, 49 | }, 50 | }, 51 | { 52 | name: 'ember-lts-6.8', 53 | npm: { 54 | devDependencies: { 55 | 'ember-source': '~6.8.0', 56 | }, 57 | }, 58 | }, 59 | { 60 | name: 'ember-release', 61 | npm: { 62 | devDependencies: { 63 | 'ember-source': await getChannelURL('release'), 64 | }, 65 | }, 66 | }, 67 | { 68 | name: 'ember-beta', 69 | npm: { 70 | devDependencies: { 71 | 'ember-source': await getChannelURL('beta'), 72 | }, 73 | }, 74 | }, 75 | { 76 | name: 'ember-canary', 77 | npm: { 78 | devDependencies: { 79 | 'ember-source': await getChannelURL('canary'), 80 | }, 81 | }, 82 | }, 83 | embroiderSafe(), 84 | embroiderOptimized(), 85 | ], 86 | }; 87 | }; 88 | -------------------------------------------------------------------------------- /docs/app/styles/components/index.scss: -------------------------------------------------------------------------------- 1 | // Headline 2 | .main-page-headline { 3 | /* more styles in brandification */ 4 | height: 300px; 5 | color: white; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | @media only screen and (max-width: $medium-breakpoint) { 10 | height: 200px; 11 | } 12 | } 13 | .main-page-headline-content { 14 | margin: 0; 15 | text-align: center; 16 | color: inherit; 17 | max-width: 530px; 18 | font-weight: 200; 19 | img { 20 | vertical-align: top; 21 | } 22 | @media only screen and (max-width: $small-breakpoint) { 23 | font-size: 34px; 24 | img { 25 | height: 40px; 26 | } 27 | } 28 | @media only screen and (min-width: $small-breakpoint) { 29 | font-size: 42px; 30 | img { 31 | height: 48px; 32 | } 33 | } 34 | } 35 | // Selling points 36 | .selling-points { 37 | @extend %wrapper; 38 | display: flex; 39 | @media only screen and (max-width: $small-breakpoint) { 40 | flex-direction: column; 41 | } 42 | } 43 | .selling-point { 44 | display: flex; 45 | padding: 2rem 0.5rem; 46 | flex: 1; 47 | flex-direction: column; 48 | text-align: center; 49 | } 50 | .selling-point-icon { 51 | width: 150px; 52 | height: auto; 53 | margin: 25px auto; 54 | color: #ccc; 55 | } 56 | .selling-point-code { 57 | color: #ccc; 58 | padding: 0; 59 | font-size: 22px; 60 | font-family: monospace; 61 | padding: 80px 0; 62 | white-space: nowrap; 63 | text-align: center; 64 | @media only screen and (min-width: 520px) and (max-width: 768px) { 65 | font-size: 18px; 66 | } 67 | } 68 | .selling-point-text { 69 | color: $gray; 70 | display: flex; 71 | flex-direction: column; 72 | justify-content: center; 73 | text-align: justify; 74 | h2 { 75 | font-weight: 400; 76 | font-size: 1.5em; 77 | margin-bottom: 1.5rem; 78 | text-align: center; 79 | } 80 | } 81 | 82 | // get-started-button 83 | .get-started-button { 84 | margin-top: 2rem; 85 | margin-bottom: 2rem; 86 | /* more styles in brandification */ 87 | border-radius: 4px; 88 | padding: 0.5rem 2rem; 89 | display: inline-block; 90 | } 91 | 92 | // Getting started area 93 | .get-started { 94 | flex-grow: 1; 95 | } 96 | -------------------------------------------------------------------------------- /test-app/tests/integration/components/basic-dropdown-wormhole-test.gts: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import { setConfig } from 'ember-basic-dropdown/config'; 5 | import { defaultBasicDropdownConfig } from 'test-app/app'; 6 | import BasicDropdownWormhole from 'ember-basic-dropdown/components/basic-dropdown-wormhole'; 7 | import type { TestContext } from '@ember/test-helpers'; 8 | 9 | interface ExtendedTestContext extends TestContext { 10 | element: HTMLElement; 11 | } 12 | 13 | function getRootNode(element: Element): HTMLElement { 14 | return element.getRootNode() as HTMLElement; 15 | } 16 | 17 | module('Integration | Component | basic-dropdown-wormhole', function (hooks) { 18 | setupRenderingTest(hooks); 19 | 20 | hooks.afterEach(function (this: ExtendedTestContext) { 21 | setConfig(defaultBasicDropdownConfig); 22 | }); 23 | 24 | test('Is present', async function (assert) { 25 | await render(); 26 | 27 | assert 28 | .dom('#ember-basic-dropdown-wormhole', getRootNode(this.element)) 29 | .exists('wormhole is present'); 30 | }); 31 | 32 | test('Uses custom destination from config if present', async function (assert) { 33 | setConfig({ 34 | ...defaultBasicDropdownConfig, 35 | destination: 'custom-wormhole-destination', 36 | }); 37 | 38 | await render(); 39 | 40 | assert 41 | .dom( 42 | '.ember-application #custom-wormhole-destination', 43 | getRootNode(this.element), 44 | ) 45 | .exists('custom destination is used'); 46 | 47 | assert 48 | .dom( 49 | '.ember-application #ember-basic-dropdown-wormhole', 50 | getRootNode(this.element), 51 | ) 52 | .doesNotExist('default destination is not used'); 53 | }); 54 | 55 | test('Has class my-custom-class', async function (assert) { 56 | await render( 57 | , 58 | ); 59 | 60 | assert 61 | .dom('.my-custom-class', getRootNode(this.element)) 62 | .exists('my-custom-class was set'); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /docs/app/styles/components/main-header.scss: -------------------------------------------------------------------------------- 1 | // main header 2 | .main-header { 3 | /* more styles in brandification */ 4 | color: white; 5 | } 6 | .main-header-nav { 7 | @extend %wrapper; 8 | } 9 | .main-header-logo { 10 | color: white; 11 | font-size: 30px; 12 | display: block; 13 | text-align: center; 14 | line-height: 30px; 15 | padding-top: 8px; 16 | .home-link { 17 | vertical-align: text-bottom; 18 | color: white; 19 | } 20 | img { 21 | height: 36px; 22 | width: 88px; 23 | vertical-align: -5%; 24 | } 25 | } 26 | .logo-dropdown-button { 27 | display: inline-block; 28 | outline: none; 29 | border-radius: 4px; 30 | /* more styles in brandification */ 31 | padding: 5px 10px; 32 | font-size: inherit; 33 | color: inherit; 34 | text-align: center; 35 | border: 1px solid #f9efe4; 36 | box-shadow: 0 3px #f9efe4; 37 | &:hover { 38 | /* more styles in brandification */ 39 | } 40 | &[aria-expanded="true"] { 41 | box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); 42 | transform: translateY(3px); 43 | } 44 | } 45 | .logo-dropdown-content { 46 | border: 1px solid #f9efe4; 47 | display: flex; 48 | width: 300px; 49 | /* more styles in brandification */ 50 | border-radius: 4px; 51 | color: white; 52 | box-shadow: 0 3px 5px rgba(0, 0, 0, 0.25); 53 | overflow: hidden; 54 | } 55 | .logo-dropdown-content-pantone-entry { 56 | flex-grow: 1; 57 | min-width: 30px; 58 | height: 90px; 59 | border: none; 60 | outline: none; 61 | transition: flex-grow 0.15s; 62 | &:hover { 63 | flex-grow: 5; 64 | } 65 | } 66 | 67 | .main-header-nav-links { 68 | display: flex; 69 | } 70 | .main-header-nav-link { 71 | font-weight: 500; 72 | padding-left: 1rem; 73 | padding-right: 1rem; 74 | } 75 | @media only screen and (max-width: $small-breakpoint) { 76 | .main-header { 77 | line-height: 44px; 78 | } 79 | .main-header-nav { 80 | padding-right: 0; 81 | padding-left: 0; 82 | } 83 | .main-header-nav-links { 84 | justify-content: space-between; 85 | background-color: rgba(black, 0.2); 86 | overflow-x: auto; 87 | } 88 | } 89 | @media only screen and (min-width: $small-breakpoint) { 90 | .main-header { 91 | line-height: 66px; 92 | } 93 | .main-header-nav { 94 | display: flex; 95 | justify-content: space-between; 96 | flex-direction: row-reverse; 97 | } 98 | .main-header-nav-link.active { 99 | background-color: white; 100 | /* more styles in brandification */ 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/docs/content-events.gts: -------------------------------------------------------------------------------- 1 | import { LinkTo } from '@ember/routing'; 2 | import CodeExample from '../../../components/code-example'; 3 | import ContentEvents1Component from '../../../components/snippets/content-events-1'; 4 | 5 | 76 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/cookbook/index.gts: -------------------------------------------------------------------------------- 1 | import CodeExample from '../../../components/code-example'; 2 | import NoTrigger1Component from '../../../components/snippets/no-trigger-1'; 3 | import NoTrigger2Component from '../../../components/snippets/no-trigger-2'; 4 | 5 | 79 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Docs CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: 9 | 10 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 11 | permissions: 12 | contents: read 13 | pages: write 14 | id-token: write 15 | 16 | jobs: 17 | lint: 18 | name: Lint 19 | runs-on: ubuntu-latest 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: pnpm/action-setup@v4 23 | 24 | - uses: actions/setup-node@v4 25 | with: 26 | node-version: 20.x 27 | cache: 'pnpm' 28 | 29 | - name: 'Install dependencies' 30 | run: pnpm install --frozen-lockfile 31 | 32 | - run: pnpm build 33 | - run: pnpm i -f # re-sync injected deps 34 | 35 | - name: Lint 36 | run: pnpm --filter docs lint 37 | 38 | test: 39 | name: "Tests" 40 | runs-on: ubuntu-latest 41 | needs: lint 42 | 43 | steps: 44 | - uses: actions/checkout@v4 45 | - uses: pnpm/action-setup@v4 46 | 47 | - name: Install Node 48 | uses: actions/setup-node@v4 49 | with: 50 | node-version: 20.x 51 | cache: pnpm 52 | 53 | - name: Install Dependencies 54 | run: pnpm install --frozen-lockfile 55 | 56 | - run: pnpm build 57 | - run: pnpm i -f # re-sync injected deps 58 | 59 | - name: Run Tests 60 | run: pnpm --filter docs test:ember 61 | 62 | build-documentation: 63 | name: Build documentation 64 | runs-on: ubuntu-latest 65 | needs: [lint, test] 66 | timeout-minutes: 5 67 | if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/master')) 68 | steps: 69 | - uses: actions/checkout@v4 70 | - uses: pnpm/action-setup@v4 71 | 72 | - name: Install Node 73 | uses: actions/setup-node@v4 74 | with: 75 | node-version: 20.x 76 | cache: pnpm 77 | 78 | - name: Install Dependencies 79 | run: pnpm install --frozen-lockfile 80 | 81 | - run: pnpm build 82 | - run: pnpm i -f # re-sync injected deps 83 | 84 | - name: Run Tests 85 | run: pnpm --filter docs build 86 | 87 | - name: Copy 404 page 88 | run: cp ./docs/404.html ./docs/dist/404.html 89 | 90 | - name: Setup Pages 91 | uses: actions/configure-pages@v5 92 | 93 | - name: Upload artifact 94 | uses: actions/upload-pages-artifact@v3 95 | with: 96 | path: './docs/dist' 97 | 98 | deploy-documentation: 99 | name: Deploy documentation 100 | environment: 101 | name: github-pages 102 | url: ${{ steps.deployment.outputs.page_url }} 103 | runs-on: ubuntu-latest 104 | needs: [lint, test, build-documentation] 105 | steps: 106 | - name: Deploy to GitHub Pages 107 | id: deployment 108 | uses: actions/deploy-pages@v4 109 | -------------------------------------------------------------------------------- /docs/app/styles/_brandification.scss: -------------------------------------------------------------------------------- 1 | /* more styles in brandification */ 2 | @mixin brandify($brand-color, $brand-color-brighter) { 3 | a { 4 | color: $brand-color; 5 | } 6 | @include ember-code-example($brand-color); 7 | 8 | .doc-page-nav-link-next, 9 | .doc-page-nav-link-prev { 10 | border: 1px solid $brand-color; 11 | } 12 | 13 | .main-page-headline { 14 | background: $brand-color 15 | linear-gradient($brand-color, $brand-color-brighter); 16 | } 17 | 18 | .get-started-button { 19 | border: 1px solid $brand-color; 20 | } 21 | 22 | .contribute-call-to-action a { 23 | background-color: $brand-color; 24 | border: 1px solid darken($brand-color, 12%); 25 | box-shadow: 0 2px darken($brand-color, 18%); 26 | &:hover { 27 | background-color: darken($brand-color, 5%); 28 | } 29 | } 30 | .main-header { 31 | background-color: $brand-color; 32 | } 33 | 34 | .logo-dropdown-button { 35 | background: $brand-color; 36 | &:hover { 37 | background-color: lighten($brand-color, 3%); 38 | } 39 | } 40 | .main-header-nav-link { 41 | color: inherit; 42 | } 43 | @media only screen and (min-width: $small-breakpoint) { 44 | .main-header-nav-link.active { 45 | color: $brand-color; 46 | } 47 | } 48 | .side-nav-link.active { 49 | border-left: 3px solid $brand-color; 50 | color: $brand-color; 51 | } 52 | .circular-loader { 53 | border-top: 1.1em solid rgba($brand-color, 0.25); 54 | border-right: 1.1em solid rgba($brand-color, 0.25); 55 | border-bottom: 1.1em solid rgba($brand-color, 0.25); 56 | } 57 | } 58 | 59 | .logo-dropdown-content-pantone-entry.brown { 60 | background-color: $brown-color; 61 | } 62 | .logo-dropdown-content-pantone-entry.orange { 63 | background-color: $orange-color; 64 | } 65 | .logo-dropdown-content-pantone-entry.blue { 66 | background-color: $blue-color; 67 | } 68 | .logo-dropdown-content-pantone-entry.purple { 69 | background-color: $purple-color; 70 | } 71 | .logo-dropdown-content-pantone-entry.line { 72 | background-color: $line-color; 73 | } 74 | .logo-dropdown-content-pantone-entry.pink { 75 | background-color: $pink-color; 76 | } 77 | .logo-dropdown-content-pantone-entry.red { 78 | background-color: $red-color; 79 | } 80 | 81 | @include brandify($brown-color, $brown-color-brighter); 82 | 83 | .orange-brand { 84 | @include brandify($orange-color, $orange-color-brighter); 85 | } 86 | .blue-brand { 87 | @include brandify($blue-color, $blue-color-brighter); 88 | } 89 | .purple-brand { 90 | @include brandify($purple-color, $purple-color-brighter); 91 | } 92 | .line-brand { 93 | @include brandify($line-color, $line-color-brighter); 94 | } 95 | .pink-brand { 96 | @include brandify($pink-color, $pink-color-brighter); 97 | } 98 | .red-brand { 99 | @include brandify($red-color, $red-color-brighter); 100 | } 101 | .brown-brand { 102 | // @include brandify($brown-color, $brown-color-brighter); 103 | } 104 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/docs/installation.gts: -------------------------------------------------------------------------------- 1 | import CodeExample from '../../../components/code-example'; 2 | import { LinkTo } from '@ember/routing'; 3 | 4 | 105 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/docs/index.gts: -------------------------------------------------------------------------------- 1 | import { LinkTo } from '@ember/routing'; 2 | 3 | 81 | -------------------------------------------------------------------------------- /docs/app/styles/_base.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | html { 12 | font-size: 100%; 13 | line-height: 1.6; 14 | touch-action: manipulation; 15 | font-family: "Open sans", sans-serif; 16 | } 17 | 18 | body { 19 | color: $text-color; 20 | // position: relative; Styles to demo non 0,0 positioned container 21 | // margin-top: 60px; 22 | } 23 | 24 | a { 25 | text-decoration: none; 26 | /* more styles in brandification */ 27 | } 28 | 29 | img, 30 | iframe { 31 | max-width: 100%; 32 | } 33 | 34 | /* Headings */ 35 | h1, 36 | h2, 37 | h3, 38 | h4, 39 | h5, 40 | h6 { 41 | margin-bottom: 0.5rem; 42 | font-weight: bold; 43 | line-height: 1.25; 44 | color: $headings-text-color; 45 | text-rendering: optimizeLegibility; 46 | } 47 | h1 { 48 | font-size: 2rem; 49 | } 50 | h2 { 51 | margin-top: 1rem; 52 | font-size: 1.5rem; 53 | } 54 | h3 { 55 | margin-top: 1.5rem; 56 | font-size: 1.25rem; 57 | } 58 | h4, 59 | h5, 60 | h6 { 61 | margin-top: 1rem; 62 | font-size: 1rem; 63 | } 64 | 65 | /* Body text */ 66 | p { 67 | margin-top: 0; 68 | margin-bottom: 1rem; 69 | } 70 | 71 | /* Lists */ 72 | ul, 73 | ol, 74 | dl { 75 | margin-top: 0; 76 | margin-bottom: 1rem; 77 | } 78 | 79 | dt { 80 | font-weight: bold; 81 | } 82 | dd { 83 | margin-bottom: 0.5rem; 84 | } 85 | 86 | /* Misc */ 87 | hr { 88 | position: relative; 89 | margin: 1.5rem 0; 90 | border: 0; 91 | border-top: 1px solid #eee; 92 | border-bottom: 1px solid #fff; 93 | } 94 | 95 | abbr { 96 | font-size: 85%; 97 | font-weight: bold; 98 | color: #555; 99 | text-transform: uppercase; 100 | } 101 | abbr[title] { 102 | cursor: help; 103 | border-bottom: 1px dotted #e5e5e5; 104 | } 105 | 106 | /* Code */ 107 | code, 108 | pre { 109 | font-family: Menlo, Monaco, "Courier New", monospace; 110 | } 111 | code { 112 | color: $code-text-color; 113 | padding: 0.2em 0; 114 | margin: 0; 115 | font-size: 75%; 116 | background-color: rgba(0, 0, 0, 0.04); 117 | border-radius: 3px; 118 | 119 | &::before, 120 | &::after { 121 | letter-spacing: -0.2em; 122 | content: "\00a0"; 123 | } 124 | } 125 | pre { 126 | max-width: calc(100vw - #{2 * $wrapper-padding}); 127 | display: block; 128 | margin-top: 0; 129 | margin-bottom: 1rem; 130 | font-size: 0.8rem; 131 | line-height: 1.4; 132 | white-space: pre; 133 | word-break: break-all; 134 | word-wrap: normal; 135 | background-color: #f9f9f9; 136 | overflow-x: auto; 137 | } 138 | pre code { 139 | padding: 0; 140 | font-size: 100%; 141 | color: inherit; 142 | background-color: transparent; 143 | 144 | &::before, 145 | &::after { 146 | content: ""; 147 | } 148 | } 149 | 150 | /* Tables */ 151 | table { 152 | margin-bottom: 1rem; 153 | width: 100%; 154 | border: 1px solid #e5e5e5; 155 | border-collapse: collapse; 156 | } 157 | td, 158 | th { 159 | padding: 0.25rem 0.5rem; 160 | border: 1px solid #e5e5e5; 161 | } 162 | 163 | /* Pygments via Jekyll */ 164 | .highlight { 165 | margin-bottom: 1rem; 166 | border-radius: 4px; 167 | } 168 | .highlight pre { 169 | margin-bottom: 0; 170 | } 171 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages/docs/dropdown-events.gts: -------------------------------------------------------------------------------- 1 | import CodeExample from '../../../components/code-example'; 2 | import { LinkTo } from '@ember/routing'; 3 | import DropDownEvents1Component from '../../../components/snippets/dropdown-events-1'; 4 | import DropDownEvents2Component from '../../../components/snippets/dropdown-events-2'; 5 | import DropDownEvents3Component from '../../../components/snippets/dropdown-events-3'; 6 | 7 | 98 | -------------------------------------------------------------------------------- /ember-basic-dropdown/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 | tsconfigRootDir: import.meta.dirname, 33 | }, 34 | }, 35 | }; 36 | 37 | export default ts.config( 38 | js.configs.recommended, 39 | ember.configs.base, 40 | ember.configs.gjs, 41 | ember.configs.gts, 42 | prettier, 43 | /** 44 | * Ignores must be in their own object 45 | * https://eslint.org/docs/latest/use/configure/ignore 46 | */ 47 | { 48 | ignores: [ 49 | 'blueprints/', 50 | 'dist/', 51 | 'declarations/', 52 | 'node_modules/', 53 | 'coverage/', 54 | '!**/.*', 55 | ], 56 | }, 57 | /** 58 | * https://eslint.org/docs/latest/use/configure/configuration-files#configuring-linter-options 59 | */ 60 | { 61 | linterOptions: { 62 | reportUnusedDisableDirectives: 'error', 63 | }, 64 | }, 65 | { 66 | files: ['**/*.js'], 67 | languageOptions: { 68 | parser: babelParser, 69 | }, 70 | }, 71 | { 72 | files: ['**/*.{js,gjs}'], 73 | languageOptions: { 74 | parserOptions: parserOptions.esm.js, 75 | globals: { 76 | ...globals.browser, 77 | }, 78 | }, 79 | }, 80 | { 81 | files: ['**/*.{ts,gts}'], 82 | languageOptions: { 83 | parser: ember.parser, 84 | parserOptions: parserOptions.esm.ts, 85 | }, 86 | extends: [...ts.configs.recommendedTypeChecked, ember.configs.gts], 87 | }, 88 | { 89 | files: ['src/**/*'], 90 | plugins: { 91 | import: importPlugin, 92 | }, 93 | rules: { 94 | // require relative imports use full extensions 95 | 'import/extensions': ['error', 'always', { ignorePackages: true }], 96 | }, 97 | }, 98 | /** 99 | * CJS node files 100 | */ 101 | { 102 | files: [ 103 | '**/*.cjs', 104 | '.prettierrc.js', 105 | '.stylelintrc.js', 106 | '.template-lintrc.js', 107 | 'addon-main.cjs', 108 | ], 109 | plugins: { 110 | n, 111 | }, 112 | 113 | languageOptions: { 114 | sourceType: 'script', 115 | ecmaVersion: 'latest', 116 | globals: { 117 | ...globals.node, 118 | }, 119 | }, 120 | }, 121 | /** 122 | * ESM node files 123 | */ 124 | { 125 | files: ['**/*.mjs'], 126 | plugins: { 127 | n, 128 | }, 129 | 130 | languageOptions: { 131 | sourceType: 'module', 132 | ecmaVersion: 'latest', 133 | parserOptions: parserOptions.esm.js, 134 | globals: { 135 | ...globals.node, 136 | }, 137 | }, 138 | }, 139 | ); 140 | -------------------------------------------------------------------------------- /docs/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 ts from 'typescript-eslint'; 19 | 20 | import ember from 'eslint-plugin-ember/recommended'; 21 | 22 | import eslintConfigPrettier from 'eslint-config-prettier'; 23 | import qunit from 'eslint-plugin-qunit'; 24 | import n from 'eslint-plugin-n'; 25 | 26 | import babelParser from '@babel/eslint-parser'; 27 | 28 | const parserOptions = { 29 | esm: { 30 | js: { 31 | ecmaFeatures: { modules: true }, 32 | ecmaVersion: 'latest', 33 | }, 34 | ts: { 35 | projectService: true, 36 | tsconfigRootDir: import.meta.dirname, 37 | }, 38 | }, 39 | }; 40 | 41 | export default ts.config( 42 | js.configs.recommended, 43 | ember.configs.base, 44 | ember.configs.gjs, 45 | ember.configs.gts, 46 | eslintConfigPrettier, 47 | /** 48 | * Ignores must be in their own object 49 | * https://eslint.org/docs/latest/use/configure/ignore 50 | */ 51 | { 52 | ignores: [ 53 | 'dist/', 54 | 'node_modules/', 55 | 'coverage/', 56 | 'app/components/snippets/*-snippet.*', 57 | '!**/.*', 58 | ], 59 | }, 60 | /** 61 | * https://eslint.org/docs/latest/use/configure/configuration-files#configuring-linter-options 62 | */ 63 | { 64 | linterOptions: { 65 | reportUnusedDisableDirectives: 'error', 66 | }, 67 | }, 68 | { 69 | files: ['**/*.js'], 70 | languageOptions: { 71 | parser: babelParser, 72 | }, 73 | }, 74 | { 75 | files: ['**/*.{js,gjs}'], 76 | languageOptions: { 77 | parserOptions: parserOptions.esm.js, 78 | globals: { 79 | ...globals.browser, 80 | }, 81 | }, 82 | }, 83 | { 84 | files: ['**/*.{ts,gts}'], 85 | languageOptions: { 86 | parser: ember.parser, 87 | parserOptions: parserOptions.esm.ts, 88 | }, 89 | extends: [...ts.configs.recommendedTypeChecked, ember.configs.gts], 90 | }, 91 | { 92 | files: ['tests/**/*-test.{js,gjs,ts,gts}'], 93 | plugins: { 94 | qunit, 95 | }, 96 | }, 97 | /** 98 | * CJS node files 99 | */ 100 | { 101 | files: [ 102 | '**/*.cjs', 103 | 'config/**/*.js', 104 | 'tests/dummy/config/**/*.js', 105 | 'testem.js', 106 | 'testem*.js', 107 | 'index.js', 108 | '.prettierrc.js', 109 | '.stylelintrc.js', 110 | '.template-lintrc.js', 111 | 'ember-cli-build.js', 112 | ], 113 | plugins: { 114 | n, 115 | }, 116 | 117 | languageOptions: { 118 | sourceType: 'script', 119 | ecmaVersion: 'latest', 120 | globals: { 121 | ...globals.node, 122 | }, 123 | }, 124 | }, 125 | /** 126 | * ESM node files 127 | */ 128 | { 129 | files: ['**/*.mjs'], 130 | plugins: { 131 | n, 132 | }, 133 | 134 | languageOptions: { 135 | sourceType: 'module', 136 | ecmaVersion: 'latest', 137 | parserOptions: parserOptions.esm.js, 138 | globals: { 139 | ...globals.node, 140 | }, 141 | }, 142 | }, 143 | ); 144 | -------------------------------------------------------------------------------- /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 ts from 'typescript-eslint'; 19 | 20 | import ember from 'eslint-plugin-ember/recommended'; 21 | 22 | import eslintConfigPrettier from 'eslint-config-prettier'; 23 | import qunit from 'eslint-plugin-qunit'; 24 | import n from 'eslint-plugin-n'; 25 | 26 | import babelParser from '@babel/eslint-parser'; 27 | 28 | const parserOptions = { 29 | esm: { 30 | js: { 31 | ecmaFeatures: { modules: true }, 32 | ecmaVersion: 'latest', 33 | requireConfigFile: false, 34 | babelOptions: { 35 | plugins: [ 36 | [ 37 | '@babel/plugin-proposal-decorators', 38 | { decoratorsBeforeExport: true }, 39 | ], 40 | ], 41 | }, 42 | }, 43 | ts: { 44 | projectService: true, 45 | tsconfigRootDir: import.meta.dirname, 46 | }, 47 | }, 48 | }; 49 | 50 | export default ts.config( 51 | js.configs.recommended, 52 | ember.configs.base, 53 | ember.configs.gjs, 54 | ember.configs.gts, 55 | eslintConfigPrettier, 56 | /** 57 | * Ignores must be in their own object 58 | * https://eslint.org/docs/latest/use/configure/ignore 59 | */ 60 | { 61 | ignores: ['dist/', 'node_modules/', 'coverage/', '!**/.*'], 62 | }, 63 | /** 64 | * https://eslint.org/docs/latest/use/configure/configuration-files#configuring-linter-options 65 | */ 66 | { 67 | linterOptions: { 68 | reportUnusedDisableDirectives: 'error', 69 | }, 70 | }, 71 | { 72 | files: ['**/*.js'], 73 | languageOptions: { 74 | parser: babelParser, 75 | }, 76 | }, 77 | { 78 | files: ['**/*.{js,gjs}'], 79 | languageOptions: { 80 | parserOptions: parserOptions.esm.js, 81 | globals: { 82 | ...globals.browser, 83 | }, 84 | }, 85 | }, 86 | { 87 | files: ['**/*.{ts,gts}'], 88 | languageOptions: { 89 | parser: ember.parser, 90 | parserOptions: parserOptions.esm.ts, 91 | }, 92 | extends: [...ts.configs.recommendedTypeChecked, ember.configs.gts], 93 | }, 94 | { 95 | files: ['tests/**/*-test.{js,gjs,ts,gts}'], 96 | plugins: { 97 | qunit, 98 | }, 99 | rules: { 100 | '@typescript-eslint/no-this-alias': 'off', 101 | }, 102 | }, 103 | /** 104 | * CJS node files 105 | */ 106 | { 107 | files: [ 108 | '**/*.cjs', 109 | 'config/**/*.js', 110 | 'tests/dummy/config/**/*.js', 111 | 'testem.js', 112 | 'testem*.js', 113 | 'index.js', 114 | '.prettierrc.js', 115 | '.stylelintrc.js', 116 | '.template-lintrc.js', 117 | 'ember-cli-build.js', 118 | ], 119 | plugins: { 120 | n, 121 | }, 122 | 123 | languageOptions: { 124 | sourceType: 'script', 125 | ecmaVersion: 'latest', 126 | globals: { 127 | ...globals.node, 128 | }, 129 | }, 130 | }, 131 | /** 132 | * ESM node files 133 | */ 134 | { 135 | files: ['**/*.mjs'], 136 | plugins: { 137 | n, 138 | }, 139 | 140 | languageOptions: { 141 | sourceType: 'module', 142 | ecmaVersion: 'latest', 143 | parserOptions: parserOptions.esm.js, 144 | globals: { 145 | ...globals.node, 146 | }, 147 | }, 148 | }, 149 | ); 150 | -------------------------------------------------------------------------------- /.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 | timeout-minutes: 10 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | - uses: pnpm/action-setup@v4 23 | - uses: actions/setup-node@v4 24 | with: 25 | node-version: 20.x 26 | cache: pnpm 27 | 28 | - name: 'Install dependencies' 29 | run: pnpm install --frozen-lockfile 30 | - run: pnpm build 31 | - run: pnpm i -f # re-sync injected deps 32 | 33 | - name: Lint 34 | run: pnpm --filter ember-basic-dropdown --filter test-app lint 35 | 36 | test: 37 | name: "Tests" 38 | runs-on: ubuntu-latest 39 | needs: lint 40 | 41 | steps: 42 | - uses: actions/checkout@v4 43 | - uses: pnpm/action-setup@v4 44 | 45 | - name: Install Node 46 | uses: actions/setup-node@v4 47 | with: 48 | node-version: 20.x 49 | cache: pnpm 50 | 51 | - name: Install Dependencies 52 | run: pnpm install --frozen-lockfile 53 | 54 | - run: pnpm build 55 | - run: pnpm i -f # re-sync injected deps 56 | 57 | - name: Run Tests 58 | run: pnpm --filter test-app test:ember 59 | 60 | test-shadow-dom: 61 | name: "Tests Shadow dom" 62 | runs-on: ubuntu-latest 63 | needs: lint 64 | 65 | steps: 66 | - uses: actions/checkout@v4 67 | - uses: pnpm/action-setup@v4 68 | 69 | - name: Install Node 70 | uses: actions/setup-node@v4 71 | with: 72 | node-version: 20.x 73 | cache: 'pnpm' 74 | 75 | - name: Install Dependencies 76 | run: pnpm install --frozen-lockfile 77 | 78 | - run: pnpm build 79 | - run: pnpm i -f # re-sync injected deps 80 | 81 | - name: Run Tests 82 | run: pnpm --filter test-app test:ember-shadow-dom 83 | 84 | floating: 85 | name: "Floating Dependencies" 86 | runs-on: ubuntu-latest 87 | needs: lint 88 | 89 | steps: 90 | - uses: actions/checkout@v4 91 | - uses: pnpm/action-setup@v4 92 | - uses: actions/setup-node@v4 93 | with: 94 | node-version: 20.x 95 | cache: pnpm 96 | 97 | - name: Install Dependencies 98 | run: pnpm install --frozen-lockfile 99 | 100 | - run: pnpm build 101 | - run: pnpm i -f # re-sync injected deps 102 | 103 | - name: Run Tests 104 | run: pnpm --filter test-app test:ember 105 | 106 | try-scenarios: 107 | name: ${{ matrix.try-scenario }} 108 | runs-on: ubuntu-latest 109 | continue-on-error: true 110 | needs: test 111 | 112 | strategy: 113 | fail-fast: false 114 | matrix: 115 | try-scenario: 116 | - ember-lts-4.12 117 | - ember-lts-5.4 118 | - ember-lts-5.8 119 | - ember-lts-5.12 120 | - ember-lts-6.4 121 | - ember-lts-6.8 122 | - ember-release 123 | - ember-beta 124 | - ember-canary 125 | - embroider-safe 126 | - embroider-optimized 127 | 128 | steps: 129 | - uses: actions/checkout@v4 130 | - uses: pnpm/action-setup@v4 131 | 132 | - name: Install Node 133 | uses: actions/setup-node@v4 134 | with: 135 | node-version: 20.x 136 | cache: pnpm 137 | 138 | - name: Install Dependencies 139 | run: pnpm install --frozen-lockfile 140 | - run: pnpm build 141 | - run: pnpm i -f # re-sync injected deps 142 | 143 | - name: Run Tests 144 | env: 145 | EMBER_TRY_SCENARIO: ${{ matrix.try-scenario }} 146 | run: pnpm --filter test-app test:ember-try $EMBER_TRY_SCENARIO --skip-cleanup 147 | -------------------------------------------------------------------------------- /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 | "format": "prettier . --cache --write", 16 | "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto", 17 | "lint:css": "stylelint \"**/*.css\" --allow-empty-input", 18 | "lint:css:fix": "concurrently \"pnpm:lint:css -- --fix\"", 19 | "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto && pnpm format", 20 | "lint:format": "prettier . --cache --check", 21 | "lint:hbs": "ember-template-lint .", 22 | "lint:hbs:fix": "ember-template-lint . --fix", 23 | "lint:js": "eslint . --cache", 24 | "lint:js:fix": "eslint . --fix", 25 | "lint:types": "ember-tsc --noEmit", 26 | "start": "ember serve", 27 | "test": "concurrently \"pnpm:lint\" \"pnpm:test:*\" --names \"lint,test:\" --prefixColors auto", 28 | "test:ember": "ember test", 29 | "test:ember-shadow-dom": "ember test shadowDom", 30 | "test:ember-try": "ember try:one" 31 | }, 32 | "dependenciesMeta": { 33 | "ember-basic-dropdown": { 34 | "injected": true 35 | } 36 | }, 37 | "devDependencies": { 38 | "@babel/core": "^7.28.5", 39 | "@babel/eslint-parser": "^7.28.5", 40 | "@babel/plugin-proposal-decorators": "^7.28.0", 41 | "@ember/optional-features": "^2.3.0", 42 | "@ember/string": "^4.0.1", 43 | "@ember/test-helpers": "^5.4.1", 44 | "@embroider/macros": "^1.19.5", 45 | "@embroider/test-setup": "^4.0.0", 46 | "@eslint/js": "^9.39.1", 47 | "@glimmer/component": "^2.0.0", 48 | "@glimmer/tracking": "^1.1.2", 49 | "@glint/ember-tsc": "^1.0.8", 50 | "@glint/template": "^1.7.3", 51 | "@tsconfig/ember": "^3.0.12", 52 | "@types/qunit": "^2.19.13", 53 | "@types/rsvp": "^4.0.9", 54 | "@warp-drive/ember": "~5.8.0", 55 | "broccoli-asset-rev": "^3.0.0", 56 | "concurrently": "^9.2.1", 57 | "ember-auto-import": "^2.12.0", 58 | "ember-basic-dropdown": "workspace:*", 59 | "ember-cli": "~6.7.0", 60 | "ember-cli-app-version": "^7.0.0", 61 | "ember-cli-babel": "^8.2.0", 62 | "ember-cli-clean-css": "^3.0.0", 63 | "ember-cli-dependency-checker": "^3.3.3", 64 | "ember-cli-deprecation-workflow": "^3.4.0", 65 | "ember-cli-fastboot": "^4.1.5", 66 | "ember-cli-htmlbars": "^6.3.0", 67 | "ember-cli-inject-live-reload": "^2.1.0", 68 | "ember-cli-sass": "^11.0.1", 69 | "ember-cli-sri": "^2.1.1", 70 | "ember-cli-terser": "^4.0.2", 71 | "ember-load-initializers": "^3.0.1", 72 | "ember-modifier": "^4.2.2", 73 | "ember-page-title": "^9.0.3", 74 | "ember-qunit": "^9.0.4", 75 | "ember-resolver": "^13.1.1", 76 | "ember-route-template": "^1.0.3", 77 | "ember-source": "~6.7.0", 78 | "ember-source-channel-url": "^3.0.0", 79 | "ember-template-imports": "^4.3.0", 80 | "ember-template-lint": "^7.9.3", 81 | "ember-truth-helpers": "^5.0.0", 82 | "ember-try": "^4.0.0", 83 | "eslint": "^9.39.1", 84 | "eslint-config-prettier": "^10.1.8", 85 | "eslint-plugin-ember": "^12.7.5", 86 | "eslint-plugin-n": "^17.23.1", 87 | "eslint-plugin-qunit": "^8.2.5", 88 | "globals": "^16.5.0", 89 | "loader.js": "^4.7.0", 90 | "prettier": "^3.7.4", 91 | "prettier-plugin-ember-template-tag": "^2.1.2", 92 | "qunit": "^2.24.3", 93 | "qunit-dom": "^3.5.0", 94 | "stylelint": "^16.26.1", 95 | "stylelint-config-standard": "^39.0.1", 96 | "tracked-built-ins": "^4.0.0", 97 | "typescript": "^5.9.3", 98 | "typescript-eslint": "^8.48.1", 99 | "webpack": "^5.103.0" 100 | }, 101 | "engines": { 102 | "node": ">= 20.11" 103 | }, 104 | "ember": { 105 | "edition": "octane" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Test app for ember-basic-dropdown addon", 6 | "repository": "", 7 | "license": "MIT", 8 | "author": "", 9 | "directories": { 10 | "doc": "doc", 11 | "test": "tests" 12 | }, 13 | "scripts": { 14 | "build": "vite build", 15 | "format": "prettier . --cache --write", 16 | "lint": "concurrently \"pnpm:lint:*(!fix)\" --names \"lint:\" --prefixColors auto", 17 | "lint:css": "stylelint \"**/*.css\" --allow-empty-input", 18 | "lint:css:fix": "concurrently \"pnpm:lint:css -- --fix\"", 19 | "lint:fix": "concurrently \"pnpm:lint:*:fix\" --names \"fix:\" --prefixColors auto && npm run format", 20 | "lint:format": "prettier . --cache --check", 21 | "lint:hbs": "ember-template-lint .", 22 | "lint:hbs:fix": "ember-template-lint . --fix", 23 | "lint:js": "eslint . --cache", 24 | "lint:js:fix": "eslint . --fix", 25 | "lint:types": "ember-tsc --noEmit", 26 | "start": "vite", 27 | "test": "concurrently \"pnpm:lint\" \"pnpm:test:*\" --names \"lint,test:\" --prefixColors auto", 28 | "test:ember": "vite build --mode development && ember test --path dist" 29 | }, 30 | "dependenciesMeta": { 31 | "ember-basic-dropdown": { 32 | "injected": true 33 | } 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.28.5", 37 | "@babel/eslint-parser": "^7.28.5", 38 | "@babel/plugin-proposal-decorators": "^7.28.0", 39 | "@babel/plugin-transform-runtime": "^7.28.5", 40 | "@babel/plugin-transform-typescript": "^7.28.5", 41 | "@ember/optional-features": "^2.3.0", 42 | "@ember/string": "^4.0.1", 43 | "@ember/test-helpers": "^5.4.1", 44 | "@embroider/compat": "^4.1.11", 45 | "@embroider/config-meta-loader": "^1.0.0", 46 | "@embroider/core": "^4.4.0", 47 | "@embroider/macros": "^1.19.5", 48 | "@embroider/vite": "^1.4.4", 49 | "@eslint/js": "^9.39.1", 50 | "@glimmer/component": "^2.0.0", 51 | "@glimmer/tracking": "^1.1.2", 52 | "@glint/ember-tsc": "^1.0.8", 53 | "@glint/template": "^1.7.3", 54 | "@rollup/plugin-babel": "^6.1.0", 55 | "@tsconfig/ember": "^3.0.12", 56 | "@types/qunit": "^2.19.13", 57 | "@types/rsvp": "^4.0.9", 58 | "babel-plugin-ember-template-compilation": "^2.3.0", 59 | "concurrently": "^9.2.1", 60 | "decorator-transforms": "^2.3.0", 61 | "ember-auto-import": "^2.12.0", 62 | "ember-cli": "~6.7.0", 63 | "ember-cli-babel": "^8.2.0", 64 | "ember-cli-deprecation-workflow": "^3.4.0", 65 | "ember-cli-htmlbars": "^6.3.0", 66 | "ember-concurrency": "^5.1.0", 67 | "ember-load-initializers": "^3.0.1", 68 | "ember-modifier": "^4.2.2", 69 | "ember-page-title": "^9.0.3", 70 | "ember-qunit": "^9.0.4", 71 | "ember-resolver": "^13.1.1", 72 | "ember-source": "~6.7.0", 73 | "ember-source-channel-url": "^3.0.0", 74 | "ember-template-lint": "^7.9.3", 75 | "ember-truth-helpers": "^5.0.0", 76 | "eslint": "^9.39.1", 77 | "eslint-config-prettier": "^10.1.8", 78 | "eslint-plugin-ember": "^12.7.5", 79 | "eslint-plugin-n": "^17.23.1", 80 | "eslint-plugin-qunit": "^8.2.5", 81 | "globals": "^16.5.0", 82 | "prettier": "^3.7.4", 83 | "prettier-plugin-ember-template-tag": "^2.1.2", 84 | "qunit": "^2.24.3", 85 | "qunit-dom": "^3.5.0", 86 | "rsvp": "^4.8.5", 87 | "sass-embedded": "^1.93.3", 88 | "shiki": "^3.19.0", 89 | "stylelint": "^16.26.1", 90 | "stylelint-config-standard": "^39.0.1", 91 | "tracked-built-ins": "^4.0.0", 92 | "typescript": "^5.9.3", 93 | "typescript-eslint": "^8.48.1", 94 | "vite": "^7.2.6" 95 | }, 96 | "engines": { 97 | "node": ">= 20.11" 98 | }, 99 | "ember": { 100 | "edition": "octane" 101 | }, 102 | "dependencies": { 103 | "ember-basic-dropdown": "workspace:*" 104 | }, 105 | "ember-addon": { 106 | "type": "app", 107 | "version": 2 108 | }, 109 | "exports": { 110 | "./tests/*": "./tests/*", 111 | "./*": "./app/*" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /docs/app/styles/look-and-feels/bootstrap.scss: -------------------------------------------------------------------------------- 1 | .trigger-bootstrap-feel { 2 | display: inline-block; 3 | text-align: center; 4 | vertical-align: middle; 5 | cursor: pointer; 6 | border: 1px solid #ccc; 7 | padding: 6px 12px; 8 | line-height: 1.42857143; 9 | border-radius: 4px; 10 | color: #333; 11 | background-color: #fff; 12 | outline: none; 13 | } 14 | 15 | .trigger-bootstrap-feel:focus, 16 | .trigger-bootstrap-feel:hover { 17 | background-color: #d4d4d4; 18 | border-color: #8c8c8c; 19 | } 20 | 21 | .trigger-bootstrap-feel[aria-disabled="true"] { 22 | background-color: #ccc; 23 | color: #666; 24 | border-color: #8c8c8c; 25 | cursor: not-allowed; 26 | &:focus, 27 | &:hover { 28 | border-color: #8c8c8c; 29 | } 30 | } 31 | 32 | .content-bootstrap-feel { 33 | background-color: #fff; 34 | border: 1px solid rgba(0, 0, 0, 0.15); 35 | border-radius: 4px; 36 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); 37 | padding: 5px; 38 | margin: 2px 0 0; 39 | } 40 | 41 | // Some bootstrap utility classes 42 | 43 | .form-control { 44 | display: block; 45 | width: 100%; 46 | height: 34px; 47 | padding: 6px 12px; 48 | font-size: 14px; 49 | line-height: 1.42857143; 50 | color: #555; 51 | background-color: #fff; 52 | border: 1px solid #ccc; 53 | border-radius: 4px; 54 | background-image: none; 55 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 56 | transition: 57 | border-color ease-in-out 0.15s, 58 | box-shadow ease-in-out 0.15s; 59 | } 60 | 61 | .input-group { 62 | position: relative; 63 | display: table; 64 | border-collapse: separate; 65 | border: 1px solid #ccc; 66 | border-radius: 4px; 67 | overflow: hidden; 68 | } 69 | 70 | .input-group .form-control { 71 | display: table-cell; 72 | border: none; 73 | border-top-right-radius: 0; 74 | border-bottom-right-radius: 0; 75 | } 76 | .form-inline .form-control { 77 | display: inline-block; 78 | margin-bottom: 0; 79 | vertical-align: middle; 80 | width: auto; 81 | float: right; 82 | } 83 | .input-group-addon { 84 | display: table-cell; 85 | padding: 6px 12px; 86 | font-size: 14px; 87 | font-weight: 400; 88 | line-height: 1; 89 | color: #555; 90 | text-align: center; 91 | background-color: #eee; 92 | border-left: 1px solid #ccc; 93 | } 94 | .input-group-addon { 95 | width: 1%; 96 | white-space: nowrap; 97 | vertical-align: middle; 98 | } 99 | 100 | .input-group .form-control:last-child, 101 | .input-group-addon:last-child, 102 | .input-group-btn:first-child > .btn-group:not(:first-child) > .btn, 103 | .input-group-btn:first-child > .btn:not(:first-child), 104 | .input-group-btn:last-child > .btn, 105 | .input-group-btn:last-child > .btn-group > .btn, 106 | .input-group-btn:last-child > .dropdown-toggle { 107 | border-top-left-radius: 0; 108 | border-bottom-left-radius: 0; 109 | } 110 | 111 | .trigger-event-demo.input-group .form-control { 112 | outline: none; 113 | } 114 | .input-group-addon.ember-basic-dropdown-trigger { 115 | outline: none; 116 | color: #444; 117 | &:focus { 118 | background-color: lighten(#66afe9, 25%); 119 | } 120 | } 121 | 122 | .input-group--focused { 123 | border-color: #66afe9; 124 | outline: 0; 125 | box-shadow: 126 | inset 0 1px 1px rgba(0, 0, 0, 0.075), 127 | 0 0 8px rgba(102, 175, 233, 0.6); 128 | } 129 | 130 | .arrow-left-content { 131 | margin-left: 10px; 132 | background: #fff; 133 | border: 1px solid rgba(0, 0, 0, 0.15); 134 | } 135 | .arrow-left-content:after, 136 | .arrow-left-content:before { 137 | right: 100%; 138 | top: 50%; 139 | border: solid transparent; 140 | content: " "; 141 | height: 0; 142 | width: 0; 143 | position: absolute; 144 | pointer-events: none; 145 | } 146 | 147 | .arrow-left-content:after { 148 | border-color: rgba(255, 255, 255, 0); 149 | border-right-color: #fff; 150 | border-width: 10px; 151 | margin-top: -10px; 152 | } 153 | .arrow-left-content:before { 154 | border-color: rgba(0, 0, 0, 0); 155 | border-right-color: rgba(0, 0, 0, 0.15); 156 | border-width: 11px; 157 | margin-top: -11px; 158 | } 159 | -------------------------------------------------------------------------------- /ember-basic-dropdown/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { babel } from '@rollup/plugin-babel'; 2 | import { Addon } from '@embroider/addon-dev/rollup'; 3 | import sass from 'rollup-plugin-sass'; 4 | import postcss from 'postcss'; 5 | import { fileURLToPath } from 'node:url'; 6 | import { resolve, dirname } from 'node:path'; 7 | 8 | const rootDirectory = dirname(fileURLToPath(import.meta.url)); 9 | const tsConfig = resolve(rootDirectory, './tsconfig.json'); 10 | 11 | const addon = new Addon({ 12 | srcDir: 'src', 13 | destDir: 'dist', 14 | }); 15 | 16 | export default [ 17 | // Compile scss file for js import 18 | { 19 | input: './_index.scss', 20 | output: { 21 | file: './src/vendor/ember-basic-dropdown.js', 22 | assetFileNames: '[name][extname]', 23 | }, 24 | plugins: [ 25 | sass({ 26 | output: './src/vendor/ember-basic-dropdown.css', 27 | }), 28 | ], 29 | }, 30 | { 31 | input: './_index.scss', 32 | output: { 33 | file: './src/vendor/ember-basic-dropdown.js', 34 | assetFileNames: '[name][extname]', 35 | }, 36 | plugins: [ 37 | sass({ 38 | processor: (css) => 39 | postcss() 40 | .process(css, { 41 | from: undefined, 42 | }) 43 | .then((result) => result.css), 44 | }), 45 | ], 46 | }, 47 | { 48 | // This provides defaults that work well alongside `publicEntrypoints` below. 49 | // You can augment this if you need to. 50 | output: addon.output(), 51 | 52 | plugins: [ 53 | // These are the modules that users should be able to import from your 54 | // addon. Anything not listed here may get optimized away. 55 | // By default all your JavaScript modules (**/*.js) will be importable. 56 | // But you are encouraged to tweak this to only cover the modules that make 57 | // up your addon's public API. Also make sure your package.json#exports 58 | // is aligned to the config here. 59 | // See https://github.com/embroider-build/embroider/blob/main/docs/v2-faq.md#how-can-i-define-the-public-exports-of-my-addon 60 | addon.publicEntrypoints([ 61 | 'index.js', 62 | 'config.js', 63 | 'styles.js', 64 | 'components/**/*.js', 65 | 'modifiers/**/*.js', 66 | 'test-support/**/*.js', 67 | 'utils/**/*.js', 68 | 'vendor/**/*.js', 69 | ]), 70 | 71 | // These are the modules that should get reexported into the traditional 72 | // "app" tree. Things in here should also be in publicEntrypoints above, but 73 | // not everything in publicEntrypoints necessarily needs to go here. 74 | addon.appReexports(['components/**/*.js', 'modifiers/**/*.js']), 75 | 76 | // Follow the V2 Addon rules about dependencies. Your code can import from 77 | // `dependencies` and `peerDependencies` as well as standard Ember-provided 78 | // package names. 79 | addon.dependencies(), 80 | 81 | // This babel config should *not* apply presets or compile away ES modules. 82 | // It exists only to provide development niceties for you, like automatic 83 | // template colocation. 84 | // 85 | // By default, this will load the actual babel config from the file 86 | // babel.config.json. 87 | babel({ 88 | extensions: ['.js', '.gjs', '.ts', '.gts'], 89 | babelHelpers: 'bundled', 90 | }), 91 | 92 | // Ensure that standalone .hbs files are properly integrated as Javascript. 93 | addon.hbs(), 94 | 95 | // Ensure that .gjs files are properly integrated as Javascript 96 | addon.gjs(), 97 | 98 | // Emit .d.ts declaration files 99 | addon.declarations( 100 | 'declarations', 101 | `pnpm ember-tsc --declaration --project ${tsConfig}`, 102 | ), 103 | 104 | // addons are allowed to contain imports of .css files, which we want rollup 105 | // to leave alone and keep in the published output. 106 | addon.keepAssets(['**/*.css']), 107 | 108 | // Remove leftover build artifacts when starting a new build. 109 | addon.clean(), 110 | ], 111 | }, 112 | ]; 113 | -------------------------------------------------------------------------------- /docs/app/templates/public-pages.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { service } from '@ember/service'; 3 | import { action } from '@ember/object'; 4 | import { htmlSafe } from '@ember/template'; 5 | import calculatePosition, { 6 | type CalculatePositionOptions, 7 | type CalculatePositionResult, 8 | } from 'ember-basic-dropdown/utils/calculate-position'; 9 | import { LinkTo } from '@ember/routing'; 10 | import BasicDropdown, { 11 | type BasicDropdownDefaultBlock, 12 | } from 'ember-basic-dropdown/components/basic-dropdown'; 13 | import { on } from '@ember/modifier'; 14 | import { fn } from '@ember/helper'; 15 | import type RouterService from '@ember/routing/router-service'; 16 | 17 | const colors = ['orange', 'blue', 'purple', 'line', 'pink', 'red', 'brown']; 18 | const brands = colors.map((c) => `${c}-brand`); 19 | 20 | export default class extends Component { 21 | @service declare router: RouterService; 22 | 23 | colors = colors; 24 | backgrounds = this.colors.map((c) => htmlSafe(`background-color: ${c}`)); 25 | color = undefined; 26 | 27 | // Actions 28 | @action 29 | preventIfNotInIndex(e: Event) { 30 | if (this.router.currentRouteName !== 'public-pages.index') { 31 | e.stopImmediatePropagation(); 32 | } 33 | } 34 | 35 | setBrandColor(color: string, dropdown: BasicDropdownDefaultBlock<'span'>) { 36 | brands.forEach((klass) => document.body.classList.remove(klass)); 37 | document.body.classList.add(`${color}-brand`); 38 | dropdown.actions.close(); 39 | } 40 | 41 | // Methods 42 | calculatePosition( 43 | trigger: HTMLElement, 44 | content: HTMLElement, 45 | destination: HTMLElement, 46 | options: CalculatePositionOptions, 47 | ): CalculatePositionResult { 48 | const pos = calculatePosition(trigger, content, destination, options); 49 | 50 | if (pos.style.top) { 51 | pos.style.top += 3; 52 | } else { 53 | pos.style.top = 3; 54 | } 55 | 56 | return pos; 57 | } 58 | 59 | 117 | } 118 | -------------------------------------------------------------------------------- /docs/app/components/snippets/content-events-1.gts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import { action } from '@ember/object'; 3 | import { didCancel, task, timeout } from 'ember-concurrency'; 4 | import BasicDropdown, { 5 | type BasicDropdownDefaultBlock, 6 | } from 'ember-basic-dropdown/components/basic-dropdown'; 7 | import { on } from '@ember/modifier'; 8 | import { fn } from '@ember/helper'; 9 | 10 | export default class extends Component { 11 | notifications = [ 12 | { text: 'Edward' }, 13 | { text: 'Jonathan' }, 14 | { text: 'Tom' }, 15 | { text: 'Eric' }, 16 | ]; 17 | 18 | // Actions 19 | prevent(e: Event) { 20 | return e.stopImmediatePropagation(); 21 | } 22 | 23 | @action 24 | open(dropdown: BasicDropdownDefaultBlock) { 25 | void this.closeLaterTask.cancelAll(); 26 | dropdown.actions.open(); 27 | } 28 | 29 | @action 30 | async closeLater(dropdown: BasicDropdownDefaultBlock) { 31 | try { 32 | await this.closeLaterTask.perform(dropdown); 33 | } catch (e) { 34 | if (!didCancel(e)) { 35 | // re-throw the non-cancelation error 36 | throw e; 37 | } 38 | } 39 | } 40 | 41 | closeLaterTask = task(async (dropdown: BasicDropdownDefaultBlock) => { 42 | await timeout(200); 43 | dropdown.actions.close(); 44 | }); 45 | 46 | 96 | } 97 | -------------------------------------------------------------------------------- /test-app/tests/unit/utils/scroll-helpers-test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | distributeScroll, 3 | getAvailableScroll, 4 | getScrollDeltas, 5 | getScrollLineHeight, 6 | DOM_DELTA_LINE, 7 | DOM_DELTA_PAGE, 8 | DOM_DELTA_PIXEL, 9 | LINES_PER_PAGE, 10 | } from 'ember-basic-dropdown/utils/scroll-helpers'; 11 | import { module, test } from 'qunit'; 12 | 13 | module('Unit | Utility | scroll helpers', function () { 14 | test('getScrollLineHeight', function (assert) { 15 | // Depends on device and settings. 16 | const result = getScrollLineHeight(); 17 | 18 | // Also blows up on 0, which is an invalid value. 19 | assert.ok(result, 'did not throw errors'); 20 | }); 21 | 22 | test('getAvailableScroll', function (assert) { 23 | const container = document.createElement('div'); 24 | container.style.height = '100px'; 25 | container.style.overflow = 'auto'; 26 | const child = document.createElement('div'); 27 | child.style.height = '300px'; 28 | child.style.overflow = 'auto'; 29 | const grandchild = document.createElement('div'); 30 | grandchild.style.height = '500px'; 31 | grandchild.innerText = 'asdf'; 32 | 33 | child.appendChild(grandchild); 34 | container.appendChild(child); 35 | document.body.appendChild(container); 36 | 37 | let result; 38 | container.scrollTop = 0; 39 | result = getAvailableScroll(child, container); 40 | assert.strictEqual(result.deltaYNegative, 0); 41 | assert.strictEqual(result.deltaYPositive, 400); 42 | 43 | container.scrollTop = 20; 44 | result = getAvailableScroll(child, container); 45 | assert.strictEqual(result.deltaYNegative, -20); 46 | assert.strictEqual(result.deltaYPositive, 380); 47 | 48 | result = getAvailableScroll(grandchild, container); 49 | assert.strictEqual(result.deltaYNegative, -20); 50 | assert.strictEqual(result.deltaYPositive, 380); 51 | }); 52 | 53 | test('distributeScroll', function (assert) { 54 | const container = document.createElement('div'); 55 | container.style.height = '100px'; 56 | container.style.overflow = 'auto'; 57 | const child = document.createElement('div'); 58 | child.style.height = '300px'; 59 | child.style.overflow = 'auto'; 60 | const grandchild = document.createElement('div'); 61 | grandchild.style.height = '500px'; 62 | grandchild.innerText = 'asdf'; 63 | 64 | child.appendChild(grandchild); 65 | container.appendChild(child); 66 | document.body.appendChild(container); 67 | 68 | container.scrollTop = 20; 69 | child.scrollTop = 20; 70 | 71 | distributeScroll(0, -30, grandchild, container); 72 | assert.strictEqual(container.scrollTop, 10); 73 | assert.strictEqual(child.scrollTop, 0); 74 | 75 | distributeScroll(0, 40, grandchild, container); 76 | assert.strictEqual(container.scrollTop, 10); 77 | assert.strictEqual(child.scrollTop, 40); 78 | }); 79 | 80 | test('getScrollDeltas DOM_DELTA_PIXEL', function (assert) { 81 | const originalDeltaX = 25; 82 | const originalDeltaY = 15; 83 | const { deltaX, deltaY } = getScrollDeltas({ 84 | deltaX: originalDeltaX, 85 | deltaY: originalDeltaY, 86 | deltaMode: DOM_DELTA_PIXEL, 87 | }); 88 | assert.strictEqual(deltaX, originalDeltaX); 89 | assert.strictEqual(deltaY, originalDeltaY); 90 | }); 91 | 92 | test('getScrollDeltas DOM_DELTA_LINE', function (assert) { 93 | const scrollLineHeight = getScrollLineHeight() || 0; 94 | const originalDeltaX = 25; 95 | const originalDeltaY = 15; 96 | const { deltaX, deltaY } = getScrollDeltas({ 97 | deltaX: originalDeltaX, 98 | deltaY: originalDeltaY, 99 | deltaMode: DOM_DELTA_LINE, 100 | }); 101 | assert.strictEqual(deltaX, originalDeltaX * scrollLineHeight); 102 | assert.strictEqual(deltaY, originalDeltaY * scrollLineHeight); 103 | }); 104 | 105 | test('getScrollDeltas DOM_DELTA_PAGE', function (assert) { 106 | const scrollLineHeight = getScrollLineHeight() || 0; 107 | const originalDeltaX = 25; 108 | const originalDeltaY = 15; 109 | const { deltaX, deltaY } = getScrollDeltas({ 110 | deltaX: originalDeltaX, 111 | deltaY: originalDeltaY, 112 | deltaMode: DOM_DELTA_PAGE, 113 | }); 114 | assert.strictEqual( 115 | deltaX, 116 | originalDeltaX * scrollLineHeight * LINES_PER_PAGE, 117 | ); 118 | assert.strictEqual( 119 | deltaY, 120 | originalDeltaY * scrollLineHeight * LINES_PER_PAGE, 121 | ); 122 | }); 123 | }); 124 | --------------------------------------------------------------------------------