{"id":13806584,"url":"https://github.com/Ravenstine/ember-custom-elements","last_synced_at":"2025-05-13T22:30:23.796Z","repository":{"id":57223634,"uuid":"253342849","full_name":"Ravenstine/ember-custom-elements","owner":"Ravenstine","description":"The easiest way to render parts of your Ember app using custom elements.","archived":false,"fork":false,"pushed_at":"2023-09-26T13:11:34.000Z","size":1331,"stargazers_count":15,"open_issues_count":10,"forks_count":4,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-11-10T03:51:36.923Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Ravenstine.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-05T22:17:19.000Z","updated_at":"2023-09-11T09:54:55.000Z","dependencies_parsed_at":"2024-05-03T11:14:08.759Z","dependency_job_id":null,"html_url":"https://github.com/Ravenstine/ember-custom-elements","commit_stats":{"total_commits":64,"total_committers":4,"mean_commits":16.0,"dds":0.078125,"last_synced_commit":"7386bc69b004694f46b657d63fd8f9d683055829"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ravenstine%2Fember-custom-elements","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ravenstine%2Fember-custom-elements/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ravenstine%2Fember-custom-elements/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ravenstine%2Fember-custom-elements/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ravenstine","download_url":"https://codeload.github.com/Ravenstine/ember-custom-elements/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225258922,"owners_count":17445834,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-04T01:01:13.555Z","updated_at":"2024-11-18T22:32:19.484Z","avatar_url":"https://github.com/Ravenstine.png","language":"JavaScript","funding_links":[],"categories":["Libraries"],"sub_categories":["Integrations"],"readme":"[![Build Status](https://travis-ci.com/Ravenstine/ember-custom-elements.svg?branch=master)](https://travis-ci.com/Ravenstine/ember-custom-elements)\n[![npm version](https://badge.fury.io/js/ember-custom-elements.svg)](https://badge.fury.io/js/ember-custom-elements)\n\nEmber Custom Elements\n=====================\n\nThe most flexible way to render parts of your Ember application using custom elements!\n\n\n## Demos\n\n- [Tic Tac Toe game using Ember and React](https://ember-twiddle.com/8fa62cb81a790a3afb6713fd9f2480b5) (based on the [React.js tutorial](https://reactjs.org/tutorial/tutorial.html))\n- [Super Rentals w/ animated route transitions](https://ember-twiddle.com/aa7bd7a7d36641dd5daa5ad6b6eebb5a) (combines custom elements for routes with [Ionic Framework](https://ionicframework.com/)'s animated nav)\n- [Nifty Squares](https://ember-twiddle.com/f99d7cb679baf906c3d6b1435e52fdf9) (demonstrates dynamic block content)\n\n\n## Table of Contents\n\n* [Compatibility](#compatibility)\n* [Installation](#installation)\n* [Usage](#usage)\n  * [Components](#components)\n    * [Attributes and Arguments](#attributes-and-arguments)\n    * [Block Content](#block-content)\n  * [Routes](#routes)\n    * [Named Outlets](#named-outlets)\n    * [Outlet Element](#outlet-element)\n  * [Applications](#applications)\n  * [Native Custom Elements](#native-custom-elements)\n  * [Options](#options)\n  * [Accessing a Custom Element](#accessing-a-custom-element)\n  * [Forwarding Component Properties](#forwarding-component-properties)\n* [Notes](#notes)\n  * [Elements](#elements)\n  * [Runloop](#runloop)\n* [Contributing](#contributing)\n* [License](#license)\n  \n\n\n## Compatibility\n\n* Ember.js v3.8 or above\n* Ember CLI v2.13 or above\n* Node.js v10 or above\n\nThis add-on won't work at all with versions of `ember-source` prior to `3.6.0`.  I will not be actively trying to support versions of Ember that are not recent LTS versions, but I'm open to any pull requests that improve backward compatibility.\n\n\n## Installation\n\n```\nember install ember-custom-elements\n```\n\nIf you are targeting older browsers, you may want to use a [polyfill for custom elements](https://github.com/webcomponents/polyfills/tree/master/packages/custom-elements).  Other features of web components are also available as [polyfills](https://github.com/webcomponents/polyfills).\n\n\n## Usage\n\n\n\n### Components\n\nAll you have to do is use the `customElement` decorator in your component file:\n\n```javascript\nimport Component from '@glimmer/component';\nimport { customElement } from 'ember-custom-elements';\n\n@customElement('my-component')\nexport default MyComponent extends Component {\n\n}\n```\n\nNow you can use your component _anywhere_ inside the window that your app was instantiated within by using your custom element:\n\n```handlebars\n\u003cmy-component\u003e\u003c/my-component\u003e\n```\n\nIn the case that you can't use TC39's proposed decorator syntax, you can call customElement as a function and pass the target class as the first argument:\n\n```javascript\nexport default customElement(MyComponent, 'my-component');\n```\n\nHowever, it's recommended that you upgrade to a recent version of [ember-cli-babel](https://github.com/babel/ember-cli-babel) so you can use decorator syntax out of the box, or manually install [babel-plugin-proposal-decorators](https://babeljs.io/docs/en/babel-plugin-proposal-decorators).\n\nIn newer versions of Ember, you will get a linting error if you have an empty backing class for your component.  Since the `@customElement` decorator needs to be used in a JS file in order to implement a custom element for your component, you may have no choice but to have an empty backing class.\n\nThus, you may want to disable the `ember/no-empty-glimmer-component-classes` ESLint rule in your Ember project.  In the future, we will explore ways to define custom elements for tagless components, but until then you either need a component class defined.\n\n\n\n\n#### Attributes and Arguments\n\nAttributes instances of your custom element are translated to arguments to your component:\n\n```handlebars\n\u003cmy-component some-message=\"hello world\"\u003e\u003c/my-component\u003e\n```\n\nTo use the attribute in your component template, you would use it like any other argument:\n\n```handlebars\n{{!-- my-component.hbs --}}\n{{@some-message}}\n```\n\nChanges to attributes are observed, and so argument values are updated automatically.\n\n\n\n\n#### Block Content\n\nBlock content inside your custom element instances can be treated just like block content within a precompiled template.  If your component contains a `{{yield}}` statement, that's where the block content will end up.\n\n```handlebars\n{{!-- my-component.hbs --}}\n\u003cspan\u003efoo {{yield}} baz\u003c/span\u003e\n```\n\n```handlebars\n\u003cmy-component\u003ebar\u003c/my-component\u003e\n```\n\nWhen the component is rendered, we get this:\n\n```handlebars\n\u003cspan\u003efoo bar baz\u003c/span\u003e\n```\n\nBlock content can be dynamic.  However, the consuming element needs to be able to handle its children being changed by other forces outside of it; if a child that's dynamic gets removed by the custom element itself, that can lead to the renderer getting confused and spitting out errors during runtime.\n\nYou can see dynamic block content can work in [this demo](https://ember-twiddle.com/f99d7cb679baf906c3d6b1435e52fdf9).\n\n\n\n### Routes\n\nThe `@customElement` decorator can define a custom element that renders an active route, much like the `{{outlet}}` helper does.  In fact, this is achieved by creating an outlet view that renders the main outlet for the route.\n\nJust like with components, you can use it directly on your route class:\n\n```javascript\n/* app/routes/posts.js */\n\nimport Route from '@ember/routing/route';\nimport { customElement } from 'ember-custom-elements';\n\n@customElement('test-route')\nexport default class PostsRoute extends Route {\n  model() {\n    ...\n  }\n}\n```\n\nIn this case, the `\u003ctest-route\u003e` element will render your route when it has been entered in your application.\n\n\n\n\n#### Named Outlets\n\nIf your route renders to [named outlets](https://api.emberjs.com/ember/release/classes/Route/methods/renderTemplate?anchor=renderTemplate), you can define custom elements for each outlet with the `outletName` option:\n\n```javascript\n/* app/routes/posts.js */\n\nimport Route from '@ember/routing/route';\nimport { customElement } from 'ember-custom-elements';\n\n@customElement('test-route')\n@customElement('test-route-sidebar', { outletName: 'sidebar' })\nexport default class PostsRoute extends Route {\n  model() {\n    ...\n  }\n\n  renderTemplate() {\n    this.render();\n    this.render('posts/sidebar', {\n      outlet: 'sidebar'\n    });\n  }\n}\n```\n\nIn this example, the `\u003ctest-route-sidebar\u003e` element exhibits the same behavior as `{{outlet \"sidebar\"}}` would inside the parent route of the `posts` route.  Notice that the `outletName` option reflects the name of the outlet specified in the call to the `render()` method.\n\nNote that the use of `renderTemplate` is being deprecated in newer versions of Ember.\n\n\n\n\n#### Outlet Element\n\nThis add-on comes with a primitive custom element called `\u003cember-outlet\u003e` which can allow you to dynamically render outlets, but with a few differences from the `{{outlet}}` helper due to technical limitations from rendering outside of a route hierarchy.\n\n\n\n\n##### Usage\n\nThe outlet element will not be defined by default.  You must do this with the `@customElement` decorator function.  Here is an example of an instance-initializer you can add to your application that will set up the outlet element:\n\n```javascript\n// app/custom-elements.js\n\nimport { setOwner } from '@ember/application';\nimport { customElement, EmberOutletElement } from 'ember-custom-elements';\n\n@customElement('ember-outlet')\nexport default class OutletElement extends EmberOutletElement {\n\n}\n```\n\nThis will allow you to render an outlet like this:\n\n```handlebars\n\u003cember-outlet\u003e\u003c/ember-outlet\u003e\n```\n\nBy default, the `\u003cember-outlet\u003e` will render the main outlet for the `application` route.  This can be useful for rendering an already initialized Ember app within other contexts.\n\nTo render another route, you must specify it using the `route=` attribute:\n\n```handlebars\n\u003cember-outlet route=\"posts.index\"\u003e\u003c/ember-outlet\u003e\n```\n\nIf your route specifies named routes, you can also specify route names:\n\n```handlebars\n\u003cember-outlet route=\"posts.index\" name=\"sidebar\"\u003e\u003c/ember-outlet\u003e\n\u003cember-outlet route=\"posts.index\" name=\"content\"\u003e\u003c/ember-outlet\u003e\n```\n\nSince an `\u003cember-outlet\u003e` can be used outside of an Ember route, the route attribute is required except if you want to render the application route.  You cannot just provide the `name=` attribute and expect it to work.\n\nIn the unusual circumstance where you would be loading two or more Ember apps that use the `ember-outlet` element on the same page, you can extend your own custom element off the `ember-outlet` in order to resolve the naming conflict between the two apps.\n\n\n\n### Applications\n\nYou can use the same `@customElement` decorator on your Ember application.  This will allow an entire Ember app to be instantiated and rendered within a custom element as soon as that element is connected to a DOM.\n\nPresumably, you will only want your Ember app to be instantiated by your custom element, so you should define `autoboot = false;` in when defining your app class, like so:\n\n```javascript\n/* app/app.js */\n\nimport Application from '@ember/application';\nimport Resolver from 'ember-resolver';\nimport loadInitializers from 'ember-load-initializers';\nimport config from './config/environment';\nimport { customElement } from 'ember-custom-elements';\n\n@customElement('ember-app')\nexport default class App extends Application {\n  modulePrefix = config.modulePrefix;\n  podModulePrefix = config.podModulePrefix;\n  Resolver = Resolver;\n  autoboot = false;\n  // 👆 this part is important\n}\n\nloadInitializers(App, config.modulePrefix);\n```\n\nOnce your app has been created, every creation of a custom element for it will only create new application instances, meaning that your instance-initializers will run again but your initializers won't perform again.  Custom elements for your app are tied directly to your existing app.\n\n\n\n### Native Custom Elements\n\nThe `customElement` decorator can also be used on native custom elements (i.e. extensions of `HTMLElement`).\n\n```javascript\n/* app/custom-elements/my-element.js */\nimport { customElement } from 'ember-custom-elements';\n\n@customElement('my-element')\nexport default class MyElement extends HTMLElement {\n\n}\n```\n\nThere's a few minor things that this add-on does for you when it comes to using plain custom elements:\n\n- If you need to access the application from a descendent class of `HTMLElement`, you can use `Ember.getOwner` anywhere in your custom element code.\n- The `connectedCallback` will only be called after Glimmer has had a chance to render the block of content passed to your custom element.  This has to happen because Glimmer inserts elements individually, so even though your custom element may have been connected to the DOM, its prospective children probably haven't been inserted yet.\n- Service injection is possible like with any other Ember class using the `@inject` decorator from `@ember/service`. (In pre-Octane Ember, you of course need a [polyfill](https://github.com/ember-polyfills/ember-decorators-polyfill) for ES decorators)\n\nIt's important that your custom elements are located in a folder named `app/custom-elements` so that they can be properly registered with your application.  This add-on will NOT infer the tagName of the elements from their respective file names; you must always use the `@customElement` decorator.\n\n\n\n### Options\n\nAt present, there are a few options you can pass when creating custom elements:\n\n- **extends**: A string representing the name of a native element your custom element should extend from.  This is the same thing as the `extends` option passed to [window.customElements.define()](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements#High-level_view).\n- **useShadowRoot**: By default, application content rendered in your custom elements will be placed directly into the main DOM.  If you set this option to `true`, a shadow root will be used.\n- **observedAttributes**: A whitelist of which element attributes to observe.  This sets the native `observedAttributes` static property on [custom elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements).  It's suggested that you only use this option if you know what you are doing, as once the `observedAttributes` are set on a defined custom element, it cannot be changed after the fact(remember that custom elements can be only defined once).  The most common reason to define `observedAttributes` would be for performance reasons, as making calls to JavaScript every time any attribute changes is more expensive than if only some attribute changes should call JavaScript.  All that said, you probably don't need this, as ember-custom-elements observes all attribute changes by default.  Does nothing for custom elements that instantiate Ember apps.\n- **customElementClass**: In the extreme edge case that you need to redefine the behavior of the custom element class itself, you can `import { EmberCustomElement } from 'ember-custom-elements';`, extend it into a subclass, and pass that subclass to the `customElementClass` option.  This is definitely an expert tool and, even if you think you need this, you probably don't need it.  This is made available only for the desperate.  The `EmberCustomElement` class should be considered a private entity.\n- **camelizeArgs**: Element attributes must be kabob-case, but if `camelizeArgs` is set to true, these attributes will be exposed to your components in camelCase.\n- **outletName**: (routes only) The name of an outlet you wish to render for a route.  Defaults to 'main'.  The section on [named outlets][#named-outlets] goes into further detail.\n- **preserveOutletContent**: (routes only) When set to `true`, this prevents the DOM content inside the element from being cleared when transition away from the route is performed.  This is `false` by default, but you may want to set this to `true` in the case where you need to keep the DOM content around for animation purposes.\n\n\n\n\n#### Options Example\n\n```javascript\n@customElement('my-component', { extends: 'p', useShadowRoot: true })\nexport default MyComponent extends Component {\n\n}\n```\n\n\n\n\n#### Global Default Options\n\nIn the case where you want to apply an option to all uses of the `customElement` decorator, you can set the option as a global default in the `config/environment.js` of your Ember project.\n\nFor example, if you want `preserveOutletContent` to be applied to all route elements, you can add this option to `ENV.emberCustomElements.defaultOptions`:\n\n```javascript\nmodule.exports = function(environment) {\n  ...\n  emberCustomElements: {\n    defaultOptions: {\n      preserveOutletContent: true\n    }\n  },\n  ...\n}\n```\n\n\n\n### Accessing a Custom Element\n\nThe custom element node that's invoking a component can be accessed using the `getCustomElement` function.\n\nSimply pass the context of a component; if the component was invoked with a custom element, the node will be returned:\n\n```javascript\nimport Component from '@glimmer/component';\nimport { customElement, getCustomElement } from 'ember-custom-elements';\n\n@customElement('foo-bar')\nexport default class FooBar extends Component {\n  constructor() {\n    super(...arguments);\n    const element = getCustomElement(this);\n    // Do something with your element\n    this.foo = element.getAttribute('foo');\n  }\n}\n```\n\n### Forwarding Component Properties\n\nHTML attributes can only be strings which, while they work well enough for many purposes, can be limiting.\n\nIf you need to share state between your component and the outside world, you can create an interface to your custom element using the `forwarded` decorator.  Properties and methods upon which the decorator is used will become accessible on the custom element node.  If an outside force sets one of these properties on a custom element, the value will be set on the component.  Likewise, a forwarded method that's called on a custom element will be called with the context of the component.\n\n```javascript\nimport Component from '@glimmer/component';\nimport { customElement, forwarded } from 'ember-custom-elements';\n\n@customElement('foo-bar')\nexport default class FooBar extends Component {\n  @forwarded\n  bar = 'foobar';\n\n  @forwarded\n  fooBar() {\n    return this.bar.toUpperCase();\n  }\n}\n```\n\nWhen rendered, you can do this:\n\n```javascript\nconst element = document.querySelector('foo-bar');\nelement.bar; // 'foobar'\nelement.fooBar(); // 'FOOBAR\"\n```\n\nIf you are using `tracked` from `@glimmer/tracking`, you can use it in tandem with the `forwarded` decorator on properties.\n\n\n## Notes\n\n\n\n### Elements\n\nOnce a custom element is defined using `window.customElements.define`, it cannot be redefined.\n\nThis add-on works around that issue by reusing the same custom element class and changing the configuration associated with it.  It's necessary in order for application and integration tests to work without encountering errors.  This behavior will only be applied to custom elements defined using this add-on.  If you try to define an application component on a custom element defined outside of this add-on, an error will be thrown.\n\n\n\n### Runloop\n\nBecause element attributes must be observed, the argument updates to your components occur asynchronously.  Thus, if you are changing your custom element attributes dynamically, your tests will need to use `await settled()`.\n\n\n## Contributing\n\nSee the [Contributing](CONTRIBUTING.md) guide for details.\n\n\n## License\n\nThis project is licensed under the [MIT License](LICENSE.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRavenstine%2Fember-custom-elements","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FRavenstine%2Fember-custom-elements","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FRavenstine%2Fember-custom-elements/lists"}