Hybrid elements are Polymer elements designed to run under both Polymer 1.x and Polymer 2.x. Polymer 2 provides a backwards-compatible API for hybrid elements.
Implementing a hybrid element requires some extra work, including maintaining multiple sets of bower dependencies and testing on both Polymer 1 and Polymer 2. Build hybrid elements if you're creating a set of reusable elements and need to support customers using both Polymer 1.x and Polymer 2.x. You may also find hybrid elements useful if you're trying to port a large application.
Polymer CLI supports installing and testing with multiple versions of your bower dependencies, so you can test your hybrid elements against multiple versions of Polymer. For an overview, see Manage dependencies for hybrid elements.
Hybrid element overview
A hybrid element is defined using a 2.x-style DOM template and a 1.x-style Polymer()
function call.
The Polymer
function sets up the prototype chain for your custom element,
so you cannot set up your own prototype chain.
-
In 1.x, your prototype is chained to the Polymer
Base
prototype (which provides Polymer value-added features). -
In 2.x, Polymer uses your prototype to create a new class that extends
Polymer.Element
. The class also mixes in thePolymer.LegacyElementMixin
, which adds backward-compatbile APIs that are not included in `Polymer.Element.
Hybrid elements must use a compatible subset of the 1.x API. (Version-specific API calls can be conditionalized.)
You can use Polymer 1.x style behaviors to share code between elements, as long as they follow the same API restrictions as hybrid elements.
Working with DOM
Hybrid elements need to run under Polymer 2.0—which uses the newer shadow DOM v1 specification, and Polymer 1.x, which uses the earlier shadow DOM v0 specification.
-
When writing the DOM template for a hybrid element, use the shadow DOM v1 style
<slot>
element and::slotted()
selector. -
When manipulating DOM elements at runtime, use the
Polymer.dom
APIs for backward compatibility.
DOM template and styling
Hybrid elements must use the shadow DOM v1 style <slot>
element and ::slotted()
CSS selector
in place of the <content>
element and ::content
selector from shadow DOM v0.
Note that <slot>
is more restrictive than the v0 <content>
mechanism:
-
Insertion points that selected content using
<content select="selector">
must be changed to named slots:<slot name="slot_name">
. Note that in shadow DOM v1, distributed content can only be selected by slot name, not by tag name, attributes or CSS classes. -
Users of your element must use the matching new
slot="slot_name"
attribute to distribute content into a named slot. -
Any
::content
CSS selectors must be replaced with::slotted(selector)
, where selector is compound selector that identifies a top-level distributed child. That is,::slotted(.foo)
is equivalent to::content > .foo
.In shadow DOM v1, you cannot select a descendant of a top-level distributed child. For example,
::slotted(*) .child
does not work. No descendant selectors can follow the::slotted()
selector.
When running in Polymer 1.x, <slot>
elements are re-written at runtime into the
equivalent <content>
elements and ::content
style rules, to work with shadow DOM v0.
<dom-module id="x-forward-compat">
<template>
<style>
#container ::slotted(.foo) {
color: red;
}
#namedContainer ::slotted(*) {
border: 2px solid black;
}
</style>
<div id="namedContainer">
<slot name="named"></slot>
</div>
<div id="container">
<slot></slot>
</div>
</template>
</dom-module>
<dom-module id="x-forward-compat">
<template>
<style>
#container ::content > .foo {
color: red;
}
#namedContainer ::content > * {
border: 2px solid black;
}
</style>
<div id="namedContainer">
<content select="[slot=named]"></content>
</div>
<div id="container">
<content></content>
</div>
</template>
</dom-module>
Note that Polymer doesn't transform code that uses this element.
Anywhere you're using x-forward-compat
, you need to use the new slot syntax:
<x-forward-compat>
<h2 slot="named">I'm the named content</h2>
<span>This content goes to the default slot.</span>
</x-forward-compat>
Default slot behavior
For native shadow DOM v1, a default slot doesn't match children with an explicit slot name
(that is, a slot="slot_name"
attribute).
When running in 1.x mode, a default <slot>
element is translated to a default <content>
element, which accepts all distributed children that haven't matched a previous <content>
element.
This means that for hybrid elements, all named slots must come before any unnamed slots in the
DOM.
For example, given this set of slots:
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
Content with slot="footer"
is distributed to the default slot in 1.x, but distributed to the
last slot in 2.x.
DOM APIs
When working with the DOM imperatively, use the Polymer 1.x APIs, such as Polymer.dom
,
observeNodes
, and getContentChildNodes
.
Note that the initial distribution of light DOM children into slots may be delayed under the
polyfill, as described in the discussion of the ready
callback.
Also note that Polymer.dom.flush
does not flush observeNodes
callbacks in 2.x. This is most
likely to affect unit tests using Polymer.dom.flush
to ensure that shadow DOM children have been
distributed.
This change is because observeNodes
uses the native slotchange
event where possible and there
is no mechanism to force the event to fire. Instead, there is a flush
method on the observer
object.
attached: function() {
this._observer = Polymer.dom(this).observeNodes(this._onNodesChange);
},
_onNodesChange: function() {
this.count = Polymer.dom(this).children.length;
}
Polymer.dom(myElement).appendChild(document.createElement('div'));
Polymer.dom.flush();
// test some condition that should be true after the observeNodes callback fires
assert.equal(myElement.count, 1, 'child count should be 1');
Polymer.dom(myElement).appendChild(document.createElement('div'));
myElement._observer.flush ? myElement._observer.flush() : Polymer.dom.flush();
// test some condition that should be true after the observeNodes callback fires
assert.equal(myElement.count, 1, 'child count should be 1');
Version-specific code
In some cases, you may need to run different code depending on which version of Polymer is in use.
One easy way to test the version is to test for the existence of the Polymer.Element
constructor,
which is only used in Polymer 2.
if (Polymer.Element) {
// version 2 code
} else {
// version 1 code
}
Manage dependencies for hybrid elements
For testing Polymer elements, Polymer CLI supports installing multiple versions of bower
dependencies. These versions are called variants. The components' default dependencies are listed
in the standard dependencies
and devDependencies
sections. The default dependencies should
use version ranges that include all versions supported by the component (typically, 1.9.1 or higher
for Polymer itself).
Other sets are listed in a special variants
section. For example:
"dependencies": {
"polymer": "Polymer/polymer#1.9 - 2"
},
"devDependencies": {
"iron-component-page": "PolymerElements/iron-component-page#^2.0.0",
"iron-demo-helpers": "PolymerElements/iron-demo-helpers#^2.0.0",
"test-fixture": "PolymerElements/test-fixture#^3.0.0",
"web-component-tester": "^6.0.0",
"webcomponentsjs": "webcomponents/webcomponentsjs#^1.0.0"
},
"variants": {
"1.x": {
"dependencies": {
"polymer": "Polymer/polymer#^1.9"
},
"devDependencies": {
"iron-component-page": "PolymerElements/iron-component-page#^1.0.0",
"paper-styles": "PolymerElements/paper-styles#^1.0.0",
"iron-demo-helpers": "PolymerElements/iron-demo-helpers#^1.0.0",
"test-fixture": "PolymerElements/test-fixture#^1.0.0",
"web-component-tester": "^4.0.0",
"webcomponentsjs": "webcomponents/webcomponentsjs#^0.7.0"
},
"resolutions": {
"webcomponentsjs": "^0.7"
}
}
},
"resolutions": {
"webcomponentsjs": "^1.0.0"
}
In the example above, the default dependencies work with either Polymer 1.x or 2.x, while the 1.x variant works with 1.x.
Run the following command to install both sets of dependencies:
polymer install --variants
To run on both 1.x and 2.x, hybrid elements should only depend on other hybrid elements. For elements built by the Polymer team, such as the iron-, paper- or app- elements, this means you will need a version number of 2.0.0 or higher.
Other CLI commands like polymer serve
and polymer test
can run against the default dependencies,
as well as any variants. For example, polymer serve
serves both versions at the same time, from
different ports. Likewise, polymer test
runs your test suites using each of the installed
variants.
When a user installs a hybrid element, only the default dependencies are considered. The variants are used purely for local development and testing.
Polymer 1.x projects using hybrid components
If your Polymer 1.x project uses hybrid components, you will need to add a resolution for the webcomponents polyfills. Otherwise, Bower is unable to resolve the conflict between the dev dependency on webcomponents v1 and the app dependency on webcomponents 0.7.
"resolutions": {
"webcomponentsjs": "^0.7"
}