Skip to content

Implement (element) helper for dynamic tag names (RFC #0389)#21230

Open
NullVoxPopuli-ai-agent wants to merge 3 commits intoemberjs:mainfrom
NullVoxPopuli-ai-agent:implement-element-helper
Open

Implement (element) helper for dynamic tag names (RFC #0389)#21230
NullVoxPopuli-ai-agent wants to merge 3 commits intoemberjs:mainfrom
NullVoxPopuli-ai-agent:implement-element-helper

Conversation

@NullVoxPopuli-ai-agent
Copy link
Copy Markdown

Summary

Implements the element helper as specified in RFC #0389, continuing the work started in #21048.

  • (element "h1") — renders an <h1> wrapping the block content, with full support for attributes, modifiers, and ...attributes
  • (element "") — renders block content without a wrapping element
  • (element null) / (element undefined) — renders nothing (<!---->)
  • Invalid types (number, boolean, object) throw an assertion in development mode
  • Tag name changes trigger teardown/recreation of the element

The helper is available in both loose mode (as a built-in helper) and strict mode (importable from @ember/helper).

Implementation approach

Uses the Glimmer VM's WrappedBuilder with dynamicTag: true and wrapped: true capabilities. The element helper returns a lightweight component definition per tag name:

  • Non-empty string: ElementComponentDefinition with a custom ElementComponentManager that provides the tag name via getTagName(). The VM's default template ({{yield}}) is used via asWrappedLayout(), which wraps it with dynamic element open/close opcodes.
  • Empty string: Same component but getTagName() returns null, so the WrappedBuilder skips the element and just yields the block.
  • null/undefined: A VoidElementComponentDefinition with an empty template (no yield), so nothing is rendered.

Files changed

  • packages/@ember/-internals/glimmer/lib/helpers/element.ts — Core implementation
  • packages/@ember/-internals/glimmer/lib/resolver.ts — Registered in BUILTIN_HELPERS for loose mode
  • packages/@ember/-internals/glimmer/index.ts — Re-export
  • packages/@ember/helper/index.ts — Public export for strict mode
  • packages/@ember/-internals/glimmer/tests/integration/helpers/element-test.js — 15 tests

Test plan

  • Basic rendering with static tag name
  • Strict mode rendering via defineComponent with imported element
  • Empty string renders content without wrapper
  • null and undefined render nothing (<!---->)
  • Element modifiers ({{on "click" ...}}) work on the dynamic element
  • Same element definition can be rendered multiple times
  • Dynamic tag name changes trigger teardown/recreation
  • Works as @tag argument with ...attributes
  • Validation: requires exactly 1 positional argument
  • Validation: rejects named arguments
  • Validation: rejects non-string types (number, boolean, object)
  • All 8646 existing tests continue to pass
  • TypeScript type-checking passes
  • ESLint passes

🤖 Generated with Claude Code

NullVoxPopuli and others added 2 commits March 23, 2026 12:33
Implements the `element` helper as specified in RFC emberjs#389, allowing
dynamic tag names in Glimmer templates. The helper returns a component
definition that renders the specified HTML element around yielded content.

Behavior:
- `(element "h1")` renders an `<h1>` wrapping the block content
- `(element "")` renders block content without a wrapping element
- `(element null)` / `(element undefined)` renders nothing
- Invalid types (number, boolean, object) throw in development mode
- Tag name changes trigger teardown/recreation of the element

Available in both loose mode (built-in) and strict mode (importable
from `@ember/helper`).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@NullVoxPopuli NullVoxPopuli marked this pull request as draft March 23, 2026 19:37
@NullVoxPopuli NullVoxPopuli self-assigned this Mar 23, 2026
@NullVoxPopuli NullVoxPopuli marked this pull request as ready for review March 26, 2026 14:08
NullVoxPopuli
NullVoxPopuli previously approved these changes Mar 26, 2026
@NullVoxPopuli
Copy link
Copy Markdown
Contributor

Tested on the big app at work, and aside from ember not owning the [Invoke] symbol in Glint, everything worked great

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants