├── 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 |
4 | {{outlet}}
5 |
6 |
7 |
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 |
4 |
5 | Click me
6 | Hello world
7 |
8 |
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 |
4 |
5 | Click me
6 |
7 |
8 | You can't see this
9 |
10 |
11 |
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
5 | My custom content
6 | 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
5 | My custom trigger
6 | satisfies TemplateOnlyComponent;
7 |
--------------------------------------------------------------------------------
/docs/app/components/snippets/position-2.gts:
--------------------------------------------------------------------------------
1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown';
2 |
3 |
4 |
5 | Click me
6 |
7 |
8 | I'm not in the root of the app
9 |
10 |
11 |
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 |
4 |
5 | Click me!
6 |
7 |
8 | I'm not in the root of the app
9 |
10 |
11 |
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 |
7 |
8 |
9 |
10 | {{outlet}}
11 |
12 | ,
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 |
4 |
5 | Animate me
6 |
7 |
8 | Miguel
9 | Matthew
10 | Dan
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/docs/app/components/snippets/how-to-use-it-3.gts:
--------------------------------------------------------------------------------
1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown';
2 |
3 |
4 |
5 | +
6 |
7 |
8 |
9 |
10 |
11 |
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 |
4 |
5 | Click me
6 |
7 |
8 | By default the overlays is black and transparent, but you can change that.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/docs/app/templates/public-pages/cookbook.gts:
--------------------------------------------------------------------------------
1 | import { LinkTo } from '@ember/routing';
2 |
3 |
4 |
5 |
6 |
7 | Usage
8 | without trigger
9 |
10 |
13 |
14 |
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 |
6 |
7 | Open me, i'm in shadow
8 | Content of the dropdown in shadow
9 |
10 | ,
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 |
4 |
5 |
6 | Click me
7 |
8 |
9 |
10 |
11 | I look like bootstrap, right?
12 | You can style me however you want
13 |
14 |
15 |
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 |
5 |
6 |
11 | Click me
12 |
13 |
14 |
15 | I was opened with a custom trigger
16 |
17 |
18 |
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 |
18 |
19 | {{~! ~}}
20 |
25 | {{~! ~}}
26 |
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/docs/app/templates/helpers-testing.gts:
--------------------------------------------------------------------------------
1 | import BasicDropdown from 'ember-basic-dropdown/components/basic-dropdown';
2 |
3 |
4 | Helpers testing
5 |
6 |
7 |
8 |
9 | Click me
10 |
11 |
12 | Hello world 0!
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | Click me
21 |
22 |
23 | Hello world 1!
24 |
25 |
26 |
27 |
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 |
5 |
6 |
7 |
12 |
17 | Click me
18 |
19 |
20 |
21 |
22 | I don't like the button, I like the input.
23 |
24 |
25 |
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 |
12 |
16 |
17 |
18 |
22 | Do not click me
23 |
24 |
25 |
26 | hello world
27 |
28 |
29 |
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 |
5 |
22 |
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 |
4 |
5 |
6 |
7 |
8 | Mousedown (default)
9 |
10 |
11 |
12 | I open as soon as you press the mouse button
13 |
14 |
15 |
16 |
17 |
18 | Click
19 |
20 |
21 | I only open when you release the mouse button
22 |
23 |
24 |
25 |
26 |
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 |
4 |
25 |
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 |
16 |
20 |
21 |
22 |
26 | Click me
27 |
28 |
29 |
30 | Don't you love those little keys?
31 |
32 |
33 |
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 |
26 |
27 | Click me
28 |
29 |
30 | {{#if this.loadUsers.isRunning}}
31 |
32 | {{else}}
33 | {{#each this.users as |user|}}
34 | {{user.name}} - {{user.assignment}}
35 | {{/each}}
36 | {{/if}}
37 |
38 |
39 |
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 |
28 |
29 |
30 | {{#if this.shadow}}
31 | {{#in-element this.shadow}}
32 | {{#each this.getStyles as |styleHref|}}
33 |
34 | {{/each}}
35 | {{yield}}
36 | {{/in-element}}
37 | {{/if}}
38 |
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 |
6 | Disabled
7 |
8 |
9 | If you disable the dropdown, it will not open or close by any mean, mouse,
10 | keyboard or touch screen.
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | Note that being disabled does not prevent the dropdown or any of its
19 | sub-components from firing events like
20 | onMouseEnter.
21 |
22 |
23 |
24 | The component takes care by default of usual good practices, removing the
25 | tabindex
26 | of the trigger and set
27 | [aria-disabled="true"]
28 | to assist screen readers.
29 |
30 |
31 |
32 | < Position
36 | Overlays >
40 |
41 |
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 |
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 |
26 |
27 | Click me
28 |
29 |
30 | Hello from the right!
31 |
32 | See? It wasn't that hard.
33 |
34 |
35 |
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 |
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 |
21 | Focus alternatively the input and the button next to it
22 |
45 |
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 |
34 |
35 |
36 | {{#if this.loadUsersAndOpen.isRunning}}
37 | Loading...
38 | {{else}}
39 | Click me
40 | {{/if}}
41 |
42 |
43 |
44 | {{#each this.loadUsersAndOpen.lastSuccessful.value as |user|}}
45 | {{user.name}} - {{user.assignment}}
46 | {{/each}}
47 |
48 |
49 |
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 |
20 |
21 | Click me
22 |
23 |
24 |
25 | Who you love more?
26 |
27 |
28 |
35 | Dad
36 |
37 |
38 |
45 | Mom
46 |
47 |
48 |
49 |
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 |
5 | Styles
6 |
7 |
8 | Since this component doesn't any visual theme, you can apply styles to it
9 | just with plain CSS or even adding the classes your favourite CSS framework
10 | gives you.
11 |
12 |
13 |
14 | If don't use any css pre-processor this is all. If you do use SASS, the
15 | addon will know it and will have to
16 | @use
17 | the styles explicitly. This gives you the chance to set a few variables that
18 | Ember Basic Dropdown will use.
19 |
20 |
21 |
22 | There is only four variables you can tweak (Sass syntax)
23 |
24 |
25 |
26 |
27 |
28 | If by example you want to change the colour of the overlay to be blue, you
29 | could do this in your
30 | app.scss.
31 |
32 |
33 |
34 |
35 |
36 | In the next sections we'll give in more involved customizations.
37 |
38 |
39 |
40 | < Overlays
44 | Custom position >
48 |
49 |
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 |
53 |
54 | {{~! ~}}{{this.prismCode}}{{~! ~}}
55 |
56 |
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 |
28 |
37 | Enable dropdown
38 |
39 |
40 |
41 |
46 | Can you open me?
47 |
48 |
49 |
50 | You solved the puzzle!
51 |
52 |
53 |
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 |
43 | {{#if (not this.shadowDom)}}
44 | {{yield}}
45 | {{else if this.shadowRootElement}}
46 | {{#in-element this.shadowRootElement}}
47 | {{#each this.getStyles as |styleHref|}}
48 |
49 | {{/each}}
50 | {{yield}}
51 | {{/in-element}}
52 | {{/if}}
53 |
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 |
6 | Overlays
7 |
8 |
9 | By default the dropdown renders floating above the rest of your page but it
10 | doesn't prevent the user from clicking on any other item. Overlays can fix
11 | that and make dropdowns look cooler.
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | What do you have to know:
20 |
21 |
22 |
23 |
24 | By default overlays have a semitransparent black background, but you can
25 | change that using the
26 | $ember-basic-dropdown-overlay-background
27 | SASS variable or target the
28 | .ember-basic-dropdown-overlay
29 |
30 |
31 | By default overlays have
32 | pointer-events: none, so it is transparent to clicks but you
33 | can change that using the
34 | $ember-basic-dropdown-overlay-pointer-events
35 | SASS variable or target the same class.
36 |
37 |
38 |
39 |
40 | In the next section we'll talk more how to customize the styles.
41 |
42 |
43 |
44 | < Disabled
48 | Styles >
52 |
53 |
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 |
5 | Migrate from 7.0 to 8.0
6 |
7 | The addon is now a
8 | V2
9 | Embroider Addon . The following changes are necessary:
10 |
11 |
12 |
13 | Add following line to your application.hbs
14 |
19 |
20 |
21 | Vanilla JS: If you have used vanilla js you must now import the css in
22 | app.js
23 | manually like (for other css adding examples see under installation)
24 |
25 |
26 |
27 | LESS: If you are using with less you need to add
28 | lessOptions
29 | into your
30 | ember-cli-build.js
31 | file
32 |
33 |
34 |
35 | Typescript: There were added / modified / fixed the typescript
36 | declarations. This could be braking for consumer app
37 |
38 |
39 |
40 |
41 | < Migrate from 8.0 to 9.0
45 | Test helpers >
49 |
50 |
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 |
6 | Animations
7 |
8 |
9 | Do you like animations in your dropdowns? Me too. Let's define some
10 |
11 |
12 |
13 | It is that simple as create a CSS3 animation using the
14 | ember-basic-dropdown--transitioning-in,
15 | ember-basic-dropdown--transitioned-in
16 | and
17 | ember-basic-dropdown--transitioning-out
18 | classes that ember-basic-dropdown gives you automatically.
19 |
20 |
21 |
22 | You will probably also want to use
23 | .ember-basic-dropdown-content--below
24 | and
25 | .ember-basic-dropdown-content--above
26 | to have different animations depending on where the dropdown is positioned.
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | There is nothing else to know about animations, other that you have to use
35 | CSS
36 | animations , not transitions.
37 |
38 |
39 |
40 | The dropdown takes care of creating a clone when closing the dropdown so you
41 | animation works in both directions.
42 |
43 |
44 |
45 | < Custom position
49 | Migrate from 7.0 to 8.0 >
53 |
54 |
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 |
5 | Test helpers
6 |
7 |
8 |
9 |
10 | Ember Basic Dropdown bundles some handy test helpers (clickDropdown
12 | and
13 | tapDropdown) that make it easier to simulate user interaction
14 | in acceptance tests.
15 |
16 |
17 |
18 | You can just have to import them at the top of your tests and call them
19 | preceded by
20 | await.
21 |
22 |
23 |
24 |
25 | clickTrigger(scope = null, eventOptions)
26 |
27 |
28 | Simulates a click to open or close the dropdown. As all integration test
29 | helpers is already runloop aware, so you don't need to wrap it in
30 | Ember.run.
31 |
32 |
33 |
34 | In case there is more than one dropdown rendered at the same time you can
35 | pass a string with the scope to trigger it over the desired one.
36 |
37 |
38 |
39 |
40 | tapTrigger(scope = null, eventOptions)
41 |
42 |
43 | Identical to
44 | clickTrigger
45 | but simulates a tap instead.
46 |
47 |
48 |
49 |
50 |
51 | < Migrate from 7.0 to 8.0
55 | API reference >
59 |
60 |
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 |
50 |
51 |
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 |
46 |
51 | Click me
52 |
53 |
54 | {{#each this.names as |name|}}
55 | {{name}}
56 |
57 | {{/each}}
58 |
59 |
60 |
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 |
6 | Content events
7 |
8 |
9 | Like the trigger, the dropdown accepts function to bind a few handy events.
10 |
11 |
12 | onFocusIn/onFocusOut(dropdown, event)
13 |
14 |
15 | Those events are just identical to the ones in the trigger.
16 |
17 |
18 | onMouseEnter / onMouseLeave(dropdown, event)
19 |
20 |
21 | Used in conjunction with the same events in the trigger it quite easy to
22 | make a dropdown that opens when you hover the trigger and closes leave it,
23 | but stays open if you move from the trigger to the content.
24 |
25 |
26 |
27 | You can even delay the closing a bit to allow the users to briefly pass
28 | outside the boundaries of the dropdown without closing it. Think the navbar
29 | of your favourite social network:
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | Let's break this down.
38 |
39 |
40 |
41 | First we
42 | <dd.Trigger \{{on "mousedown" this.prevent\\}}>
43 | neglect mouse input: Open/close both by click and hover will trip our users.
44 |
45 |
46 |
47 | Then
48 | \{{on "mouseenter" (fn this.open dd)}}
49 | and
50 | \{{on "mouseleave" (fn this.closeLater dd)}}
51 | open a close the dropdown, but we we leave we don't close immediately.
52 | Instead we delay the close a few milliseconds as a grace period, allowing
53 | the user to transition from the trigger to the content even if the
54 | trajectory of the mouse is not perfect.
55 |
56 |
57 |
58 | If before that grace period they enter again the boundaries of the
59 | component, we cancel the scheduled close. This would be much cleaner using
60 | ember-concurrency
61 | tasks.
62 |
63 |
64 |
65 | In the following chapters we will learn how to customize the dropdown
66 | behaviour with all the different options.
67 |
68 |
69 |
70 | < Trigger events
74 |
75 |
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 |
6 | Usage without trigger
7 |
8 |
9 | Use the component without a trigger? Is that possible?
10 |
11 |
12 |
13 | Yes, it is. You don't need to invoke the
14 | <dropdown.trigger>
15 | component to make the dropdown work. The yielded
16 | dropdown
17 | has actions you can invoke from any other item. Yes, it is. You don't need
18 | to invoke the
19 | <dropdown.trigger>
20 | component to make the dropdown work. The yielded
21 | dropdown
22 | has actions you can invoke from any other item.
23 |
24 |
25 |
26 | Sometimes you just cannot achieve what you want with this approach, so you
27 | need to step down and wire things yourself.
28 |
29 |
30 |
31 | If you invoke the
32 | open
33 | or
34 | toggle
35 | action from say, a button, the dropdown is going to open and will take as
36 | the element to be anchored to element with
37 | data-ebd-id="\{{dropdown.uniqueId}}-trigger".
38 |
39 |
40 |
41 | What kind of things can you do with this?
42 |
43 |
44 |
45 | By example, you can open or close the component when an item is clicked but
46 | make the dropdown attach itself to an entirely different item in the page.
47 |
48 |
49 |
50 | Let's create an input with a button. When that button is clicked the drodown
51 | will open below the input, not not below the button that you clicked.
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | Remember that almost always you will want to use the provided trigger
60 | component
61 | <dropdown.trigger>
62 | because it takes care of all the A11y, bindings and classes for you, but
63 | when you can't it's good to know that you can still use your own markup and
64 | wire things together yourself.
65 |
66 |
67 |
68 | A good middle-ground is to apply the same trigger modifier that the
69 | component uses, but to your element or component. It will get all of the
70 | same a11y and bindings that the
71 | <dropdown.trigger>
72 | component gets.
73 |
74 |
75 |
76 |
77 |
78 |
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 |
5 | Installation
6 |
7 |
8 | To install ember-basic-dropdown, run the following command in your ember
9 | project directory
10 |
11 |
12 |
13 |
14 |
$ pnpm install ember-basic-dropdown
15 |
16 |
17 |
18 |
19 | After the installation you need to add the following lines somewhere in your
20 | app.js/ts.
21 |
22 |
23 |
28 |
29 |
30 | Then you need to add the following lines somewhere in your templates where
31 | you want to render the dropdown content into e.g. your
32 | application.gjs/gts. In this component will be rendered the
33 | dropdown content.
34 |
35 |
36 |
41 |
42 |
43 | If you use vanilla CSS, you need to add the following line into
44 | app.js
45 | or in any route/controller/component
46 | .js/.ts
47 | file:
48 |
49 |
50 |
51 |
52 |
53 | Instead of adding the styling in an
54 | .js
55 | file and depending from your build config you can also add the css in any
56 | template/component css file by using following line
57 |
58 |
59 |
64 |
65 |
66 | However, if you are using SASS or LESS you need to add an import statement
67 | to your styles.
68 |
69 |
70 |
75 |
76 |
77 | If you are using LESS there is also necessary to register the
78 | paths
79 | in
80 | lessOptions
81 |
82 |
83 |
84 |
85 |
86 | The styles of the addon are
87 | very minimal
88 | and deal mostly with positioning. You can tweak a couple things but we'll
89 | get to that later.
90 |
91 |
92 |
93 | Now let's learn the API of the component.
94 |
95 |
96 |
97 | <
98 | Overview
99 | How to use it >
103 |
104 |
105 |
--------------------------------------------------------------------------------
/docs/app/templates/public-pages/docs/index.gts:
--------------------------------------------------------------------------------
1 | import { LinkTo } from '@ember/routing';
2 |
3 |
4 | Overview
5 |
6 | I'm going to tell you the story of how this dropdown addon was born.
7 |
8 |
9 | I was building a select component but I realized that a select is nothing
10 | more than a dropdown with some extra stuff on top. And datepickers too. And
11 | colorpickers, contextual menus and many other widgets.
12 |
13 |
14 |
15 | Essentially, any floating box of content that is opened when you interact
16 | with a trigger is basically a dropdown. Dropdowns are the foundation to at
17 | least half a dozen common UI widgets we use daily, so I decided to shape
18 | this addon in a way that was easy to reuse and customize to create those
19 | other widgets.
20 |
21 |
22 |
23 | But if a dropdown is just a floating box activated by a trigger,
24 | why would you want to use a third part addon for such a simple thing?
25 |
26 |
27 |
28 | Well, there are two kinds of people. Those who think that dropdowns
29 | are an easy thing and those who have actually built one.
30 |
31 |
32 |
33 | When I transitioned from the first kind to the second I learned a lot of
34 | stuff.
35 |
36 |
37 |
38 | Absolutely position elements in a performant way using the runloop
39 | Overcoming z-index limitations
40 | How useful MutationObservers and DOM Events can be
41 | Proper detection of taps and touch scroll
42 | Animating elements being removed from the screen
43 | Chasing memory leaks when global events are not properly removed
44 | Accessibility and keyboard navigation
45 |
46 |
47 |
48 | After I built the initial version, I started to build other widgets on top
49 | and I had to step back a few times and identify where it wasn't flexible
50 | enough. This process ended up in a component that distilled the essence of
51 | what a dropdown is with as few assumptions as possible.
52 |
53 |
54 |
55 | I honestly want to save you time and tears. I've been there.
56 |
57 |
58 |
59 | This component is a
60 | building block
61 | for you to build other components on top, so it prioritizes flexibility and
62 | explicitness over succinctness in its API, but still allows allows basic
63 | usage out of the box with no ceremony.
64 |
65 |
66 |
67 | Another thing to note is that this component doesn't have any theme by
68 | default, so unless you do something about it, it will look pretty ugly. The
69 | few styles it has are only to deal with positioning.
70 |
71 |
72 | I hope you like it.
73 |
74 |
75 | Installation >
79 |
80 |
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 |
8 | Dropdown events
9 |
10 |
11 | Although the top-level component is tagless, it does fire a few events that
12 | you can use to react to changes in the state of the dropdown.
13 |
14 |
15 |
16 | This has no mystery.
17 |
18 |
19 | onOpen(dropdown, event?)
20 |
21 |
22 | Pretty self-explanatory. The
23 | dropdown
24 | argument in the signature is the public API of the component. The
25 | event
26 | argument will be passed if this event is fired as a consequence of another
27 | event (e.g. a click), but will be undefined if it was fired
28 | programmatically.
29 |
30 |
31 | What kinds of things you can do with this event?
32 |
33 |
34 | In general when you want to do something outside the component when this
35 | loads. By example, you can delay the loading of some data until the dropdown
36 | is opened.
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | There is something you should know about this hook: If you
45 | return false;
46 | from it you will prevent the component from opening.
47 |
48 |
49 |
50 | Let's use this feature along with the public API and event received as
51 | aguments to do a nifty trick. We are going to iterate over the example above
52 | so we prevent the component from opening, load the users and once loaded we
53 | open it.
54 |
55 |
56 |
57 |
58 |
59 |
60 | For the record, I don't think this is good UX
61 |
62 | onClose(dropdown, event?)
63 |
64 |
65 | Symmetrically, you can perform on action when the dropdown is closed. For
66 | example, save the a user selection, and you can also
67 | return false
68 | to prevent the component from closing.
69 |
70 |
71 |
72 | Example: Create a dropdown with some checkboxes inside, and don't allow it
73 | to close until one checkbox is selected.
74 |
75 |
76 |
77 |
78 |
79 |
80 | Cruel, isn't it?
81 |
82 |
83 | Those were the events fired by the top-level component, now let's go deep on
84 | the events that are fired by the trigger.
85 |
86 |
87 |
88 | < How to use it
92 | Trigger events >
96 |
97 |
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 |
60 |
61 |
62 |
63 |
Docs
67 |
Cookbook
71 |
Github
75 |
76 |
77 |
82 |
83 |
84 | Basic
85 | {{! template-lint-disable no-pointer-down-event-binding }}
86 |
91 | Dropdown
92 |
93 |
94 |
95 | {{#each this.colors as |color|}}
96 |
101 | {{/each}}
102 |
103 |
104 |
105 |
106 |
107 |
108 | {{outlet}}
109 |
110 |
116 |
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 |
47 |
48 |
49 | Bookface
50 |
51 |
52 | {{! template-lint-disable no-pointer-down-event-binding }}
53 |
58 |
59 |
63 |
64 |
65 | {{this.notifications.length}}
66 |
67 |
68 |
69 |
74 |
75 | {{#each this.notifications as |notification|}}
76 |
77 |
81 |
85 |
86 | {{notification.text}}
87 |
88 | {{/each}}
89 |
90 |
91 |
92 |
93 |
94 |
95 |
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 |
--------------------------------------------------------------------------------