diff --git a/.github/READ_THIS.md b/.github/READ_THIS.md
new file mode 100644
index 00000000000..41ea05d6311
--- /dev/null
+++ b/.github/READ_THIS.md
@@ -0,0 +1,29 @@
+# READ THIS
+
+_not named README because GitHub will display a README.md from this directory at the root_
+
+## Workflows
+
+## Guidelines
+
+### Avoid caches
+
+We first disabled caches for publish due to the risk of cache poisoning.
+
+Later we found caches were allowing CI to pass when updating actions. They would
+later fail when the caches were cycled. To avoid this, and after testing to ensure
+the slowdown was acceptable, we have disabled all caching on actions/setup-node
+and pnpm/action-setup.
+
+### Actions must be pinned to shas
+
+Actions are pinned to shas and this is a requirement of the GitHub organization.
+This prevents the risk of a malicious action being used -- as releases can be reset
+to different shas.
+
+To update actions to shas run `pnpm actions-up`.
+
+### Run Zizmor
+
+The workflow-lint.yml workflow runs [Zizmor](https://github.com/zizmorcore/zizmor)
+on all workflows and actions to statically check for insecure patterns.
diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml
index b5cfa8f9fdb..cb111598d02 100644
--- a/.github/actions/setup/action.yml
+++ b/.github/actions/setup/action.yml
@@ -2,40 +2,43 @@ name: Setup node and pnpm
description: Setup node and install dependencies using pnpm
inputs:
use_lockfile:
- description: 'Whether to use the lockfile vs latest floating dependencies'
+ description: "Whether to use the lockfile vs latest floating dependencies"
required: false
- default: 'true'
+ default: "true"
node-version:
- description: 'The node version to use'
+ description: "The node version to use"
required: false
- default: '20'
+ default: "20"
use-node-modules-cache:
- description: 'Whether to use the node-modules cache'
+ description: "Whether to use the node-modules cache"
required: false
- default: 'true'
+ default: "false" # We don't want to use the cache. It causes false passes of CI when updating actions and workflows.
runs:
- using: 'composite'
+ using: "composite"
steps:
- name: Install pnpm
uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
with:
+ cache: false
+ version: 10.33.2
run_install: false
- name: Install Node.js (WITH cache)
if: ${{ inputs.use-node-modules-cache == 'true' }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: '${{ inputs.node-version }}'
- registry-url: 'https://registry.npmjs.org'
+ node-version: "${{ inputs.node-version }}"
+ registry-url: "https://registry.npmjs.org"
cache: pnpm
- name: Install Node.js (NO cache)
if: ${{ inputs.use-node-modules-cache != 'true' }}
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: '${{ inputs.node-version }}'
- registry-url: 'https://registry.npmjs.org'
+ package-manager-cache: false
+ node-version: "${{ inputs.node-version }}"
+ registry-url: "https://registry.npmjs.org"
- run: pnpm install ${INSTALL_OPTIONS}
env:
diff --git a/.github/workflows/ci-jobs.yml b/.github/workflows/ci-jobs.yml
index 73f644736d9..34f1f04b741 100644
--- a/.github/workflows/ci-jobs.yml
+++ b/.github/workflows/ci-jobs.yml
@@ -260,7 +260,7 @@ jobs:
name: Perf script still works
runs-on: ubuntu-latest
timeout-minutes: 10
- if: ${{ !startsWith(github.ref, 'refs/tags/') }} # Don't run on tags
+ if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/main' }} # Only run on PRs and main
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index c90741abdec..76ef654e54e 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -2,7 +2,7 @@ name: Generate Docs
on:
push:
tags:
- - 'v*'
+ - "v*"
workflow_dispatch:
inputs:
ref:
@@ -10,9 +10,9 @@ on:
required: true
description: Tag name or branch or ref to generate docs for
-permissions:
+permissions:
contents: read
-
+
jobs:
generate-and-pr:
runs-on: ubuntu-latest
@@ -42,12 +42,14 @@ jobs:
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
name: Install pnpm
with:
+ cache: false
+ version: 10.33.2
run_install: false
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
- node-version: '20'
+ node-version: "20"
package-manager-cache: false
- name: Install dependencies for ember-jsonapi-docs
diff --git a/.github/workflows/publish-to-npm.yml b/.github/workflows/publish-to-npm.yml
index 8ad17dec492..5a21e12e4b9 100644
--- a/.github/workflows/publish-to-npm.yml
+++ b/.github/workflows/publish-to-npm.yml
@@ -24,7 +24,7 @@ jobs:
- uses: ./.github/actions/setup
with:
node-version: 20
- use-node-modules-cache: false
+ use-node-modules-cache: false # Absolutely no cache should be used when publishing due to the potential of cache-poisoning.
- name: Update npm
run: npm install -g npm@latest # npm >= 11.5.1 is needed
- name: Build for Publish (Alpha)
diff --git a/.github/workflows/smoke-test-app-template-updates.yml b/.github/workflows/smoke-test-app-template-updates.yml
index 8b07719343a..874b567c609 100644
--- a/.github/workflows/smoke-test-app-template-updates.yml
+++ b/.github/workflows/smoke-test-app-template-updates.yml
@@ -3,7 +3,7 @@ name: Update smoke-test templates
on:
workflow_dispatch:
schedule:
- - cron: '0 9 * * 1' # weekly on Monday; job will self-gate to every 10 weeks
+ - cron: "0 9 * * 1" # weekly on Monday; job will self-gate to every 10 weeks
permissions:
contents: read
@@ -43,9 +43,13 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
+ package-manager-cache: false
node-version: 20
- name: Setup pnpm
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
+ uses: pnpm/action-setup@91ab88e2619ed1f46221f0ba42d1492c02baf788 # v6.0.6
+ with:
+ cache: false
+ version: 10.33.2
- name: Generate ember-test-app using classic blueprint
run: |
set -euo pipefail
@@ -61,7 +65,7 @@ jobs:
run: pnpm install --no-frozen-lockfile
- name: Create PR
- uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
+ uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.SMOKE_TEST_TEMPLATE_GEN_TOKEN }}
branch: smoke-tests/update-app-template
@@ -86,9 +90,13 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
+ package-manager-cache: false
node-version: 20
- name: Setup pnpm
- uses: pnpm/action-setup@903f9c1a6ebcba6cf41d87230be49611ac97822e # v6.0.3
+ uses: pnpm/action-setup@91ab88e2619ed1f46221f0ba42d1492c02baf788 # v6.0.6
+ with:
+ cache: false
+ version: 10.33.2
- name: Generate v2-app-template
run: |
set -euo pipefail
@@ -103,7 +111,7 @@ jobs:
run: pnpm install --no-frozen-lockfile
- name: Create PR
- uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0
+ uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.SMOKE_TEST_TEMPLATE_GEN_TOKEN }}
branch: smoke-tests/update-v2-app-template
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ee14276c1c0..e2ad657e834 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
# Ember Changelog
+## v7.1.0 (June 22, 2026)
+
+- [#21068](https://github.com/emberjs/ember.js/pull/21068) [FEATURE] Build in the `{{on}}` modifier as a keyword in strict-mode templates per [RFC# 997](https://rfcs.emberjs.com/id/0997-make-on-built-in).
+- [#21299](https://github.com/emberjs/ember.js/pull/21299) [FEATURE] Build in the `{{fn}}` helper as a keyword in strict-mode templates per [RFC #998](https://rfcs.emberjs.com/id/0998-make-fn-built-in).
+- [#21230](https://github.com/emberjs/ember.js/pull/21230) / [#21343](https://github.com/emberjs/ember.js/pull/21343) [FEATURE] Add a built-in `(element)`/ `{{element}}` helper for dynamic tag names in strict-mode templates per [RFC #389](https://rfcs.emberjs.com/id/0389-dynamic-tag-names).
+- [#21334](https://github.com/emberjs/ember.js/pull/21334) [FEATURE] Build in the `{{hash}}` helper as a keyword in strict-mode templates per [RFC# 999](https://rfcs.emberjs.com/id/0999-make-hash-built-in).
+- [#21336](https://github.com/emberjs/ember.js/pull/21336) [FEATURE] Build in the `{{array}}` helper as a keyword in strict-mode templates per [RFC#1000](https://rfcs.emberjs.com/id/1000-make-array-built-in).
+- [#21337](https://github.com/emberjs/ember.js/pull/21337) [FEATURE] Build in `{{and}}`, `{{or}}`, `{{not}}` helpers as keywords in strict-mode templates per [RFC #562](https://rfcs.emberjs.com/id/0562-add-logical-operators/).
+- [#21339](https://github.com/emberjs/ember.js/pull/21339) [FEATURE] Build in `{{eq}}` and `{{neq}}` helpers as keywords in strict-mode templates per [RFC #560](https://rfcs.emberjs.com/id/0560-add-equality-operators/).
+- [#21342](https://github.com/emberjs/ember.js/pull/21342) [FEATURE] Build in the `{{lt}}`, `{{lte}}`, `{{gt}}`, `{{gte}}` helpers as keywords in strict-mode templates per [RFC# 561](https://rfcs.emberjs.com/id/0561-add-numeric-comparison-operators).
+- [#21373](https://github.com/emberjs/ember.js/pull/21373) [BUGFIX] Fix newly added keywords polluting the JS namespace
+- [#21232](https://github.com/emberjs/ember.js/pull/21232) [INTERNAL] Clean up scope bag, wire format, and debug render tree
+- [#21350](https://github.com/emberjs/ember.js/pull/21350) [INTERNAL] Remove barrel file imports from internal code for better tree-shaking
+- [#21402](https://github.com/emberjs/ember.js/pull/21402) [INTERNAL] Do not use any caches on release and lock publishing of `ember-source` to `npm` to require approval from select persons.
+- [#21379](https://github.com/emberjs/ember.js/pull/21379) [CLEANUP] Remove old Component Lookup
+- [#21468](https://github.com/emberjs/ember.js/pull/21468) [DOC] Update API docs to use template tag where appropriate
+
## v7.0.0 (May 11, 2026)
- [#21240](https://github.com/emberjs/ember.js/pull/21240) [CLEANUP] Remove deprecated amd bundles and `use-ember-modules` optional feature per [RFC #1101](https://rfcs.emberjs.com/id/1101-deprecate-ember-vendor-bundles).
diff --git a/package.json b/package.json
index 51e8d17d769..2bc96fc1282 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ember-source",
- "version": "7.1.0-alpha.1",
+ "version": "7.1.0",
"description": "A JavaScript framework for creating ambitious web applications",
"keywords": [
"ember-addon"
diff --git a/packages/@ember/-internals/glimmer/index.ts b/packages/@ember/-internals/glimmer/index.ts
index 66afa93812e..abb5506b78f 100644
--- a/packages/@ember/-internals/glimmer/index.ts
+++ b/packages/@ember/-internals/glimmer/index.ts
@@ -1,6 +1,23 @@
/**
[Glimmer](https://github.com/tildeio/glimmer) is a templating engine used by Ember.js that is compatible with a subset of the [Handlebars](http://handlebarsjs.com/) syntax.
+ Ember ships with two types of JavaScript classes for components:
+
+ 1. Glimmer components, imported from `@glimmer/component`, which are the
+ default component's for Ember Octane (3.15) and more recent editions.
+ 2. Classic components, imported from `@ember/component`, which were the
+ default for older editions of Ember (pre 3.15) but are still supported.
+
+ Below is the documentation for Classic components. If you are looking for the
+ API documentation for Template-only or Glimmer components, it is [available
+ here](/ember/release/modules/@glimmer%2Fcomponent).
+
+ Note: Prior to Ember 6.8, by default, components were authored in paired `.hbs` and `.js`
+ files. This is still supported, but the default authoring format is now `.gjs` or "template tag".
+ The documentation for `@ember/component` still refers to the older authoring format. To read about
+ the new authoring format, see the
+ [Glimmer Component API documentation](/ember/release/modules/@glimmer%2Fcomponent).
+
### Showing a property
Templates manage the flow of an application's UI, and display state (through
@@ -98,16 +115,24 @@
When designing components `{{yield}}` is used to denote where, inside the component's
template, an optional block passed to the component should render:
- ```app/templates/application.hbs
-
- First name:
-
+ ```app/templates/application.gjs
+ import LabeledTextField from '../components/labeled-textfield';
+
+
+
+ First name:
+
+
```
- ```app/components/labeled-textfield.hbs
-
+ ```app/components/labeled-textfield.gjs
+ import { Input } from '@ember/component';
+
+
+
+
```
Result:
@@ -120,19 +145,34 @@
Additionally you can `yield` properties into the context for use by the consumer:
- ```app/templates/application.hbs
-
- {{#if validationError}}
-
+
```
Result:
@@ -198,15 +250,19 @@
Multiple components can be yielded with the `hash` and `component` helper:
- ```app/templates/application.hbs
-
- Banner title
- Banner subtitle
- A load of body text
-
+ ```app/templates/application.gjs
+ import Banner from '../components/banner';
+
+
+
+ Banner title
+ Banner subtitle
+ A load of body text
+
+
```
- ```app/components/banner.js
+ ```app/components/banner.gjs
import Title from './banner/title';
import Subtitle from './banner/subtitle';
import Body from './banner/body';
@@ -215,19 +271,19 @@
Title = Title;
Subtitle = Subtitle;
Body = Body;
+
+
+
- ```
-
Result:
```html
@@ -240,12 +296,16 @@
A benefit of using this pattern is that the user of the component can change the order the components are displayed.
- ```app/templates/application.hbs
-
- Banner subtitle
- Banner title
- A load of body text
-
+ ```app/templates/application.gjs
+ import Banner from '../components/banner';
+
+
+
+ Banner subtitle
+ Banner title
+ A load of body text
+
+
```
Result:
@@ -261,27 +321,33 @@
Another benefit to using `yield` with the `hash` and `component` helper
is you can pass attributes and arguments to these components:
- ```app/templates/application.hbs
-
- Banner subtitle
- Banner title
- A load of body text
-
+ ```app/templates/application.gjs
+ import Banner from '../components/banner';
+
+
+
+ Banner subtitle
+ Banner title
+ A load of body text
+
+
```
- ```app/components/banner/subtitle.hbs
+ ```app/components/banner/subtitle.gjs
{{!-- note the use of ..attributes --}}
- {{/if}}
+ {{/if}}
+
```
Result:
@@ -307,36 +373,28 @@
This component is invoked with a block:
```handlebars
- {{#my-component}}
+
Hi Jen!
- {{/my-component}}
+
```
This component is invoked without a block:
```handlebars
- {{my-component}}
- ```
-
- Using angle bracket invocation, this looks like:
-
- ```html
- Hi Jen! {{! with a block}}
- ```
-
- ```html
- {{! without a block}}
+
```
This is useful when you want to create a component that can optionally take a block
and then render a default template when it is not invoked with a block.
- ```app/templates/components/my-component.hbs
- {{#if (has-block)}}
- Welcome {{yield}}, we are happy you're here!
- {{else}}
- Hey you! You're great!
- {{/if}}
+ ```app/components/my-component.gjs
+
+ {{#if (has-block)}}
+ Welcome {{yield}}, we are happy you're here!
+ {{else}}
+ Hey you! You're great!
+ {{/if}}
+
```
@method has-block
@@ -350,26 +408,10 @@
`{{(has-block-params)}}` indicates if the component was invoked with block params.
This component is invoked with block params:
-
- ```handlebars
- {{#my-component as |favoriteFlavor|}}
- Hi Jen!
- {{/my-component}}
- ```
-
- This component is invoked without block params:
-
+
```handlebars
- {{#my-component}}
- Hi Jenn!
- {{/my-component}}
- ```
-
- With angle bracket syntax, block params look like this:
-
- ```handlebars
- Hi Jen!
+ Hi Jen!
```
@@ -377,21 +419,23 @@
```handlebars
- Hi Jen!
+ Hi Jen!
```
This is useful when you want to create a component that can render itself
differently when it is not invoked with block params.
- ```app/templates/components/my-component.hbs
- {{#if (has-block-params)}}
- Welcome {{yield this.favoriteFlavor}}, we're happy you're here and hope you
- enjoy your favorite ice cream flavor.
- {{else}}
- Welcome {{yield}}, we're happy you're here, but we're unsure what
- flavor ice cream you would enjoy.
- {{/if}}
+ ```app/components/my-component.gjs
+
+ {{#if (has-block-params)}}
+ Welcome {{yield this.favoriteFlavor}}, we're happy you're here and hope you
+ enjoy your favorite ice cream flavor.
+ {{else}}
+ Welcome {{yield}}, we're happy you're here, but we're unsure what
+ flavor ice cream you would enjoy.
+ {{/if}}
+
```
@method has-block-params
diff --git a/packages/@ember/-internals/glimmer/lib/component.ts b/packages/@ember/-internals/glimmer/lib/component.ts
index f61e446f829..b4f64601824 100644
--- a/packages/@ember/-internals/glimmer/lib/component.ts
+++ b/packages/@ember/-internals/glimmer/lib/component.ts
@@ -230,6 +230,12 @@ declare const SIGNATURE: unique symbol;
API documentation for Template-only or Glimmer components, it is [available
here](/ember/release/modules/@glimmer%2Fcomponent).
+ Note: Prior to Ember 6.8, by default, components were authored in paired `.hbs` and `.js`
+ files. This is still supported, but the default authoring format is now `.gjs` or "template tag".
+ The documentation for `@ember/component` still refers to the older authoring format. To read about
+ the new authoring format, see the
+ [Glimmer Component API documentation](/ember/release/modules/@glimmer%2Fcomponent).
+
## Defining a Classic Component
If you want to customize the component in order to handle events, transform
@@ -256,7 +262,7 @@ declare const SIGNATURE: unique symbol;
And then use it in the component's template:
- ```app/templates/components/person-profile.hbs
+ ```app/components/person-profile.hbs
{{this.displayName}}
{{yield}}
```
@@ -644,7 +650,7 @@ declare const SIGNATURE: unique symbol;
The `layout` property should be set to the default export of a template
module, which is the name of a template file without the `.hbs` extension.
- ```app/templates/components/person-profile.hbs
+ ```app/components/person-profile.hbs
Person's Title
{{yield}}
```
diff --git a/packages/@ember/-internals/glimmer/lib/components/input.ts b/packages/@ember/-internals/glimmer/lib/components/input.ts
index cd9898555dc..07f67109a83 100644
--- a/packages/@ember/-internals/glimmer/lib/components/input.ts
+++ b/packages/@ember/-internals/glimmer/lib/components/input.ts
@@ -67,8 +67,12 @@ if (hasDOM) {
/**
The `Input` component lets you create an HTML `` element.
- ```handlebars
-
+ ```gjs
+ import { Input } from '@ember/component';
+
+
+
+
```
creates an `` element with `type="text"` and value set to 987.
diff --git a/packages/@ember/-internals/glimmer/lib/components/link-to.ts b/packages/@ember/-internals/glimmer/lib/components/link-to.ts
index 4f11670388c..5b69b46e1f6 100644
--- a/packages/@ember/-internals/glimmer/lib/components/link-to.ts
+++ b/packages/@ember/-internals/glimmer/lib/components/link-to.ts
@@ -50,10 +50,14 @@ function isQueryParams(value: unknown): value is QueryParams {
supplied model to the route as its `model` context of the route. The block for `LinkTo`
becomes the contents of the rendered element:
- ```handlebars
-
- Great Hamster Photos
-
+ ```gjs
+ import { LinkTo } from '@ember/routing';
+
+
+
+ Great Hamster Photos
+
+
```
This will result in:
diff --git a/packages/@ember/-internals/glimmer/lib/components/textarea.ts b/packages/@ember/-internals/glimmer/lib/components/textarea.ts
index 3ab5f40c178..874b0ccd0a4 100644
--- a/packages/@ember/-internals/glimmer/lib/components/textarea.ts
+++ b/packages/@ember/-internals/glimmer/lib/components/textarea.ts
@@ -15,7 +15,11 @@ import { type OpaqueInternalComponentConstructor, opaquify } from './internal';
This template:
```handlebars
-
+ import { Textarea } from '@ember/component';
+
+
+
+
```
Would result in the following HTML:
@@ -32,19 +36,19 @@ import { type OpaqueInternalComponentConstructor, opaquify } from './internal';
In the following example, the `writtenWords` property on the component will be updated as the user
types 'Lots of text' into the text area of their browser's window.
- ```app/components/word-editor.js
+ ```app/components/word-editor.gjs
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
export default class WordEditorComponent extends Component {
@tracked writtenWords = "Lots of text that IS bound";
+
+
+
+
}
```
- ```handlebars
-
- ```
-
Would result in the following HTML:
```html
diff --git a/packages/@ember/-internals/glimmer/lib/glimmer-tracking-docs.ts b/packages/@ember/-internals/glimmer/lib/glimmer-tracking-docs.ts
index fa7776f65fc..87dbdce419a 100644
--- a/packages/@ember/-internals/glimmer/lib/glimmer-tracking-docs.ts
+++ b/packages/@ember/-internals/glimmer/lib/glimmer-tracking-docs.ts
@@ -26,17 +26,7 @@
property changes, any templates that used that property, directly or
indirectly, will rerender. For instance, consider this component:
- ```handlebars
-
+
}
```
@@ -61,7 +61,7 @@
will cause a rerender when updated - this includes through method calls and
other means:
- ```javascript
+ ```gjs
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
diff --git a/packages/@ember/-internals/glimmer/lib/helper.ts b/packages/@ember/-internals/glimmer/lib/helper.ts
index 9f99e1d9829..fff4a1a28c1 100644
--- a/packages/@ember/-internals/glimmer/lib/helper.ts
+++ b/packages/@ember/-internals/glimmer/lib/helper.ts
@@ -58,20 +58,32 @@ declare const SIGNATURE: unique symbol;
Ember Helpers are functions that can compute values, and are used in templates.
For example, this code calls a helper named `format-currency`:
- ```app/templates/application.hbs
-
+ ```app/templates/application.gjs
+ import Cost from '../components/cost';
+
+
+
+
```
- ```app/components/cost.hbs
-
{{format-currency @cents currency="$"}}
+ ```app/components/cost.gjs
+ import formatCurrency from '../helpers/format-currency';
+
+
+
{{formatCurrency @cents currency="$"}}
+
```
Additionally a helper can be called as a nested helper.
In this example, we show the formatted currency value if the `showMoney`
named argument is truthy.
- ```handlebars
- {{if @showMoney (format-currency @cents currency="$")}}
+ ```gjs
+ import formatCurrency from '../helpers/format-currency';
+
+
+ {{if @showMoney (formatCurrency @cents currency="$")}}
+
```
Helpers defined using a class must provide a `compute` function. For example:
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/array.ts b/packages/@ember/-internals/glimmer/lib/helpers/array.ts
index f694c148082..870414067fb 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/array.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/array.ts
@@ -15,7 +15,7 @@
```
or
```handlebars
- {{my-component people=(array
+ {{yield people=(array
'Tom Dale'
'Yehuda Katz'
this.myOtherPerson)
@@ -29,6 +29,8 @@
```
Where the 3rd item in the array is bound to updates of the `myOtherPerson` property.
+
+ The `array` helper is built-in keyword and does not need to be imported as of v7.1.0.
@method array
@for @ember/helper
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/component.ts b/packages/@ember/-internals/glimmer/lib/helpers/component.ts
index 871839c21f8..b669c39ebbb 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/component.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/component.ts
@@ -3,143 +3,87 @@
*/
/**
- The `{{component}}` helper lets you add instances of `Component` to a
- template. See [Component](/ember/release/classes/Component) for
- additional information on how a `Component` functions.
- `{{component}}`'s primary use is for cases where you want to dynamically
- change which type of component is rendered as the state of your application
- changes. This helper has three modes: inline, block, and nested.
-
- ### Inline Form
-
- Given the following template:
-
- ```app/application.hbs
- {{component this.infographicComponentName}}
- ```
-
- And the following application code:
-
- ```app/controllers/application.js
- import Controller from '@ember/controller';
- import { tracked } from '@glimmer/tracking';
-
- export default class ApplicationController extends Controller {
- @tracked isMarketOpen = 'live-updating-chart'
-
- get infographicComponentName() {
- return this.isMarketOpen ? 'live-updating-chart' : 'market-close-summary';
- }
- }
- ```
+ The `component` helper is used to package a Component with initial arguments.
+ The included arguments can then be merged during the final invocation.
- The `live-updating-chart` component will be appended when `isMarketOpen` is
- `true`, and the `market-close-summary` component will be appended when
- `isMarketOpen` is `false`. If the value changes while the app is running,
- the component will be automatically swapped out accordingly.
- Note: You should not use this helper when you are consistently rendering the same
- component. In that case, use standard component syntax, for example:
+ See [Component](/ember/release/modules/@glimmer%2Fcomponent/) for
+ additional information on how a `Component` functions.
- ```app/templates/application.hbs
-
- ```
+ This is similar to the concept of Partial Application.
+
+ For example, given a `FullName` component:
+
+ ```app/components/full-name.gjs
+ import MyInputComponent from './my-input-component';
+
+
+ {{yield (component MyInputComponent value=@model.name placeholder="Username")}}
+
+ ```
+
+ The yielded component can be invoked by the calling component.
+ See the following snippet:
+
+ ```app/components/person-form.gjs
+ import FullName from './full-name';
+
+
+
+
+
+
+ ```
+
+ Which will output an input whose value is already bound to `@model.name` and `placeholder`
+ is "Username".
+
+ Any arguments passed at the invocation site of the component will override those applied via
+ the `component` helper. For example, if the invocation site of the component is:
- or
+ ```app/components/person-form.gjs
+ import FullName from './full-name';
- ```app/templates/application.hbs
- {{live-updating-chart}}
+
+
+
+
+
```
- ### Block Form
-
- Using the block form of this helper is similar to using the block form
- of a component. Given the following application template:
+ The output will be an input whose value is bound to `@model.name` and `placeholder`
+ is "Your name".
+
+ The `component` helper is built-in and does not need to be imported.
+
+ Prior to Strict Mode aka "Template Tag" or gjs, the component helper was also used to invoke
+ components dynamically. This is no longer necessary, and they can be directly invoked, as above.
- ```app/templates/application.hbs
- {{#component this.infographicComponentName}}
- Last update: {{this.lastUpdateTimestamp}}
- {{/component}}
- ```
-
- The following controller code:
+ ### Dynamic Component Invocation
- ```app/controllers/application.js
- import Controller from '@ember/controller';
- import { computed } from '@ember/object';
+ ```app/templates/application.gjs
+ import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
+ import { component } from '@ember/helper';
+ import LiveUpdatingChart from '../components/live-updating-chart';
+ import MarketCloseSummary from '../components/market-close-summary';
- export default class ApplicationController extends Controller {
- @tracked isMarketOpen = 'live-updating-chart'
+ export default class Application extends Component {
+ @tracked isMarketOpen = false;
- get lastUpdateTimestamp() {
- return new Date();
+ get infographicComponent() {
+ return this.isMarketOpen ? LiveUpdatingChart : MarketCloseSummary;
}
- get infographicComponentName() {
- return this.isMarketOpen ? 'live-updating-chart' : 'market-close-summary';
- }
+
+ {{!-- The component can be invoked directly --}}
+
+
+ {{!-- The component helper here is no longer necessary --}}
+ {{component this.infographicComponentName}}
+
}
```
- And the following component template:
-
- ```app/templates/components/live-updating-chart.hbs
- {{! chart }}
- {{yield}}
- ```
-
- The `Last Update: {{this.lastUpdateTimestamp}}` will be rendered in place of the `{{yield}}`.
-
- ### Nested Usage
-
- The `component` helper can be used to package a component path with initial attrs.
- The included attrs can then be merged during the final invocation.
- For example, given a `person-form` component with the following template:
-
- ```app/templates/components/person-form.hbs
- {{yield (hash
- nameInput=(component "my-input-component" value=@model.name placeholder="First Name")
- )}}
- ```
-
- When yielding the component via the `hash` helper, the component is invoked directly.
- See the following snippet:
-
- ```
-
-
-
- ```
-
- or
- ```
- {{#person-form as |form|}}
- {{form.nameInput placeholder="Username"}}
- {{/person-form}}
- ```
-
- Which outputs an input whose value is already bound to `model.name` and `placeholder`
- is "Username".
-
- When yielding the component without the `hash` helper use the `component` helper.
- For example, below is a `full-name` component template:
-
- ```handlebars
- {{yield (component "my-input-component" value=@model.name placeholder="Name")}}
- ```
-
- ```
-
- {{component field placeholder="Full name"}}
-
- ```
- or
- ```
- {{#full-name as |field|}}
- {{component field placeholder="Full name"}}
- {{/full-name}}
- ```
-
@method component
@since 1.11.0
@for Ember.Templates.helpers
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/concat.ts b/packages/@ember/-internals/glimmer/lib/helpers/concat.ts
index 0a1609f27f2..f392a35d0e2 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/concat.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/concat.ts
@@ -7,13 +7,17 @@
Example:
- ```handlebars
- {{some-component name=(concat firstName " " lastName)}}
-
- {{! would pass name="" to the component}}
+ ```gjs
+ import { concat } from '@ember/helper';
+
+
+ {{yield (concat firstName " " lastName)}}
+
+ {{! would yield name="" to the component}}
+
```
- or for angle bracket invocation, you actually don't need concat at all.
+ For invocation of components, you don't need concat at all.
```handlebars
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/each-in.ts b/packages/@ember/-internals/glimmer/lib/helpers/each-in.ts
index 1e4c51bc083..e49a1132e4f 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/each-in.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/each-in.ts
@@ -11,7 +11,7 @@ import { consumeTag } from '@glimmer/validator/lib/tracking';
import { internalHelper } from './internal-helper';
/**
- The `{{#each}}` helper loops over elements in a collection. It is an extension
+ The `{{#each}}` keyword loops over elements in a collection. It is an extension
of the base Handlebars `{{#each}}` helper.
The default behavior of `{{#each}}` is to yield its inner block once for every
@@ -55,6 +55,8 @@ import { internalHelper } from './internal-helper';
{{/each}}
```
+
+ `#each` is a keyword and does not need to be imported.
### Specifying Keys
@@ -122,11 +124,11 @@ import { internalHelper } from './internal-helper';
*/
/**
- The `{{each-in}}` helper loops over properties on an object.
+ The `{{#each-in}}` keyword loops over properties on an object.
For example, given this component definition:
- ```app/components/developer-details.js
+ ```app/components/developer-details.gjs
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
@@ -135,21 +137,19 @@ import { internalHelper } from './internal-helper';
"name": "Shelly Sails",
"age": 42
};
+
+
+
+ {{#each-in this.developer as |key value|}}
+
{{key}}: {{value}}
+ {{/each-in}}
+
+
}
```
This template would display all properties on the `developer`
- object in a list:
-
- ```app/components/developer-details.hbs
-
- {{#each-in this.developer as |key value|}}
-
{{key}}: {{value}}
- {{/each-in}}
-
- ```
-
- Outputting their name and age:
+ object in a list, outputting their name and age:
```html
@@ -157,6 +157,8 @@ import { internalHelper } from './internal-helper';
age: 42
```
+
+ `#each-in` is a keyword and does not need to be imported.
@method each-in
@for Ember.Templates.helpers
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/element.ts b/packages/@ember/-internals/glimmer/lib/helpers/element.ts
index d8800ff9d88..c772369f112 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/element.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/element.ts
@@ -98,6 +98,8 @@ function getElementDefinition(tagName: string): ElementComponentDefinition {
Passing `null`, `undefined`, or non-string values will throw an assertion error.
Changing the tag name will tear down and recreate the element and its contents.
+
+ The `element` helper is built-in and does not need to be imported.
@method element
@for Ember.Templates.helpers
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/fn.ts b/packages/@ember/-internals/glimmer/lib/helpers/fn.ts
index f8bd15d6088..4e89b4e7f00 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/fn.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/fn.ts
@@ -12,13 +12,15 @@
to a component invoked within the loop. Here's how you could use the `fn`
helper to pass both the function and its arguments together:
- ```app/templates/components/items-listing.hbs
- {{#each @items as |item|}}
-
- {{/each}}
+ ```app/components/items-listing.gjs
+
+ {{#each @items as |item|}}
+
+ {{/each}}
+
```
- ```app/components/items-list.js
+ ```app/components/items-list.gjs
import Component from '@glimmer/component';
import { action } from '@ember/object';
@@ -30,7 +32,7 @@
}
```
- In this case the `display-item` component will receive a normal function
+ In this case the `DisplayItem` component will receive a normal function
that it can invoke. When it invokes the function, the `handleSelected`
function will receive the `item` and any arguments passed, thanks to the
`fn` helper.
@@ -47,7 +49,7 @@
properly bound to the `items-list`, but let's explore what happens if we
left out `@action`:
- ```app/components/items-list.js
+ ```app/components/items-list.gjs
import Component from '@glimmer/component';
export default class ItemsList extends Component {
@@ -63,6 +65,8 @@
are bound (via `@action` or other means) before passing into `fn`!
See also [partial application](https://en.wikipedia.org/wiki/Partial_application).
+
+ `fn` is built-in and does not need to be imported.
@method fn
@for @ember/helper
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/get.ts b/packages/@ember/-internals/glimmer/lib/helpers/get.ts
index f0ad61105e7..c6511578565 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/get.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/get.ts
@@ -9,28 +9,33 @@
For example, these two usages are equivalent:
- ```app/components/developer-detail.js
+ ```app/components/developer-detail.gjs
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
+ import { get } from '@ember/object';
export default class extends Component {
@tracked developer = {
name: "Sandi Metz",
language: "Ruby"
}
+
+
+ {{this.developer.name}}
+ {{get this.developer "name"}}
+
}
```
-
- ```handlebars
- {{this.developer.name}}
- {{get this.developer "name"}}
- ```
-
+
If there were several facts about a person, the `{{get}}` helper can dynamically
pick one:
- ```app/templates/application.hbs
-
+ ```app/templates/application.gjs
+ import DeveloperDetail from '../components/developer-detail';
+
+
+
+
+ {{get this.developer this.currentFact}}
-
-
- ```
-
- The `{{get}}` helper can also respect mutable values itself. For example:
-
- ```app/components/developer-detail.js
-
-
-
-
+
+
+
+ }
```
- Would allow the user to swap what fact is being displayed, and also edit
- that fact via a two-way mutable binding.
-
The `{{get}}` helper can also be used for array element access via index.
This would display the value of the first element in the array `this.names`:
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/hash.ts b/packages/@ember/-internals/glimmer/lib/helpers/hash.ts
index e72cd88ee8b..023b3926ddd 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/hash.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/hash.ts
@@ -4,7 +4,7 @@
/**
Use the `{{hash}}` helper to create a hash to pass as an option to your
- components. This is specially useful for contextual components where you can
+ components. This is especially useful for contextual components where you can
just yield a hash:
```handlebars
@@ -32,6 +32,8 @@
return Object.prototype.toString.apply(obj);
}
```
+
+ The `hash` helper is a built-in keyword and does not need to be imported as of v7.1.0.
@method hash
@for @ember/helper
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/if-unless.ts b/packages/@ember/-internals/glimmer/lib/helpers/if-unless.ts
index ad6fe62ea0c..7e1ee11bf3e 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/if-unless.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/if-unless.ts
@@ -17,26 +17,34 @@
using the block form to wrap the section of template you want to conditionally render.
Like so:
- ```app/templates/application.hbs
-
+ ```app/templates/application.gjs
+ import Weather from '../components/weather';
+
+
+
+
```
- ```app/components/weather.hbs
- {{! will not render because greeting is undefined}}
- {{#if @isRaining}}
- Yes, grab an umbrella!
- {{/if}}
+ ```app/components/weather.gjs
+
+ {{! will not render because greeting is undefined}}
+ {{#if @isRaining}}
+ Yes, grab an umbrella!
+ {{/if}}
+
```
You can also define what to show if the property is falsey by using
the `else` helper.
- ```app/components/weather.hbs
- {{#if @isRaining}}
- Yes, grab an umbrella!
- {{else}}
- No, it's lovely outside!
- {{/if}}
+ ```app/components/weather.gjs
+
+ {{#if @isRaining}}
+ Yes, grab an umbrella!
+ {{else}}
+ No, it's lovely outside!
+ {{/if}}
+
```
You are also able to combine `else` and `if` helpers to create more complex
@@ -44,20 +52,26 @@
For the following template:
- ```app/components/weather.hbs
- {{#if @isRaining}}
- Yes, grab an umbrella!
- {{else if @isCold}}
- Grab a coat, it's chilly!
- {{else}}
- No, it's lovely outside!
- {{/if}}
+ ```app/components/weather.gjs
+
+ {{#if @isRaining}}
+ Yes, grab an umbrella!
+ {{else if @isCold}}
+ Grab a coat, it's chilly!
+ {{else}}
+ No, it's lovely outside!
+ {{/if}}
+
```
If you call it by saying `isCold` is true:
- ```app/templates/application.hbs
-
+ ```app/templates/application.gjs
+ import Weather from '../components/weather';
+
+
+
+
```
Then `Grab a coat, it's chilly!` will be rendered.
@@ -71,12 +85,18 @@
For example, if `useLongGreeting` is truthy, the following:
- ```app/templates/application.hbs
-
+ ```app/templates/application.gjs
+ import Greeting from '../components/greeting';
+
+
+
+
```
- ```app/components/greeting.hbs
- {{if @useLongGreeting "Hello" "Hi"}} Alex
+ ```app/components/greeting.gjs
+
+ {{if @useLongGreeting "Hello" "Hi"}} Alex
+
```
Will render:
@@ -88,7 +108,9 @@
One detail to keep in mind is that both branches of the `if` helper will be evaluated,
so if you have `{{if condition "foo" (expensive-operation "bar")`,
`expensive-operation` will always calculate.
-
+
+ `if` is built-in and does not need to be imported.
+
@method if
@for Ember.Templates.helpers
@public
@@ -108,12 +130,18 @@
For example, if you pass a falsey `useLongGreeting` to the `Greeting` component:
- ```app/templates/application.hbs
-
+ ```app/templates/application.gjs
+ import Greeting from '../components/greeting';
+
+
+
+
```
- ```app/components/greeting.hbs
- {{unless @useLongGreeting "Hi" "Hello"}} Ben
+ ```app/components/greeting.gjs
+
+ {{unless @useLongGreeting "Hi" "Hello"}} Ben
+
```
Then it will display:
@@ -128,14 +156,20 @@
The following will not render anything:
- ```app/templates/application.hbs
-
+ ```app/templates/application.gjs
+ import Greeting from '../components/greeting';
+
+
+
+
```
- ```app/components/greeting.hbs
- {{#unless @greeting}}
- No greeting was found. Why not set one?
- {{/unless}}
+ ```app/components/greeting.gjs
+
+ {{#unless @greeting}}
+ No greeting was found. Why not set one?
+ {{/unless}}
+
```
You can also use an `else` helper with the `unless` block. The
@@ -143,18 +177,24 @@
If you have the following component:
- ```app/components/logged-in.hbs
- {{#unless @userData}}
- Please login.
- {{else}}
- Welcome back!
- {{/unless}}
+ ```app/components/logged-in.gjs
+
+ {{#unless @userData}}
+ Please login.
+ {{else}}
+ Welcome back!
+ {{/unless}}
+
```
Calling it with a truthy `userData`:
- ```app/templates/application.hbs
-
+ ```app/templates/application.gjs
+ import LoggedIn from '../components/logged-in';
+
+
+
+
```
Will render:
@@ -165,8 +205,12 @@
and calling it with a falsey `userData`:
- ```app/templates/application.hbs
-
+ ```app/templates/application.gjs
+ import LoggedIn from '../components/logged-in';
+
+
+
+
```
Will render:
@@ -174,7 +218,9 @@
```html
Please login.
```
-
+
+ `unless` is built-in and does not need to be imported.
+
@method unless
@for Ember.Templates.helpers
@public
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/mut.ts b/packages/@ember/-internals/glimmer/lib/helpers/mut.ts
index 94bb9c2106c..b66cc4aa729 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/mut.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/mut.ts
@@ -7,73 +7,42 @@ import { createInvokableRef, isUpdatableRef } from '@glimmer/reference/lib/refer
import { internalHelper } from './internal-helper';
/**
- The `mut` helper lets you __clearly specify__ that a child `Component` can update the
- (mutable) value passed to it, which will __change the value of the parent component__.
+ The `mut` helper is a shortcut for updating for args.
+
+ However, defining update functions on your backing class is preferable to using `mut`.
+
+ More directly: Don't use `mut`.
- To specify that a parameter is mutable, when invoking the child `Component`:
-
- ```handlebars
-
- ```
-
- or
-
- ```handlebars
- {{my-child childClickCount=(mut totalClicks)}}
- ```
-
- The child `Component` can then modify the parent's value just by modifying its own
- property:
-
- ```javascript
- // my-child.js
- export default class MyChild extends Component {
- click() {
- this.incrementProperty('childClickCount');
- }
- }
- ```
-
- Note that for curly components (`{{my-component}}`) the bindings are already mutable,
- making the `mut` unnecessary.
-
- Additionally, the `mut` helper can be combined with the `fn` helper to
- mutate a value. For example:
-
- ```handlebars
-
- ```
-
- or
+ The `mut` helper, when used with `fn`, will return a function that
+ sets the value passed to `mut` to its first argument. As an example, we can create a
+ button that increments a value passing the value directly to the `fn`:
```handlebars
- {{my-child childClickCount=totalClicks click-count-change=(fn (mut totalClicks))}}
+
```
- The child `Component` would invoke the function with the new click value:
+ The child `Component` would invoke the function with the new click count:
- ```javascript
- // my-child.js
+ ```app/components/my-child.gjs
+ import Component from '@glimmer/component';
+ import { action } from '@ember/object';
+
export default class MyChild extends Component {
- click() {
- this.get('click-count-change')(this.get('childClickCount') + 1);
+ @action
+ update() {
+ this.args.clickCountChange(this.args.childClickCount + 1);
}
+
+
+
+
}
```
The `mut` helper changes the `totalClicks` value to what was provided as the `fn` argument.
- The `mut` helper, when used with `fn`, will return a function that
- sets the value passed to `mut` to its first argument. As an example, we can create a
- button that increments a value passing the value directly to the `fn`:
-
- ```handlebars
- {{! inc helper is not provided by Ember }}
-
- ```
-
@method mut
@param {Object} [attr] the "two-way" attribute that can be modified.
@for Ember.Templates.helpers
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/page-title.ts b/packages/@ember/-internals/glimmer/lib/helpers/page-title.ts
index 3ae5a51a351..3ecdd428410 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/page-title.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/page-title.ts
@@ -7,8 +7,12 @@
append additional titles for each route. For complete documentation, see
https://github.com/ember-cli/ember-page-title.
- ```handlebars
- {{page-title "My Page Title" }}
+ ```gjs
+ import pageTitle from 'ember-page-title/helpers/page-title';
+
+
+ {{pageTitle "My Page Title" }}
+
```
@method page-title
diff --git a/packages/@ember/-internals/glimmer/lib/helpers/readonly.ts b/packages/@ember/-internals/glimmer/lib/helpers/readonly.ts
index b251f82069b..ee06ef4dbdf 100644
--- a/packages/@ember/-internals/glimmer/lib/helpers/readonly.ts
+++ b/packages/@ember/-internals/glimmer/lib/helpers/readonly.ts
@@ -9,6 +9,10 @@ import { internalHelper } from './internal-helper';
/**
The `readonly` helper let's you specify that a binding is one-way only,
instead of two-way.
+
+ This is a vestigial helper from the days of `@ember/component` and does not apply to
+ components extending from `@glimmer/component`.
+
When you pass a `readonly` binding from an outer context (e.g. parent component),
to to an inner context (e.g. child component), you are saying that changing that
property in the inner context does not change the value in the outer context.
@@ -21,14 +25,6 @@ import { internalHelper } from './internal-helper';
}
```
- ```app/templates/components/my-parent.hbs
- {{log totalClicks}} // -> 3
-
- ```
- ```
- {{my-child childClickCount=(readonly totalClicks)}}
- ```
-
Now, when you update `childClickCount`:
```app/components/my-child.js
diff --git a/packages/@ember/-internals/glimmer/lib/modifiers/on.ts b/packages/@ember/-internals/glimmer/lib/modifiers/on.ts
index 97094175680..acbccab9a22 100644
--- a/packages/@ember/-internals/glimmer/lib/modifiers/on.ts
+++ b/packages/@ember/-internals/glimmer/lib/modifiers/on.ts
@@ -10,20 +10,20 @@
For example, if you'd like to run a function on your component when a `