├── .editorconfig ├── .github └── workflows │ ├── ci.yml │ ├── deprecate.yml │ └── mirror.yml ├── .gitignore ├── .npmrc ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── RELEASE.md ├── ember-element-helper ├── .eslintignore ├── .eslintrc.cjs ├── .npmignore ├── CHANGELOG.md ├── README.md ├── addon-main.cjs ├── babel.config.json ├── package.json ├── rollup.config.mjs ├── src │ ├── helpers │ │ └── element.ts │ ├── index.ts │ └── template-registry.ts ├── tsconfig.json └── types │ └── index.d.ts ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── test-app ├── .editorconfig ├── .ember-cli ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .stylelintignore ├── .stylelintrc.js ├── .template-lintrc.js ├── .watchmanconfig ├── README.md ├── app ├── app.js ├── components │ ├── element-receiver.hbs │ └── element-receiver.ts ├── index.html ├── router.js ├── styles │ └── app.css └── templates │ └── application.hbs ├── config ├── ember-cli-update.json ├── ember-try.js ├── environment.js ├── optional-features.json └── targets.js ├── ember-cli-build.js ├── package.json ├── public └── robots.txt ├── testem.js ├── tests ├── helpers │ └── index.js ├── index.html ├── integration │ ├── .gitkeep │ └── helpers │ │ └── element-test.js ├── test-helper.js └── unit │ └── .gitkeep ├── tsconfig.json └── types └── global.d.ts /.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 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: {} 5 | pull_request: {} 6 | push: 7 | branches: 8 | - main 9 | - master 10 | tags: 11 | - v* 12 | schedule: 13 | - cron: "0 0 * * *" 14 | 15 | concurrency: 16 | group: ci-${{ github.head_ref || github.ref }} 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | test: 21 | name: "Tests" 22 | runs-on: ubuntu-latest 23 | timeout-minutes: 10 24 | 25 | steps: 26 | - name: Setup 27 | uses: wyvox/action@v1 28 | - name: Lint 29 | run: pnpm lint 30 | - name: Run Tests 31 | run: pnpm test 32 | 33 | floating: 34 | name: "Floating Dependencies" 35 | runs-on: ubuntu-latest 36 | timeout-minutes: 10 37 | 38 | steps: 39 | - name: Setup 40 | uses: wyvox/action@v1 41 | with: 42 | pnpm-args: --no-frozen-lockfile 43 | - name: Run Tests 44 | run: pnpm test 45 | 46 | try-scenarios: 47 | name: ${{ matrix.try-scenario }} 48 | runs-on: ubuntu-latest 49 | needs: "test" 50 | timeout-minutes: 10 51 | 52 | strategy: 53 | fail-fast: false 54 | matrix: 55 | try-scenario: 56 | - ember-lts-3.28 57 | - ember-lts-4.4 58 | - ember-lts-4.8 59 | - ember-lts-4.12 60 | - ember-5.0 61 | - ember-release 62 | - ember-beta 63 | - ember-canary 64 | - ember-classic 65 | - embroider-safe 66 | - embroider-optimized 67 | 68 | steps: 69 | - name: Setup 70 | uses: wyvox/action@v1 71 | - name: Try Scenario 72 | run: pnpm exec ember try:one ${{ matrix.try-scenario }} --skip-cleanup 73 | working-directory: test-app 74 | 75 | typecheck: 76 | name: "${{ matrix.typescript-scenario }}" 77 | runs-on: ubuntu-latest 78 | needs: "test" 79 | timeout-minutes: 5 80 | continue-on-error: true 81 | strategy: 82 | fail-fast: true 83 | matrix: 84 | typescript-scenario: 85 | - typescript@4.8 86 | - typescript@4.9 87 | - typescript@5.0 88 | - typescript@5.1 89 | 90 | steps: 91 | - name: Setup 92 | uses: wyvox/action@v1 93 | - name: "Change TS to ${{ matrix.typescript-scenario }}" 94 | run: "pnpm add --save-dev ${{ matrix.typescript-scenario}}" 95 | working-directory: ./test-app 96 | - name: "Type checking" 97 | run: | 98 | pnpm --filter "test-app*" exec tsc -v; 99 | pnpm --filter "test-app*" exec glint --version; 100 | pnpm --filter "test-app*" exec glint; 101 | 102 | publish: 103 | name: Publish 104 | needs: test 105 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') 106 | runs-on: ubuntu-latest 107 | steps: 108 | - name: Setup 109 | uses: wyvox/action@v1 110 | - name: Set up npm 111 | run: | 112 | echo "//registry.npmjs.org/:_authToken=${NPM_AUTH_TOKEN}"> ~/.npmrc 113 | npm whoami 114 | env: 115 | NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 116 | - name: Publish to npm 117 | # NOTE: --no-git-checks due to publish on tag bug 118 | # https://github.com/apimda/npm-layer-version/pull/2 119 | # bug report: https://github.com/pnpm/pnpm/issues/5894 120 | run: pnpm publish --no-git-checks 121 | working-directory: ember-element-helper 122 | -------------------------------------------------------------------------------- /.github/workflows/deprecate.yml: -------------------------------------------------------------------------------- 1 | name: Deprecate "master" branch 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | on-push: 12 | runs-on: ubuntu-latest 13 | if: ${{ github.event_name == 'push' }} 14 | steps: 15 | - name: Deprecation 16 | uses: peter-evans/commit-comment@v1 17 | with: 18 | body: | 19 | Hello @${{ github.event.sender.login }}! 20 | 21 | I see that you have pushed some commits to the "master" branch. We are in the process of renaming the "master" branch to "main" in this repository. 22 | 23 | :warning: **The "master" branch is deprecated and will be removed from this repository in the future.** 24 | 25 | Please migrate your local repository by renaming the "master" branch to "main": 26 | 27 | ```bash 28 | $ cd my-git-project 29 | $ git checkout master 30 | $ git branch -m main 31 | $ git branch -u origin/main 32 | ``` 33 | 34 | Before merging pull requests, ensure their base branch is set to "main" instead of "master". For more information on how to do this, refer to [this GitHub support article][1]. 35 | 36 | [1]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-base-branch-of-a-pull-request 37 | 38 | on-pull-request: 39 | runs-on: ubuntu-latest 40 | if: ${{ github.event_name == 'pull_request' }} 41 | env: 42 | DEPRECATION_MESSAGE: | 43 | Hello @${{ github.event.sender.login }}! 44 | 45 | I see that you have opened a pull request against the "master" branch. We are in the process of renaming the "master" branch to "main" in this repository. 46 | 47 | :warning: **The "master" branch is deprecated and will be removed from this repository in the future.** 48 | 49 | Please migrate your local repository by renaming the "master" branch to "main": 50 | 51 | ```bash 52 | $ cd my-git-project 53 | $ git checkout master 54 | $ git branch -m main 55 | $ git branch -u origin/main 56 | ``` 57 | 58 | Please also set the base branch for this pull request to "main" instead of "master". For more information on how to do this, refer to [this GitHub support article][1]. 59 | 60 | [1]: https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/changing-the-base-branch-of-a-pull-request 61 | steps: 62 | - name: Deprecation 63 | if: ${{ github.event.pull_request.head.repo.fork == false }} 64 | uses: peter-evans/create-or-update-comment@v1 65 | with: 66 | issue-number: ${{ github.event.number }} 67 | body: ${{ env.DEPRECATION_MESSAGE }} 68 | - name: Deprecation 69 | if: ${{ github.event.pull_request.head.repo.fork == true }} 70 | run: | 71 | echo "$DEPRECATION_MESSAGE" 72 | echo '::error::Please set the base branch for this pull request to "main" instead of "master".' 73 | exit 1 74 | -------------------------------------------------------------------------------- /.github/workflows/mirror.yml: -------------------------------------------------------------------------------- 1 | name: Mirror master and main branches 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | 8 | jobs: 9 | mirror: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | - name: Push 17 | run: git push origin HEAD:master HEAD:main 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist/ 5 | declarations/ 6 | tmp/ 7 | 8 | # dependencies 9 | node_modules/ 10 | 11 | # misc 12 | .env* 13 | .pnp* 14 | .sass-cache 15 | .eslintcache 16 | connect.lock 17 | coverage/ 18 | libpeerconnection.log 19 | npm-debug.log* 20 | testem.log 21 | yarn-error.log 22 | 23 | # ember-try 24 | .node_modules.ember-try/ 25 | npm-shrinkwrap.json.ember-try 26 | package.json.ember-try 27 | package-lock.json.ember-try 28 | yarn.lock.ember-try 29 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | public-hoist-pattern[]=*prettier* 2 | public-hoist-pattern[]=*eslint* 3 | public-hoist-pattern[]=@typescript-eslint/* 4 | public-hoist-pattern[]=*ember-template-lint* 5 | public-hoist-pattern[]=*stylelint* 6 | public-hoist-pattern[]=*@glint* 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How To Contribute 2 | 3 | ## Installation 4 | 5 | * `git clone ` 6 | * `cd ember-element-helper` 7 | * `pnpm install` 8 | 9 | ## Linting 10 | 11 | * `pnpm lint` 12 | * `pnpm lint:fix` 13 | 14 | ## Running tests 15 | 16 | * `pnpm test` – Runs the test suite on the current Ember version 17 | * `pnpm test --server` – Runs the test suite in "watch mode" 18 | * `pnpm test:all` – Runs the test suite against multiple Ember versions 19 | 20 | ## Running the dummy application 21 | 22 | * `ember serve` 23 | * Visit the dummy application at [http://localhost:4200](http://localhost:4200). 24 | 25 | For more information on using ember-cli, visit [https://ember-cli.com/](https://ember-cli.com/). 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-element-helper 2 | 3 | [![Build Status](https://github.com/tildeio/ember-element-helper/actions/workflows/ci.yml/badge.svg)](https://github.com/tildeio/ember-element-helper/actions/workflows/ci.yml) 4 | 5 | Dynamic element helper for Glimmer templates. 6 | 7 | This addon provides a ~~polyfill~~ high fidelity reference implementation of 8 | [RFC #389](https://github.com/emberjs/rfcs/pull/389), including the proposed 9 | amendments in [RFC PR #620](https://github.com/emberjs/rfcs/pull/620). 10 | 11 | Please note that while [RFC #389](https://github.com/emberjs/rfcs/pull/389) 12 | has been approved, it has not been implemented in Ember.js yet. As such, the 13 | feature is still subject to change based on implementation feedback. 14 | 15 | When this feature is implemented in Ember.js, we will release a 1.0 version of 16 | this addon as a true polyfill for the feature, allowing the feature to be used 17 | on older Ember.js versions and be completely inert on newer versions where the 18 | official implementation is available. 19 | 20 | ## Compatibility 21 | 22 | * Ember.js v3.28 or above 23 | * Ember CLI v3.28 or above 24 | * Node.js v12 or above 25 | 26 | ## Limitations 27 | 28 | This implementation has the following known limitations: 29 | 30 | * By default, an auto-generated `id` attribute will be added to the element 31 | (e.g. `id="ember123"`). It is possible to override this by providing an 32 | `id` attribute when invoking the component (e.g. ``). 33 | However, it is not possible to remove the `id` attribute completely. The 34 | proposed helper will not have this behavior, as such this should not be 35 | relied upon (e.g. in CSS and `qunit-dom` selectors). 36 | 37 | * The element will have an `ember-view` class (i.e. `class="ember-view"`). 38 | This is in addition and merged with the class attribute provided when 39 | invoking the component (e.g. `` will result in 40 | something like `
`). It is not possible 41 | to remove the `ember-view` class. The proposed helper will not have this 42 | behavior, as such this should not be relied upon (e.g. in CSS and `qunit-dom` 43 | selectors). 44 | 45 | * In Ember versions before 3.11, modifiers cannot be passed to the element, 46 | even when addons such as the [modifier manager](https://github.com/ember-polyfills/ember-modifier-manager-polyfill) 47 | and [on modifier](https://github.com/buschtoens/ember-on-modifier) polyfills 48 | are used. Doing so requires [RFC #435](https://github.com/emberjs/rfcs/blob/master/text/0435-modifier-splattributes.md) 49 | which is first available on Ember 3.11. This is an Ember.js limitation, 50 | unrelated to this addon. 51 | 52 | ## Installation 53 | 54 | ``` 55 | ember install ember-element-helper 56 | ``` 57 | 58 | ## Usage 59 | 60 | ```hbs 61 | {{#let (element this.tagName) as |Tag|}} 62 | hello world! 63 | {{/let}} 64 | ``` 65 | 66 | You can also pass around the result of invoking this helper into any components 67 | that accepts "contextual components" as arguments: 68 | 69 | ```hbs 70 | 71 | ``` 72 | 73 | ```hbs 74 | {{!-- in my-component.hbs --}} 75 | {{#let @tag as |Tag|}} 76 | hello world! 77 | {{/let}} 78 | 79 | {{!-- ...or more directly... --}} 80 | <@tag class="my-tag">hello world! 81 | ``` 82 | 83 | ### Single File Components 84 | 85 | Using the `(element)` helper with [first class component 86 | templates](http://emberjs.github.io/rfcs/0779-first-class-component-templates.html): 87 | 88 | ```gjs 89 | import { element } from 'ember-element-helper'; 90 | 91 | 96 | ``` 97 | 98 | ### Glint Usage in Classic Mode 99 | 100 | In order to use a typed `(element)` helper in classic mode, you need to import 101 | the addon's glint template registry and extend your app's registry declaration 102 | as described in the [Using 103 | Addons](https://typed-ember.gitbook.io/glint/using-glint/ember/using-addons#using-glint-enabled-addons) 104 | documentation: 105 | 106 | ```ts 107 | import '@glint/environment-ember-loose'; 108 | import type EmberElementHelperRegistry from 'ember-element-helper/template-registry'; 109 | 110 | declare module '@glint/environment-ember-loose/registry' { 111 | export default interface Registry extends EmberElementHelperRegistry, /* other addon registries */ { 112 | // local entries 113 | } 114 | } 115 | ``` 116 | 117 | > **Note:** Glint itself is still under active development, and as such breaking changes might occur. 118 | > Therefore, Glint support by this addon is also considered experimental, and not covered by our SemVer contract! 119 | 120 | ### Typing your Components 121 | 122 | When your component accepts an element with the `(element)` helper, you want to 123 | give this argument a proper type. Here is how: 124 | 125 | ```ts 126 | import type { ElementSignature } from 'ember-element-helper'; 127 | 128 | interface YourComponentSignature { 129 | Element: HTMLSectionElement; 130 | Args: { 131 | element?: ElementSignature['Return']; 132 | }; 133 | } 134 | ``` 135 | 136 | When the `@element` argument influences the `Element` of your component: 137 | 138 | ```ts 139 | import type { ElementSignature, ElementFromTagName } from 'ember-element-helper'; 140 | 141 | interface YourComponentSignature { 142 | Element: ElementFromTagName; 143 | Args: { 144 | element?: ElementSignature['Return']; 145 | }; 146 | } 147 | ``` 148 | 149 | When your component already uses an element for a given condition. When 150 | the condition isn't met, a fallback element is used. The fallback can even be 151 | provided from the outside. Here is the type: 152 | 153 | ```ts 154 | import type { ElementSignature, ElementFromTagName } from 'ember-element-helper'; 155 | 156 | interface YourComponentSignature< 157 | T extends string = 'section' 158 | > { 159 | Element: HTMLButtonElement | HTMLAnchorElement | ElementFromTagName; 160 | Args: { 161 | element?: ElementSignature['Return']; 162 | }; 163 | } 164 | ``` 165 | 166 | ## Contributing 167 | 168 | See the [Contributing](CONTRIBUTING.md) guide for details. 169 | 170 | ## License 171 | 172 | This project is licensed under the [MIT License](LICENSE.md). 173 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | Releases are mostly automated using 4 | [release-it](https://github.com/release-it/release-it/) and 5 | [lerna-changelog](https://github.com/lerna/lerna-changelog/). 6 | 7 | ## Preparation 8 | 9 | Since the majority of the actual release process is automated, the primary 10 | remaining task prior to releasing is confirming that all pull requests that 11 | have been merged since the last release have been labeled with the appropriate 12 | `lerna-changelog` labels and the titles have been updated to ensure they 13 | represent something that would make sense to our users. Some great information 14 | on why this is important can be found at 15 | [keepachangelog.com](https://keepachangelog.com/en/1.0.0/), but the overall 16 | guiding principle here is that changelogs are for humans, not machines. 17 | 18 | When reviewing merged PR's the labels to be used are: 19 | 20 | * breaking - Used when the PR is considered a breaking change. 21 | * enhancement - Used when the PR adds a new feature or enhancement. 22 | * bug - Used when the PR fixes a bug included in a previous release. 23 | * documentation - Used when the PR adds or updates documentation. 24 | * internal - Used for internal changes that still require a mention in the 25 | changelog/release notes. 26 | 27 | ## Release 28 | 29 | Once the prep work is completed, the actual release is straight forward: 30 | 31 | * Initially, `cd ember-element-helper`, as the release process must occur within the package directory. 32 | 33 | * First, ensure that you have installed your projects dependencies: 34 | 35 | ```sh 36 | pnpm install 37 | ``` 38 | 39 | * Second, ensure that you have obtained a 40 | [GitHub personal access token][generate-token] with the `repo` scope (no 41 | other permissions are needed). Make sure the token is available as the 42 | `GITHUB_AUTH` environment variable. 43 | 44 | For instance: 45 | 46 | ```bash 47 | export GITHUB_AUTH=abc123def456 48 | ``` 49 | 50 | [generate-token]: https://github.com/settings/tokens/new?scopes=repo&description=GITHUB_AUTH+env+variable 51 | 52 | * And last (but not least 😁) do your release. 53 | 54 | ```sh 55 | npx release-it 56 | ``` 57 | 58 | [release-it](https://github.com/release-it/release-it/) manages the actual 59 | release process. It will prompt you to to choose the version number after which 60 | you will have the chance to hand tweak the changelog to be used (for the 61 | `CHANGELOG.md` and GitHub release), then `release-it` continues on to tagging, 62 | pushing the tag and commits, etc. 63 | -------------------------------------------------------------------------------- /ember-element-helper/.eslintignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | declarations/ 4 | -------------------------------------------------------------------------------- /ember-element-helper/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { configs } = require('@nullvoxpopuli/eslint-configs'); 4 | 5 | module.exports = configs.ember(); 6 | -------------------------------------------------------------------------------- /ember-element-helper/.npmignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | .eslintcache 4 | 5 | # README is copied from monorepo root each build 6 | README.md 7 | -------------------------------------------------------------------------------- /ember-element-helper/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ## v0.8.8 (2025-05-12) 6 | 7 | #### :bug: Bug Fix 8 | * [#125](https://github.com/tildeio/ember-element-helper/pull/125) Remove unneeded peerDeps ([@NullVoxPopuli](https://github.com/NullVoxPopuli)) 9 | 10 | #### Committers: 1 11 | - [@NullVoxPopuli](https://github.com/NullVoxPopuli) 12 | 13 | ## v0.8.7 (2025-04-02) 14 | 15 | #### :bug: Bug Fix 16 | * [#123](https://github.com/tildeio/ember-element-helper/pull/123) Remove @embroider/util ([@NullVoxPopuli](https://github.com/NullVoxPopuli)) 17 | 18 | #### Committers: 1 19 | - [@NullVoxPopuli](https://github.com/NullVoxPopuli) 20 | 21 | ## v0.8.6 (2024-03-14) 22 | 23 | #### :rocket: Enhancement 24 | * [#118](https://github.com/tildeio/ember-element-helper/pull/118) use caret for @embroider/addon-shim dependency ([@SergeAstapov](https://github.com/SergeAstapov)) 25 | 26 | #### :house: Internal 27 | * [#120](https://github.com/tildeio/ember-element-helper/pull/120) Get linting passing ([@NullVoxPopuli](https://github.com/NullVoxPopuli)) 28 | 29 | #### Committers: 2 30 | - Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov)) 31 | - [@NullVoxPopuli](https://github.com/NullVoxPopuli) 32 | 33 | ## v0.8.5 (2023-10-11) 34 | 35 | #### :bug: Bug Fix 36 | * [#115](https://github.com/tildeio/ember-element-helper/pull/115) Add typesVersions to package.json to make TS imports work ([@SergeAstapov](https://github.com/SergeAstapov)) 37 | 38 | #### Committers: 2 39 | - Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov)) 40 | - [@NullVoxPopuli](https://github.com/NullVoxPopuli) 41 | 42 | ## v0.8.4 (2023-08-29) 43 | 44 | re-release of v0.8.3 45 | 46 | ## v0.8.3 (2023-08-29) 47 | 48 | * [#113](https://github.com/tildeio/ember-element-helper/pull/113) Remove reference to EmberComponent to remove the triple-slash directive in the generated declarations ([@NullVoxPopuli](https://github.com/NullVoxPopuli)) 49 | 50 | ## v0.8.2 (2023-08-18) 51 | 52 | re-release of v0.8.0 53 | 54 | ## v0.8.1 (2023-08-18) 55 | 56 | re-release of v0.8.0 57 | 58 | ## v0.8.0 (2023-08-17) 59 | 60 | #### :boom: Breaking Change 61 | * [#107](https://github.com/tildeio/ember-element-helper/pull/107) Add glint support ([@gossi](https://github.com/gossi)) 62 | drops support for ember 3.24 63 | 64 | #### :rocket: Enhancement 65 | * [#107](https://github.com/tildeio/ember-element-helper/pull/107) Add glint support ([@gossi](https://github.com/gossi)) 66 | 67 | #### :memo: Documentation 68 | * [#109](https://github.com/tildeio/ember-element-helper/pull/109) Update RELEASE and CONTRIBUTING md files ([@NullVoxPopuli](https://github.com/NullVoxPopuli)) 69 | 70 | #### :house: Internal 71 | * [#110](https://github.com/tildeio/ember-element-helper/pull/110) Tell volta to use node@16 ([@NullVoxPopuli](https://github.com/NullVoxPopuli)) 72 | * [#108](https://github.com/tildeio/ember-element-helper/pull/108) Switch to `pnpm` ([@gossi](https://github.com/gossi)) 73 | 74 | #### Committers: 2 75 | - Thomas Gossmann ([@gossi](https://github.com/gossi)) 76 | - [@NullVoxPopuli](https://github.com/NullVoxPopuli) 77 | 78 | ## v0.7.1 (2023-07-27) 79 | 80 | Re-trigger automated release 81 | 82 | 83 | ## v0.7.0 (2023-07-26) 84 | 85 | #### :rocket: Enhancement 86 | * [#105](https://github.com/tildeio/ember-element-helper/pull/105) Update addon to support ember v5 ([@mkszepp](https://github.com/mkszepp)) 87 | * [#88](https://github.com/tildeio/ember-element-helper/pull/88) v2 addon conversion ([@NullVoxPopuli](https://github.com/NullVoxPopuli)) 88 | 89 | #### :bug: Bug Fix 90 | * [#106](https://github.com/tildeio/ember-element-helper/pull/106) Support ember-source > v4 ([@NullVoxPopuli](https://github.com/NullVoxPopuli)) 91 | 92 | #### :memo: Documentation 93 | * [#83](https://github.com/tildeio/ember-element-helper/pull/83) Correct Typo in Test Name ([@angelayanpan](https://github.com/angelayanpan)) 94 | 95 | #### :house: Internal 96 | * [#105](https://github.com/tildeio/ember-element-helper/pull/105) Update addon to support ember v5 ([@mkszepp](https://github.com/mkszepp)) 97 | 98 | #### Committers: 3 99 | - Angela Pan ([@angelayanpan](https://github.com/angelayanpan)) 100 | - [@NullVoxPopuli](https://github.com/NullVoxPopuli) 101 | - [@mkszepp](https://github.com/mkszepp) 102 | 103 | 104 | ## v0.6.1 (2022-04-14) 105 | 106 | #### :rocket: Enhancement 107 | * [#82](https://github.com/tildeio/ember-element-helper/pull/82) Remove the need for an AST transform ([@SergeAstapov](https://github.com/SergeAstapov)) 108 | 109 | #### Committers: 1 110 | - Sergey Astapov ([@SergeAstapov](https://github.com/SergeAstapov)) 111 | 112 | 113 | ## v0.6.0 (2022-02-05) 114 | 115 | #### :boom: Breaking Change 116 | * [#73](https://github.com/tildeio/ember-element-helper/pull/73) Update to Ember 4 and Octane blueprint, drop support for Ember < 3.24 ([@knownasilya](https://github.com/knownasilya)) 117 | * [#74](https://github.com/tildeio/ember-element-helper/pull/74) Allow Embroider v1 deps; Drop node v10 ([@josemarluedke](https://github.com/josemarluedke)) 118 | 119 | #### :rocket: Enhancement 120 | * [#74](https://github.com/tildeio/ember-element-helper/pull/74) Allow Embroider v1 deps; Drop node v10 ([@josemarluedke](https://github.com/josemarluedke)) 121 | 122 | #### :memo: Documentation 123 | * [#78](https://github.com/tildeio/ember-element-helper/pull/78) Fix CI status badge ([@simonihmig](https://github.com/simonihmig)) 124 | 125 | #### :house: Internal 126 | * [#73](https://github.com/tildeio/ember-element-helper/pull/73) Update to Ember 4 and Octane blueprint, drop support for Ember < 3.24 ([@knownasilya](https://github.com/knownasilya)) 127 | * [#77](https://github.com/tildeio/ember-element-helper/pull/77) Upgrade Ember Auto Import to v2; Refresh other deps ([@josemarluedke](https://github.com/josemarluedke)) 128 | 129 | #### Committers: 5 130 | - Ilya Radchenko ([@knownasilya](https://github.com/knownasilya)) 131 | - Jakub Olek ([@hakubo](https://github.com/hakubo)) 132 | - Josemar Luedke ([@josemarluedke](https://github.com/josemarluedke)) 133 | - Miguel Camba ([@cibernox](https://github.com/cibernox)) 134 | - Simon Ihmig ([@simonihmig](https://github.com/simonihmig)) 135 | 136 | 137 | ## v0.5.5 (2021-06-14) 138 | 139 | #### :house: Internal 140 | * [#60](https://github.com/tildeio/ember-element-helper/pull/60) Fix publishing automation ([@rwjblue](https://github.com/rwjblue)) 141 | 142 | #### Committers: 1 143 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue)) 144 | 145 | 146 | ## v0.5.4 (2021-06-14) 147 | 148 | #### :bug: Bug Fix 149 | * [#54](https://github.com/tildeio/ember-element-helper/pull/54) Fix AST transform caching when used with Embroider ([@mydea](https://github.com/mydea)) 150 | 151 | #### :memo: Documentation 152 | * [#46](https://github.com/tildeio/ember-element-helper/pull/46) Updated the URL for Build status badge ([@ijlee2](https://github.com/ijlee2)) 153 | * [#52](https://github.com/tildeio/ember-element-helper/pull/52) Fix typo in docs ([@bertdeblock](https://github.com/bertdeblock)) 154 | 155 | #### :house: Internal 156 | * [#35](https://github.com/tildeio/ember-element-helper/pull/35) Changelog automation and generation ([@rwjblue](https://github.com/rwjblue)) 157 | 158 | #### Committers: 4 159 | - Bert De Block ([@bertdeblock](https://github.com/bertdeblock)) 160 | - Francesco Novy ([@mydea](https://github.com/mydea)) 161 | - Isaac Lee ([@ijlee2](https://github.com/ijlee2)) 162 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue)) 163 | 164 | 165 | ## v0.5.3 (2021-06-09) 166 | 167 | #### :rocket: Enhancement 168 | * [#59](https://github.com/tildeio/ember-element-helper/pull/59) Relax dependency on @embroider/utils ([@cibernox](https://github.com/cibernox)) 169 | 170 | #### Committers: 1 171 | - Miguel Camba ([@cibernox](https://github.com/cibernox)) 172 | 173 | 174 | ## v0.4.0 (2021-03-23) 175 | 176 | #### :rocket: Enhancement 177 | * [#41](https://github.com/tildeio/ember-element-helper/pull/41) Migrate to functional plugin format. ([@rwjblue](https://github.com/rwjblue)) 178 | 179 | #### :house: Internal 180 | * [#39](https://github.com/tildeio/ember-element-helper/pull/39) Fix beta build ([@chancancode](https://github.com/chancancode)) 181 | 182 | #### Committers: 2 183 | - Godfrey Chan ([@chancancode](https://github.com/chancancode)) 184 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue)) 185 | 186 | 187 | ## v0.3.2 (2020-11-18) 188 | 189 | #### :bug: Bug Fix 190 | * [#33](https://github.com/tildeio/ember-element-helper/pull/33) Support Parallel Build ([@lelea2](https://github.com/lelea2)) 191 | 192 | #### Committers: 2 193 | - Julien Palmas ([@bartocc](https://github.com/bartocc)) 194 | - Khanh Dao ([@lelea2](https://github.com/lelea2)) 195 | 196 | -------------------------------------------------------------------------------- /ember-element-helper/README.md: -------------------------------------------------------------------------------- 1 | # ember-element-helper 2 | 3 | [![Build Status](https://github.com/tildeio/ember-element-helper/actions/workflows/ci.yml/badge.svg)](https://github.com/tildeio/ember-element-helper/actions/workflows/ci.yml) 4 | 5 | Dynamic element helper for Glimmer templates. 6 | 7 | This addon provides a ~~polyfill~~ high fidelity reference implementation of 8 | [RFC #389](https://github.com/emberjs/rfcs/pull/389), including the proposed 9 | amendments in [RFC PR #620](https://github.com/emberjs/rfcs/pull/620). 10 | 11 | Please note that while [RFC #389](https://github.com/emberjs/rfcs/pull/389) 12 | has been approved, it has not been implemented in Ember.js yet. As such, the 13 | feature is still subject to change based on implementation feedback. 14 | 15 | When this feature is implemented in Ember.js, we will release a 1.0 version of 16 | this addon as a true polyfill for the feature, allowing the feature to be used 17 | on older Ember.js versions and be completely inert on newer versions where the 18 | official implementation is available. 19 | 20 | ## Compatibility 21 | 22 | * Ember.js v3.28 or above 23 | * Ember CLI v3.28 or above 24 | * Node.js v12 or above 25 | 26 | ## Limitations 27 | 28 | This implementation has the following known limitations: 29 | 30 | * By default, an auto-generated `id` attribute will be added to the element 31 | (e.g. `id="ember123"`). It is possible to override this by providing an 32 | `id` attribute when invoking the component (e.g. ``). 33 | However, it is not possible to remove the `id` attribute completely. The 34 | proposed helper will not have this behavior, as such this should not be 35 | relied upon (e.g. in CSS and `qunit-dom` selectors). 36 | 37 | * The element will have an `ember-view` class (i.e. `class="ember-view"`). 38 | This is in addition and merged with the class attribute provided when 39 | invoking the component (e.g. `` will result in 40 | something like `
`). It is not possible 41 | to remove the `ember-view` class. The proposed helper will not have this 42 | behavior, as such this should not be relied upon (e.g. in CSS and `qunit-dom` 43 | selectors). 44 | 45 | * In Ember versions before 3.11, modifiers cannot be passed to the element, 46 | even when addons such as the [modifier manager](https://github.com/ember-polyfills/ember-modifier-manager-polyfill) 47 | and [on modifier](https://github.com/buschtoens/ember-on-modifier) polyfills 48 | are used. Doing so requires [RFC #435](https://github.com/emberjs/rfcs/blob/master/text/0435-modifier-splattributes.md) 49 | which is first available on Ember 3.11. This is an Ember.js limitation, 50 | unrelated to this addon. 51 | 52 | ## Installation 53 | 54 | ``` 55 | ember install ember-element-helper 56 | ``` 57 | 58 | ## Usage 59 | 60 | ```hbs 61 | {{#let (element this.tagName) as |Tag|}} 62 | hello world! 63 | {{/let}} 64 | ``` 65 | 66 | You can also pass around the result of invoking this helper into any components 67 | that accepts "contextual components" as arguments: 68 | 69 | ```hbs 70 | 71 | ``` 72 | 73 | ```hbs 74 | {{!-- in my-component.hbs --}} 75 | {{#let @tag as |Tag|}} 76 | hello world! 77 | {{/let}} 78 | 79 | {{!-- ...or more directly... --}} 80 | <@tag class="my-tag">hello world! 81 | ``` 82 | 83 | ### Single File Components 84 | 85 | Using the `(element)` helper with [first class component 86 | templates](http://emberjs.github.io/rfcs/0779-first-class-component-templates.html): 87 | 88 | ```gjs 89 | import { element } from 'ember-element-helper'; 90 | 91 | 96 | ``` 97 | 98 | ### Glint Usage in Classic Mode 99 | 100 | In order to use a typed `(element)` helper in classic mode, you need to import 101 | the addon's glint template registry and extend your app's registry declaration 102 | as described in the [Using 103 | Addons](https://typed-ember.gitbook.io/glint/using-glint/ember/using-addons#using-glint-enabled-addons) 104 | documentation: 105 | 106 | ```ts 107 | import '@glint/environment-ember-loose'; 108 | import type EmberElementHelperRegistry from 'ember-element-helper/template-registry'; 109 | 110 | declare module '@glint/environment-ember-loose/registry' { 111 | export default interface Registry extends EmberElementHelperRegistry, /* other addon registries */ { 112 | // local entries 113 | } 114 | } 115 | ``` 116 | 117 | > **Note:** Glint itself is still under active development, and as such breaking changes might occur. 118 | > Therefore, Glint support by this addon is also considered experimental, and not covered by our SemVer contract! 119 | 120 | ### Typing your Components 121 | 122 | When your component accepts an element with the `(element)` helper, you want to 123 | give this argument a proper type. Here is how: 124 | 125 | ```ts 126 | import type { ElementSignature } from 'ember-element-helper'; 127 | 128 | interface YourComponentSignature { 129 | Element: HTMLSectionElement; 130 | Args: { 131 | element?: ElementSignature['Return']; 132 | }; 133 | } 134 | ``` 135 | 136 | When the `@element` argument influences the `Element` of your component: 137 | 138 | ```ts 139 | import type { ElementSignature, ElementFromTagName } from 'ember-element-helper'; 140 | 141 | interface YourComponentSignature { 142 | Element: ElementFromTagName; 143 | Args: { 144 | element?: ElementSignature['Return']; 145 | }; 146 | } 147 | ``` 148 | 149 | When your component already uses an element for a given condition. When 150 | the condition isn't met, a fallback element is used. The fallback can even be 151 | provided from the outside. Here is the type: 152 | 153 | ```ts 154 | import type { ElementSignature, ElementFromTagName } from 'ember-element-helper'; 155 | 156 | interface YourComponentSignature< 157 | T extends string = 'section' 158 | > { 159 | Element: HTMLButtonElement | HTMLAnchorElement | ElementFromTagName; 160 | Args: { 161 | element?: ElementSignature['Return']; 162 | }; 163 | } 164 | ``` 165 | 166 | ## Contributing 167 | 168 | See the [Contributing](CONTRIBUTING.md) guide for details. 169 | 170 | ## License 171 | 172 | This project is licensed under the [MIT License](LICENSE.md). 173 | -------------------------------------------------------------------------------- /ember-element-helper/addon-main.cjs: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const { addonV1Shim } = require('@embroider/addon-shim'); 4 | 5 | module.exports = addonV1Shim(__dirname); 6 | -------------------------------------------------------------------------------- /ember-element-helper/babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-typescript"], 3 | "plugins": [ 4 | "@embroider/addon-dev/template-colocation-plugin", 5 | ["@babel/plugin-proposal-decorators", { "legacy": true }], 6 | "@babel/plugin-proposal-class-properties" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /ember-element-helper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-element-helper", 3 | "version": "0.8.8", 4 | "description": "Dynamic element helper for Glimmer templates.", 5 | "repository": "https://github.com/tildeio/ember-element-helper", 6 | "license": "MIT", 7 | "author": "Godfrey Chan ", 8 | "keywords": [ 9 | "ember-addon", 10 | "element helper", 11 | "dynamic element" 12 | ], 13 | "exports": { 14 | ".": { 15 | "types": "./declarations/index.d.ts", 16 | "default": "./dist/index.js" 17 | }, 18 | "./*": { 19 | "types": "./declarations/*.d.ts", 20 | "default": "./dist/*" 21 | }, 22 | "./addon-main.js": "./addon-main.cjs" 23 | }, 24 | "typesVersions": { 25 | "*": { 26 | "*": [ 27 | "declarations/*" 28 | ] 29 | } 30 | }, 31 | "files": [ 32 | "dist", 33 | "declarations", 34 | "addon-main.cjs", 35 | "CHANGELOG.md", 36 | "README.md" 37 | ], 38 | "scripts": { 39 | "build": "concurrently 'npm:build:*'", 40 | "build:js": "rollup -c", 41 | "build:types": "tsc --emitDeclarationOnly", 42 | "build:docs": "cp ../README.md ./README.md", 43 | "lint": "concurrently 'npm:lint:js'", 44 | "lint:fix": "concurrently 'npm:lint:js:fix'", 45 | "lint:js": "eslint . --cache", 46 | "lint:js:fix": "eslint . --fix", 47 | "lint:types": "glint", 48 | "start": "concurrently 'npm:start:*'", 49 | "start:js": "rollup -c --watch --no-watch.clearScreen", 50 | "start:types": "tsc --emitDeclarationOnly -w", 51 | "pretest": "pnpm build", 52 | "test": "echo 'Addon does not have tests, run tests in test-app'", 53 | "prepare": "pnpm build", 54 | "prepublishOnly": "pnpm build" 55 | }, 56 | "dependencies": { 57 | "@embroider/addon-shim": "^1.8.3" 58 | }, 59 | "devDependencies": { 60 | "@babel/core": "7.18.6", 61 | "@babel/plugin-proposal-class-properties": "7.18.6", 62 | "@babel/plugin-proposal-decorators": "7.18.6", 63 | "@babel/plugin-syntax-decorators": "7.18.6", 64 | "@babel/preset-typescript": "7.22.5", 65 | "@babel/runtime": "7.22.6", 66 | "@embroider/addon-dev": "4.1.0", 67 | "@glimmer/component": "^1.1.2", 68 | "@glint/core": "1.0.2", 69 | "@glint/environment-ember-loose": "1.0.2", 70 | "@glint/template": "1.0.2", 71 | "@nullvoxpopuli/eslint-configs": "3.2.2", 72 | "@release-it-plugins/lerna-changelog": "^6.0.0", 73 | "@rollup/plugin-babel": "6.0.3", 74 | "@rollup/plugin-node-resolve": "15.1.0", 75 | "@tsconfig/ember": "^3.0.0", 76 | "@types/rsvp": "^4.0.4", 77 | "@typescript-eslint/eslint-plugin": "^5.62.0", 78 | "@typescript-eslint/parser": "^5.62.0", 79 | "concurrently": "8.2.2", 80 | "ember-source": "~4.12.3", 81 | "eslint": "^8.56.0", 82 | "eslint-config-prettier": "9.1.0", 83 | "eslint-plugin-decorator-position": "^5.0.2", 84 | "eslint-plugin-ember": "12.0.0", 85 | "eslint-plugin-import": "2.29.1", 86 | "eslint-plugin-json": "3.1.0", 87 | "eslint-plugin-node": "11.1.0", 88 | "eslint-plugin-prettier": "5.1.3", 89 | "eslint-plugin-simple-import-sort": "10.0.0", 90 | "rollup": "3.26.3", 91 | "typescript": "5.1.6" 92 | }, 93 | "publishConfig": { 94 | "registry": "https://registry.npmjs.org" 95 | }, 96 | "ember": { 97 | "edition": "octane" 98 | }, 99 | "ember-addon": { 100 | "version": 2, 101 | "type": "addon", 102 | "main": "./addon-main.cjs", 103 | "app-js": { 104 | "./helpers/element.js": "./dist/_app_/helpers/element.js" 105 | } 106 | }, 107 | "engines": { 108 | "node": "14.* || 16.* || >= 18" 109 | }, 110 | "volta": { 111 | "extends": "../package.json" 112 | }, 113 | "release-it": { 114 | "plugins": { 115 | "@release-it-plugins/lerna-changelog": { 116 | "infile": "CHANGELOG.md", 117 | "launchEditor": true 118 | } 119 | }, 120 | "git": { 121 | "tagName": "v${version}" 122 | }, 123 | "github": { 124 | "release": true, 125 | "tokenRef": "GITHUB_AUTH" 126 | }, 127 | "npm": { 128 | "publish": false 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /ember-element-helper/rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { babel } from '@rollup/plugin-babel'; 2 | import { Addon } from '@embroider/addon-dev/rollup'; 3 | import { defineConfig } from 'rollup'; 4 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 5 | 6 | const addon = new Addon({ 7 | srcDir: 'src', 8 | destDir: 'dist', 9 | }); 10 | 11 | const extensions = ['.js', '.ts']; 12 | 13 | export default defineConfig({ 14 | // This provides defaults that work well alongside `publicEntrypoints` below. 15 | // You can augment this if you need to. 16 | output: addon.output(), 17 | 18 | plugins: [ 19 | // These are the modules that users should be able to import from your 20 | // addon. Anything not listed here may get optimized away. 21 | addon.publicEntrypoints(['index.js', 'helpers/element.js']), 22 | 23 | // These are the modules that should get reexported into the traditional 24 | // "app" tree. Things in here should also be in publicEntrypoints above, but 25 | // not everything in publicEntrypoints necessarily needs to go here. 26 | addon.appReexports(['helpers/element.js']), 27 | 28 | nodeResolve({ extensions }), 29 | 30 | // This babel config should *not* apply presets or compile away ES modules. 31 | // It exists only to provide development niceties for you, like automatic 32 | // template colocation. 33 | // 34 | // By default, this will load the actual babel config from the file 35 | // babel.config.json. 36 | babel({ 37 | extensions, 38 | babelHelpers: 'bundled', 39 | }), 40 | 41 | // Follow the V2 Addon rules about dependencies. Your code can import from 42 | // `dependencies` and `peerDependencies` as well as standard Ember-provided 43 | // package names. 44 | addon.dependencies(), 45 | 46 | // Ensure that standalone .hbs files are properly integrated as Javascript. 47 | addon.hbs(), 48 | 49 | // addons are allowed to contain imports of .css files, which we want rollup 50 | // to leave alone and keep in the published output. 51 | addon.keepAssets(['**/*.css']), 52 | 53 | // Remove leftover build artifacts when starting a new build. 54 | addon.clean(), 55 | ], 56 | }); 57 | -------------------------------------------------------------------------------- /ember-element-helper/src/helpers/element.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line ember/no-classic-components 2 | import EmberComponent from '@ember/component'; 3 | import Helper from '@ember/component/helper'; 4 | import { assert, runInDebug } from '@ember/debug'; 5 | 6 | import type { ComponentLike } from '@glint/template'; 7 | 8 | // eslint-disable-next-line @typescript-eslint/no-empty-function 9 | function UNINITIALIZED() { } 10 | 11 | export type ElementFromTagName = T extends keyof HTMLElementTagNameMap 12 | ? HTMLElementTagNameMap[T] 13 | : Element; 14 | 15 | type Positional = [name: T]; 16 | type Return = ComponentLike<{ 17 | Element: ElementFromTagName; 18 | Blocks: { default: [] }; 19 | }>; 20 | 21 | export interface ElementSignature { 22 | Args: { 23 | Positional: Positional; 24 | }; 25 | Return: Return | undefined; 26 | } 27 | 28 | export default class ElementHelper extends Helper> { 29 | tagName: string | (() => void) = UNINITIALIZED; 30 | componentClass?: Return; 31 | 32 | compute(params: Positional, hash: object) { 33 | assert('The `element` helper takes a single positional argument', params.length === 1); 34 | assert( 35 | 'The `element` helper does not take any named arguments', 36 | Object.keys(hash).length === 0 37 | ); 38 | 39 | let tagName = params[0]; 40 | 41 | if (tagName !== this.tagName) { 42 | this.tagName = tagName; 43 | 44 | if (typeof tagName === 'string') { 45 | this.componentClass = 46 | class DynamicElement extends EmberComponent { 47 | tagName = tagName; 48 | } as unknown as Return; 49 | } else { 50 | this.componentClass = undefined; 51 | 52 | runInDebug(() => { 53 | let message = 'The argument passed to the `element` helper must be a string'; 54 | 55 | try { 56 | message += ` (you passed \`${tagName}\`)`; 57 | } catch (e) { 58 | // ignore 59 | } 60 | 61 | assert(message, tagName === undefined || tagName === null); 62 | }); 63 | } 64 | } 65 | 66 | return this.componentClass; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /ember-element-helper/src/index.ts: -------------------------------------------------------------------------------- 1 | import ElementHelper, { type ElementFromTagName, type ElementSignature } from './helpers/element'; 2 | 3 | export { ElementHelper as element }; 4 | export type { ElementFromTagName, ElementSignature }; 5 | -------------------------------------------------------------------------------- /ember-element-helper/src/template-registry.ts: -------------------------------------------------------------------------------- 1 | import type ElementHelper from './helpers/element'; 2 | 3 | export default interface EmberElementHelperRegistry { 4 | element: typeof ElementHelper; 5 | } 6 | -------------------------------------------------------------------------------- /ember-element-helper/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember/tsconfig.json", 3 | "glint": { 4 | "environment": [ 5 | "ember-loose", 6 | "ember-template-imports" 7 | ] 8 | }, 9 | "compilerOptions": { 10 | "noEmit": false, 11 | "declarationDir": "./declarations", 12 | "paths": { 13 | "*": [ 14 | "./types/*" 15 | ] 16 | }, 17 | // for typescript < 5 18 | "moduleResolution": "NodeNext" 19 | }, 20 | "include": [ 21 | "src/**/*", 22 | "types/**/*" 23 | ] 24 | } -------------------------------------------------------------------------------- /ember-element-helper/types/index.d.ts: -------------------------------------------------------------------------------- 1 | import 'ember-source/types'; 2 | import 'ember-source/types/preview'; 3 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "repository": "https://github.com/tildeio/ember-element-helper", 4 | "license": "MIT", 5 | "author": "Godfrey Chan ", 6 | "scripts": { 7 | "build": "pnpm --filter ember-element-helper build", 8 | "start": "concurrently 'npm:start:*' --restart-after 5000 --prefix-colors cyan,white,yellow", 9 | "start:addon": "pnpm --filter ember-element-helper start", 10 | "start:test": "pnpm --filter test-app start", 11 | "lint": "pnpm --filter '*' lint", 12 | "lint:fix": "pnpm --filter '*' lint:fix", 13 | "test": "pnpm --filter '*' test" 14 | }, 15 | "packageManager": "pnpm@8", 16 | "volta": { 17 | "node": "16.20.2", 18 | "npm": "9.8.1" 19 | }, 20 | "devDependencies": { 21 | "concurrently": "^8.2.2" 22 | }, 23 | "version": "0.8.8" 24 | } 25 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - ember-element-helper 3 | - test-app 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test-app/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false, 9 | 10 | /** 11 | Setting `isTypeScriptProject` to true will force the blueprint generators to generate TypeScript 12 | rather than JavaScript by default, when a TypeScript version of a given blueprint is available. 13 | */ 14 | "isTypeScriptProject": false 15 | } 16 | -------------------------------------------------------------------------------- /test-app/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .*/ 17 | .eslintcache 18 | 19 | # ember-try 20 | /.node_modules.ember-try/ 21 | /bower.json.ember-try 22 | /npm-shrinkwrap.json.ember-try 23 | /package.json.ember-try 24 | /package-lock.json.ember-try 25 | /yarn.lock.ember-try 26 | -------------------------------------------------------------------------------- /test-app/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: '@babel/eslint-parser', 6 | parserOptions: { 7 | ecmaVersion: 'latest', 8 | sourceType: 'module', 9 | requireConfigFile: false, 10 | babelOptions: { 11 | plugins: [ 12 | ['@babel/plugin-proposal-decorators', { decoratorsBeforeExport: true }], 13 | ], 14 | }, 15 | }, 16 | plugins: ['ember'], 17 | extends: [ 18 | 'eslint:recommended', 19 | 'plugin:ember/recommended', 20 | 'plugin:prettier/recommended', 21 | ], 22 | env: { 23 | browser: true, 24 | }, 25 | rules: {}, 26 | overrides: [ 27 | // node files 28 | { 29 | files: [ 30 | './.eslintrc.js', 31 | './.prettierrc.js', 32 | './.stylelintrc.js', 33 | './.template-lintrc.js', 34 | './ember-cli-build.js', 35 | './index.js', 36 | './testem.js', 37 | './blueprints/*/index.js', 38 | './config/**/*.js', 39 | './tests/dummy/config/**/*.js', 40 | './node-tests/**/*.js', 41 | './lib/**/*.js', 42 | ], 43 | parserOptions: { 44 | sourceType: 'script', 45 | }, 46 | env: { 47 | browser: false, 48 | node: true, 49 | }, 50 | extends: ['plugin:n/recommended'], 51 | }, 52 | { 53 | // test files 54 | files: ['tests/**/*-test.{js,ts}'], 55 | extends: ['plugin:qunit/recommended'], 56 | }, 57 | ], 58 | }; 59 | -------------------------------------------------------------------------------- /test-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.env* 13 | /.pnp* 14 | /.sass-cache 15 | /.eslintcache 16 | /connect.lock 17 | /coverage/ 18 | /libpeerconnection.log 19 | /npm-debug.log* 20 | /testem.log 21 | /yarn-error.log 22 | 23 | # ember-try 24 | /.node_modules.ember-try/ 25 | /bower.json.ember-try 26 | /npm-shrinkwrap.json.ember-try 27 | /package.json.ember-try 28 | /package-lock.json.ember-try 29 | /yarn.lock.ember-try 30 | 31 | # broccoli-debug 32 | /DEBUG/ 33 | -------------------------------------------------------------------------------- /test-app/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /node_modules/ 11 | 12 | # misc 13 | /coverage/ 14 | !.* 15 | .eslintcache 16 | .lint-todo/ 17 | 18 | # ember-try 19 | /.node_modules.ember-try/ 20 | /npm-shrinkwrap.json.ember-try 21 | /package.json.ember-try 22 | /package-lock.json.ember-try 23 | /yarn.lock.ember-try 24 | -------------------------------------------------------------------------------- /test-app/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | overrides: [ 5 | { 6 | files: '*.{js,ts}', 7 | options: { 8 | singleQuote: true, 9 | }, 10 | }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /test-app/.stylelintignore: -------------------------------------------------------------------------------- 1 | # unconventional files 2 | /blueprints/*/files/ 3 | 4 | # compiled output 5 | /dist/ 6 | 7 | # addons 8 | /.node_modules.ember-try/ 9 | -------------------------------------------------------------------------------- /test-app/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: ['stylelint-config-standard', 'stylelint-prettier/recommended'], 5 | }; 6 | -------------------------------------------------------------------------------- /test-app/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | rules: { 6 | 'no-curly-component-invocation': { allow: ['counter'] }, 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /test-app/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /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/) (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 test-app` 19 | * `npm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `npm run lint` 39 | * `npm run lint:fix` 40 | 41 | ### Building 42 | 43 | * `ember build` (development) 44 | * `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | * [ember.js](https://emberjs.com/) 53 | * [ember-cli](https://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/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'test-app/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /test-app/app/components/element-receiver.hbs: -------------------------------------------------------------------------------- 1 | {{#let @tag as |Tag|}} 2 | {{!@glint-ignore}} 3 | {{yield}} 4 | {{/let}} -------------------------------------------------------------------------------- /test-app/app/components/element-receiver.ts: -------------------------------------------------------------------------------- 1 | import Component from '@glimmer/component'; 2 | import type { ElementSignature, ElementFromTagName } from 'ember-element-helper'; 3 | 4 | interface ElementReceiverSignature { 5 | Element: ElementFromTagName; 6 | Args: { 7 | tag: ElementSignature['Return']; 8 | }; 9 | Blocks: { 10 | default: []; 11 | } 12 | } 13 | 14 | export default class ElementReceiver extends Component> {} 15 | -------------------------------------------------------------------------------- /test-app/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | 11 | 12 | 13 | 14 | {{content-for "head-footer"}} 15 | 16 | 17 | {{content-for "body"}} 18 | 19 | 20 | 21 | 22 | {{content-for "body-footer"}} 23 | 24 | 25 | -------------------------------------------------------------------------------- /test-app/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'test-app/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /test-app/app/styles/app.css: -------------------------------------------------------------------------------- 1 | /* Ember supports plain CSS out of the box. More info: https://cli.emberjs.com/release/advanced-use/stylesheets/ */ 2 | -------------------------------------------------------------------------------- /test-app/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | {{#let (element "article") as |Article|}} 2 |
Hello there!
3 | {{/let}} -------------------------------------------------------------------------------- /test-app/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "4.12.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 | "--embroider", 15 | "--ci-provider=github" 16 | ] 17 | } 18 | ] 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /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-3.28', 12 | npm: { 13 | devDependencies: { 14 | 'ember-resolver': '^8.0.0', 15 | 'ember-source': '~3.28.0', 16 | }, 17 | }, 18 | }, 19 | { 20 | name: 'ember-lts-4.4', 21 | npm: { 22 | devDependencies: { 23 | 'ember-resolver': '^8.0.0', 24 | 'ember-source': '~4.4.0', 25 | }, 26 | }, 27 | }, 28 | { 29 | name: 'ember-lts-4.8', 30 | npm: { 31 | devDependencies: { 32 | 'ember-source': '~4.8.0', 33 | }, 34 | }, 35 | }, 36 | { 37 | name: 'ember-lts-4.12', 38 | npm: { 39 | devDependencies: { 40 | 'ember-source': '~4.12.0', 41 | }, 42 | }, 43 | }, 44 | { 45 | name: 'ember-5.0', 46 | npm: { 47 | devDependencies: { 48 | 'ember-source': '~5.0.0', 49 | }, 50 | }, 51 | }, 52 | { 53 | name: 'ember-release', 54 | npm: { 55 | devDependencies: { 56 | 'ember-source': await getChannelURL('release'), 57 | }, 58 | }, 59 | }, 60 | { 61 | name: 'ember-beta', 62 | npm: { 63 | devDependencies: { 64 | 'ember-source': await getChannelURL('beta'), 65 | }, 66 | }, 67 | }, 68 | { 69 | name: 'ember-canary', 70 | npm: { 71 | devDependencies: { 72 | 'ember-source': await getChannelURL('canary'), 73 | }, 74 | }, 75 | }, 76 | { 77 | name: 'ember-classic', 78 | env: { 79 | EMBER_OPTIONAL_FEATURES: JSON.stringify({ 80 | 'application-template-wrapper': true, 81 | 'default-async-observers': false, 82 | 'template-only-glimmer-components': false, 83 | }), 84 | }, 85 | npm: { 86 | devDependencies: { 87 | 'ember-resolver': '^8.0.0', 88 | 'ember-source': '~3.28.0', 89 | }, 90 | ember: { 91 | edition: 'classic', 92 | }, 93 | }, 94 | }, 95 | embroiderSafe(), 96 | embroiderOptimized(), 97 | ], 98 | }; 99 | }; 100 | -------------------------------------------------------------------------------- /test-app/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | const ENV = { 5 | modulePrefix: 'test-app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'history', 9 | EmberENV: { 10 | EXTEND_PROTOTYPES: false, 11 | FEATURES: { 12 | // Here you can enable experimental features on an ember canary build 13 | // e.g. EMBER_NATIVE_DECORATOR_SUPPORT: true 14 | }, 15 | }, 16 | 17 | APP: { 18 | // Here you can pass flags/options to your application instance 19 | // when it is created 20 | }, 21 | }; 22 | 23 | if (environment === 'development') { 24 | // ENV.APP.LOG_RESOLVER = true; 25 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 26 | // ENV.APP.LOG_TRANSITIONS = true; 27 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 28 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 29 | } 30 | 31 | if (environment === 'test') { 32 | // Testem prefers this... 33 | ENV.locationType = 'none'; 34 | 35 | // keep test console output quieter 36 | ENV.APP.LOG_ACTIVE_GENERATION = false; 37 | ENV.APP.LOG_VIEW_LOOKUPS = false; 38 | 39 | ENV.APP.rootElement = '#ember-testing'; 40 | ENV.APP.autoboot = false; 41 | } 42 | 43 | if (environment === 'production') { 44 | // here you can enable a production-specific feature 45 | } 46 | 47 | return ENV; 48 | }; 49 | -------------------------------------------------------------------------------- /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 | } 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test-app/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | const packageJson = require('./package'); 5 | 6 | module.exports = function (defaults) { 7 | const app = new EmberApp(defaults, { 8 | // Add options here 9 | autoImport: { 10 | watchDependencies: Object.keys(packageJson.dependencies), 11 | }, 12 | 'ember-cli-babel': { 13 | enableTypeScriptTransform: true, 14 | }, 15 | }); 16 | 17 | const { maybeEmbroider } = require('@embroider/test-setup'); 18 | return maybeEmbroider(app, { 19 | skipBabel: [ 20 | { 21 | package: 'qunit', 22 | }, 23 | ], 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /test-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "test-app", 3 | "private": true, 4 | "description": "Small description for test-app goes here", 5 | "repository": "", 6 | "license": "MIT", 7 | "author": "", 8 | "directories": { 9 | "doc": "doc", 10 | "test": "tests" 11 | }, 12 | "scripts": { 13 | "build": "ember build --environment=production", 14 | "lint": "concurrently 'npm:lint:*(!fix)' --names 'lint:'", 15 | "_lint:css": "stylelint '**/*.css'", 16 | "_lint:css:fix": "concurrently 'npm:lint:css -- --fix'", 17 | "lint:fix": "concurrently 'npm:lint:*:fix' --names 'fix:'", 18 | "lint:js": "eslint . --cache", 19 | "lint:js:fix": "eslint . --fix", 20 | "lint:types": "glint", 21 | "start": "ember serve", 22 | "test": "ember test" 23 | }, 24 | "dependencies": { 25 | "ember-element-helper": "workspace:*" 26 | }, 27 | "dependenciesMeta": { 28 | "ember-element-helper": { 29 | "injected": true 30 | } 31 | }, 32 | "devDependencies": { 33 | "@babel/eslint-parser": "^7.23.10", 34 | "@babel/plugin-proposal-decorators": "^7.21.0", 35 | "@ember/optional-features": "^2.0.0", 36 | "@ember/string": "^3.1.1", 37 | "@ember/test-helpers": "^2.9.3", 38 | "@embroider/compat": "^2.1.1", 39 | "@embroider/core": "^2.1.1", 40 | "@embroider/macros": "^1.0.0", 41 | "@embroider/test-setup": "^1.8.3", 42 | "@embroider/webpack": "^2.1.1", 43 | "@glimmer/component": "^1.1.2", 44 | "@glimmer/tracking": "^1.1.2", 45 | "@glint/core": "^1.0.2", 46 | "@glint/environment-ember-loose": "^1.0.2", 47 | "@glint/environment-ember-template-imports": "^1.0.2", 48 | "@glint/template": "^1.0.2", 49 | "@tsconfig/ember": "^3.0.0", 50 | "@typescript-eslint/eslint-plugin": "^5.62.0", 51 | "@typescript-eslint/parser": "^5.62.0", 52 | "broccoli-asset-rev": "^3.0.0", 53 | "concurrently": "^8.2.2", 54 | "ember-auto-import": "^2.6.3", 55 | "ember-cli": "~4.12.0", 56 | "ember-cli-app-version": "^6.0.0", 57 | "ember-cli-babel": "^7.26.11", 58 | "ember-cli-dependency-checker": "^3.3.1", 59 | "ember-cli-htmlbars": "^6.2.0", 60 | "ember-cli-inject-live-reload": "^2.1.0", 61 | "ember-disable-prototype-extensions": "^1.1.3", 62 | "ember-fetch": "^8.1.2", 63 | "ember-load-initializers": "^2.1.2", 64 | "ember-modifier": "^4.1.0", 65 | "ember-page-title": "^7.0.0", 66 | "ember-qunit": "^6.2.0", 67 | "ember-resolver": "^10.0.0", 68 | "ember-source": "~4.12.0", 69 | "ember-source-channel-url": "^3.0.0", 70 | "ember-template-imports": "^3.4.2", 71 | "ember-template-lint": "^5.7.2", 72 | "ember-try": "^2.0.0", 73 | "eslint": "^8.56.0", 74 | "eslint-config-prettier": "^9.1.0", 75 | "eslint-plugin-ember": "^12.0.0", 76 | "eslint-plugin-n": "^16.6.2", 77 | "eslint-plugin-prettier": "^5.1.3", 78 | "eslint-plugin-qunit": "^8.0.1", 79 | "loader.js": "^4.7.0", 80 | "prettier": "^3.2.4", 81 | "qunit": "^2.19.4", 82 | "qunit-dom": "^2.0.0", 83 | "release-it": "^14.2.1", 84 | "release-it-lerna-changelog": "^3.1.0", 85 | "stylelint": "^16.2.1", 86 | "stylelint-config-prettier": "^9.0.5", 87 | "stylelint-config-standard": "^36.0.0", 88 | "stylelint-prettier": "^5.0.0", 89 | "tracked-built-ins": "^3.1.1", 90 | "typescript": "5.1.6", 91 | "webpack": "^5.78.0" 92 | }, 93 | "engines": { 94 | "node": "14.* || 16.* || >= 18" 95 | }, 96 | "ember": { 97 | "edition": "octane" 98 | }, 99 | "volta": { 100 | "extends": "../package.json" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /test-app/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /test-app/tests/helpers/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | setupApplicationTest as upstreamSetupApplicationTest, 3 | setupRenderingTest as upstreamSetupRenderingTest, 4 | setupTest as upstreamSetupTest, 5 | } from 'ember-qunit'; 6 | 7 | // This file exists to provide wrappers around ember-qunit's / ember-mocha's 8 | // test setup functions. This way, you can easily extend the setup that is 9 | // needed per test type. 10 | 11 | function setupApplicationTest(hooks, options) { 12 | upstreamSetupApplicationTest(hooks, options); 13 | 14 | // Additional setup for application tests can be done here. 15 | // 16 | // For example, if you need an authenticated session for each 17 | // application test, you could do: 18 | // 19 | // hooks.beforeEach(async function () { 20 | // await authenticateSession(); // ember-simple-auth 21 | // }); 22 | // 23 | // This is also a good place to call test setup functions coming 24 | // from other addons: 25 | // 26 | // setupIntl(hooks); // ember-intl 27 | // setupMirage(hooks); // ember-cli-mirage 28 | } 29 | 30 | function setupRenderingTest(hooks, options) { 31 | upstreamSetupRenderingTest(hooks, options); 32 | 33 | // Additional setup for rendering tests can be done here. 34 | } 35 | 36 | function setupTest(hooks, options) { 37 | upstreamSetupTest(hooks, options); 38 | 39 | // Additional setup for unit tests can be done here. 40 | } 41 | 42 | export { setupApplicationTest, setupRenderingTest, setupTest }; 43 | -------------------------------------------------------------------------------- /test-app/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TestApp Tests 6 | 7 | 8 | 9 | {{content-for "head"}} 10 | {{content-for "test-head"}} 11 | 12 | 13 | 14 | 15 | 16 | {{content-for "head-footer"}} 17 | {{content-for "test-head-footer"}} 18 | 19 | 20 | {{content-for "body"}} 21 | {{content-for "test-body"}} 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | {{content-for "body-footer"}} 37 | {{content-for "test-body-footer"}} 38 | 39 | 40 | -------------------------------------------------------------------------------- /test-app/tests/integration/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tildeio/ember-element-helper/6537b14d025608b1dfa07bf04ad3a73bb0b39b13/test-app/tests/integration/.gitkeep -------------------------------------------------------------------------------- /test-app/tests/integration/helpers/element-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { hbs } from 'ember-cli-htmlbars'; 3 | import { setupRenderingTest } from 'ember-qunit'; 4 | import { click, render, settled } from '@ember/test-helpers'; 5 | import { helper } from '@ember/component/helper'; 6 | import Ember from 'ember'; 7 | import { macroCondition, dependencySatisfies } from '@embroider/macros'; 8 | 9 | module('Integration | Helper | element', function (hooks) { 10 | let originalOnerror; 11 | let expectEmberError; 12 | let expectedEmberErrors; 13 | 14 | setupRenderingTest(hooks); 15 | 16 | hooks.beforeEach((assert) => { 17 | originalOnerror = Ember.onerror; 18 | 19 | expectEmberError = function (expectation) { 20 | let _onerror = Ember.onerror; 21 | 22 | expectedEmberErrors.push(expectation); 23 | 24 | Ember.onerror = function (error) { 25 | assert.throws(() => { 26 | throw error; 27 | }, expectedEmberErrors.pop()); 28 | Ember.onerror = _onerror; 29 | }; 30 | }; 31 | 32 | expectedEmberErrors = []; 33 | }); 34 | 35 | hooks.afterEach((assert) => { 36 | Ember.onerror = originalOnerror; 37 | 38 | expectedEmberErrors.forEach((expected) => { 39 | assert.strictEqual(undefined, expected); 40 | }); 41 | }); 42 | 43 | test('it renders a tag with the given tag name', async function (assert) { 44 | await render(hbs` 45 | {{#let (element "h1") as |Tag|}} 46 | hello world! 47 | {{/let}} 48 | `); 49 | 50 | assert.dom('h1#content').hasText('hello world!'); 51 | }); 52 | 53 | test('it does not render any tags when passed an empty string', async function (assert) { 54 | await render(hbs` 55 | {{#let (element "") as |Tag|}} 56 | hello world! 57 | {{/let}} 58 | `); 59 | 60 | assert.strictEqual(this.element.innerHTML.trim(), 'hello world!'); 61 | }); 62 | 63 | test('it does not render anything when passed null', async function (assert) { 64 | await render(hbs` 65 | {{#let (element null) as |Tag|}} 66 | hello world! 67 | {{/let}} 68 | `); 69 | 70 | assert.strictEqual(this.element.innerHTML.trim(), ''); 71 | }); 72 | 73 | test('it does not render anything when passed undefined', async function (assert) { 74 | await render(hbs` 75 | {{#let (element undefined) as |Tag|}} 76 | hello world! 77 | {{/let}} 78 | `); 79 | 80 | assert.strictEqual(this.element.innerHTML.trim(), ''); 81 | }); 82 | 83 | test('it works with element modifiers', async function (assert) { 84 | let clicked = 0; 85 | 86 | this.set('didClick', () => clicked++); 87 | 88 | // https://github.com/ember-cli/babel-plugin-htmlbars-inline-precompile/issues/103 89 | await render( 90 | hbs( 91 | '\ 92 | {{#let (element "button") as |Tag|}}\ 93 | hello world!\ 94 | {{/let}}\ 95 | ', 96 | { insertRuntimeErrors: true }, 97 | ), 98 | ); 99 | 100 | assert 101 | .dom('button#action') 102 | .hasAttribute('type', 'button') 103 | .hasText('hello world!'); 104 | assert.strictEqual(clicked, 0, 'never clicked'); 105 | 106 | await click('button#action'); 107 | 108 | assert.strictEqual(clicked, 1, 'clicked once'); 109 | 110 | await click('button#action'); 111 | 112 | assert.strictEqual(clicked, 2, 'clicked twice'); 113 | }); 114 | 115 | test('it can be rendered multiple times', async function (assert) { 116 | await render(hbs` 117 | {{#let (element "h1") as |Tag|}} 118 | hello 119 | world 120 | !!!!! 121 | {{/let}} 122 | `); 123 | 124 | assert.dom('h1#content-1').hasText('hello'); 125 | assert.dom('h1#content-2').hasText('world'); 126 | assert.dom('h1#content-3').hasText('!!!!!'); 127 | }); 128 | 129 | test('it can be passed to the component helper', async function (assert) { 130 | await render(hbs` 131 | {{#let (component (ensure-safe-component (element "h1"))) as |Tag|}} 132 | hello 133 | {{/let}} 134 | 135 | {{#let (element "h2") as |h2|}} 136 | {{#let (ensure-safe-component h2) as |Tag|}} 137 | world 138 | {{/let}} 139 | {{/let}} 140 | 141 | {{#let (element "h3") as |h3|}} 142 | {{#component (ensure-safe-component h3) id="content-3"}}!!!!!{{/component}} 143 | {{/let}} 144 | `); 145 | 146 | assert.dom('h1#content-1').hasText('hello'); 147 | assert.dom('h2#content-2').hasText('world'); 148 | assert.dom('h3#content-3').hasText('!!!!!'); 149 | }); 150 | 151 | test('it renders when the tag name changes', async function (assert) { 152 | let count = 0; 153 | 154 | this.owner.register( 155 | 'helper:counter', 156 | helper(() => ++count), 157 | ); 158 | 159 | this.set('tagName', 'h1'); 160 | 161 | await render(hbs` 162 | {{#let (element this.tagName) as |Tag|}} 163 | rendered {{counter}} time(s) 164 | {{/let}} 165 | `); 166 | 167 | assert.dom('h1#content').hasText('rendered 1 time(s)'); 168 | assert.dom('h2#content').doesNotExist(); 169 | assert.dom('h3#content').doesNotExist(); 170 | 171 | this.set('tagName', 'h2'); 172 | 173 | await settled(); 174 | 175 | assert.dom('h1#content').doesNotExist(); 176 | assert.dom('h2#content').hasText('rendered 2 time(s)'); 177 | assert.dom('h3#content').doesNotExist(); 178 | 179 | this.set('tagName', 'h2'); 180 | 181 | await settled(); 182 | 183 | assert.dom('h1#content').doesNotExist(); 184 | assert.dom('h2#content').hasText('rendered 2 time(s)'); 185 | assert.dom('h3#content').doesNotExist(); 186 | 187 | this.set('tagName', 'h3'); 188 | 189 | await settled(); 190 | 191 | assert.dom('h1#content').doesNotExist(); 192 | assert.dom('h2#content').doesNotExist(); 193 | assert.dom('h3#content').hasText('rendered 3 time(s)'); 194 | 195 | this.set('tagName', ''); 196 | 197 | await settled(); 198 | 199 | assert.dom('h1#content').doesNotExist(); 200 | assert.dom('h2#content').doesNotExist(); 201 | assert.dom('h3#content').doesNotExist(); 202 | 203 | assert.strictEqual(this.element.innerHTML.trim(), 'rendered 4 time(s)'); 204 | 205 | this.set('tagName', 'h1'); 206 | 207 | await settled(); 208 | 209 | assert.dom('h1#content').hasText('rendered 5 time(s)'); 210 | assert.dom('h2#content').doesNotExist(); 211 | assert.dom('h3#content').doesNotExist(); 212 | }); 213 | 214 | test('it can be passed as argument and works with ...attributes', async function (assert) { 215 | this.set('tagName', 'p'); 216 | 217 | await render(hbs` 218 | Test 219 | `); 220 | 221 | assert.dom('p#content').hasText('Test').hasClass('extra'); 222 | 223 | this.set('tagName', 'div'); 224 | 225 | await settled(); 226 | 227 | assert.dom('div#content').hasText('Test').hasClass('extra'); 228 | 229 | this.set('tagName', ''); 230 | 231 | await settled(); 232 | 233 | assert.strictEqual(this.element.innerText.trim(), 'Test'); 234 | 235 | this.set('tagName', 'p'); 236 | 237 | await settled(); 238 | 239 | assert.dom('p#content').hasText('Test').hasClass('extra'); 240 | }); 241 | 242 | test.skip('it can be invoked inline', async function (assert) { 243 | this.set('tagName', 'p'); 244 | 245 | await render(hbs`{{element this.tagName}}`); 246 | 247 | assert.dom('p').exists(); 248 | 249 | this.set('tagName', 'br'); 250 | 251 | await settled(); 252 | 253 | assert.dom('br').exists(); 254 | 255 | this.set('tagName', ''); 256 | 257 | assert.strictEqual(this.element.innerHTML.trim(), ''); 258 | 259 | this.set('tagName', 'p'); 260 | 261 | await settled(); 262 | 263 | assert.dom('p').exists(); 264 | }); 265 | 266 | module('invalid usages', function () { 267 | test('it requires at least one argument', async function () { 268 | expectEmberError( 269 | new Error( 270 | 'Assertion Failed: The `element` helper takes a single positional argument', 271 | ), 272 | ); 273 | 274 | await render(hbs` 275 |
276 | {{#let (element) as |Tag|}} 277 | hello world! 278 | {{/let}} 279 |
280 | `); 281 | }); 282 | 283 | test('it requires no more than one argument', async function () { 284 | expectEmberError( 285 | new Error( 286 | 'Assertion Failed: The `element` helper takes a single positional argument', 287 | ), 288 | ); 289 | 290 | await render(hbs` 291 |
292 | {{#let (element "h1" "h2") as |Tag|}} 293 | hello world! 294 | {{/let}} 295 |
296 | `); 297 | }); 298 | 299 | test('it does not take any named arguments', async function () { 300 | expectEmberError( 301 | new Error( 302 | 'Assertion Failed: The `element` helper does not take any named arguments', 303 | ), 304 | ); 305 | 306 | await render(hbs` 307 |
308 | {{#let (element "h1" id="content") as |Tag|}} 309 | hello world! 310 | {{/let}} 311 |
312 | `); 313 | }); 314 | 315 | test('it does not take a block', async function (assert) { 316 | // Before the EMBER_GLIMMER_ANGLE_BRACKET_BUILT_INS feature was enabled 317 | // in 3.10, the "dash rule" short-circuited this assertion by accident, 318 | // so this was just a no-op but no error was thrown. 319 | if ( 320 | macroCondition(dependencySatisfies('ember-source', '>=3.25.0-beta.0')) 321 | ) { 322 | expectEmberError( 323 | new Error( 324 | 'Attempted to resolve `element`, which was expected to be a component, but nothing was found.', 325 | ), 326 | ); 327 | } else if ( 328 | macroCondition(dependencySatisfies('ember-source', '>=3.10.0-beta.0')) 329 | ) { 330 | expectEmberError( 331 | new Error( 332 | 'Assertion Failed: Helpers may not be used in the block form, for example {{#element}}{{/element}}. Please use a component, or alternatively use the helper in combination with a built-in Ember helper, for example {{#if (element)}}{{/if}}.', 333 | ), 334 | ); 335 | } 336 | 337 | // Due to https://github.com/glimmerjs/glimmer-vm/pull/1073, we need to 338 | // wrap the invalid block in a conditional to ensure the initial render 339 | // complete without errors. This is fixed in Ember 3.16+. 340 | this.set('showBlock', false); 341 | 342 | await render(hbs` 343 |
344 | {{#if this.showBlock}} 345 | {{#element "h1"}}hello world!{{/element}} 346 | {{/if}} 347 |
348 | `); 349 | 350 | assert.dom('h1').doesNotExist(); 351 | 352 | this.set('showBlock', true); 353 | 354 | await settled(); 355 | 356 | assert.dom('h1').doesNotExist(); 357 | }); 358 | 359 | test('it throws when passed a number', async function () { 360 | expectEmberError( 361 | new Error( 362 | 'Assertion Failed: The argument passed to the `element` helper must be a string (you passed `123`)', 363 | ), 364 | ); 365 | 366 | await render(hbs` 367 |
368 | {{#let (element 123) as |Tag|}} 369 | hello world! 370 | {{/let}} 371 |
372 | `); 373 | }); 374 | 375 | test('it throws when passed a boolean', async function () { 376 | expectEmberError( 377 | new Error( 378 | 'Assertion Failed: The argument passed to the `element` helper must be a string (you passed `false`)', 379 | ), 380 | ); 381 | 382 | await render(hbs` 383 |
384 | {{#let (element false) as |Tag|}} 385 | hello world! 386 | {{/let}} 387 |
388 | `); 389 | }); 390 | 391 | test('it throws when passed an object', async function () { 392 | expectEmberError( 393 | new Error( 394 | 'Assertion Failed: The argument passed to the `element` helper must be a string', 395 | ), 396 | ); 397 | 398 | await render(hbs` 399 |
400 | {{#let (element (hash)) as |Tag|}} 401 | hello world! 402 | {{/let}} 403 |
404 | `); 405 | }); 406 | }); 407 | }); 408 | -------------------------------------------------------------------------------- /test-app/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'test-app/app'; 2 | import config from 'test-app/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /test-app/tests/unit/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tildeio/ember-element-helper/6537b14d025608b1dfa07bf04ad3a73bb0b39b13/test-app/tests/unit/.gitkeep -------------------------------------------------------------------------------- /test-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/ember/tsconfig.json", 3 | "glint": { 4 | "environment": [ 5 | "ember-loose", 6 | "ember-template-imports" 7 | ] 8 | }, 9 | "compilerOptions": { 10 | // The combination of `baseUrl` with `paths` allows Ember's classic package 11 | // layout, which is not resolvable with the Node resolution algorithm, to 12 | // work with TypeScript. 13 | "baseUrl": ".", 14 | "paths": { 15 | "test-app/tests/*": [ 16 | "tests/*" 17 | ], 18 | "test-app/*": [ 19 | "app/*" 20 | ], 21 | "*": [ 22 | "types/*" 23 | ] 24 | }, 25 | // for typescript < 5 26 | "moduleResolution": "NodeNext" 27 | }, 28 | "include": [ 29 | "app/**/*", 30 | "tests/**/*", 31 | "types/**/*" 32 | ] 33 | } -------------------------------------------------------------------------------- /test-app/types/global.d.ts: -------------------------------------------------------------------------------- 1 | import 'ember-source/types'; 2 | import 'ember-source/types/preview'; 3 | 4 | import '@glint/environment-ember-loose'; 5 | import '@glint/environment-ember-loose/native-integration'; 6 | 7 | import ElementHelperRegistry from 'ember-element-helper/template-registry'; 8 | 9 | declare module '@glint/environment-ember-loose/registry' { 10 | export default interface Registry extends ElementHelperRegistry { 11 | // Examples 12 | // state: HelperLike<{ Args: {}, Return: State }>; 13 | // attachShadow: ModifierLike<{ Args: { Positional: [State['update']]}}>; 14 | // welcome: typeof Welcome; 15 | } 16 | } 17 | --------------------------------------------------------------------------------