diff --git a/.eslintignore b/.eslintignore index 2fb5b53d36..d288dfcde5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,2 @@ -src/**/demo/* -src/**/experimental/* +node_modules/* +bower_components/* diff --git a/.eslintrc.json b/.eslintrc.json index cc13518ec5..4ab15499c8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,18 +1,24 @@ { "extends": "eslint:recommended", + "ecmaVersion": 6, "rules": { - "no-console": 0 + "no-console": "off", + "no-var": "error", + "strict": "error" }, "env": { - "browser": true + "browser": true, + "es6": true }, "plugins": [ "html" ], "globals": { - "CustomElements": false, - "HTMLImports": false, - "Polymer": false, - "WeakMap": false + "customElements": true, + "HTMLImports": true, + "Polymer": true, + "ShadyDOM": true, + "ShadyCSS": true, + "goog": true } } diff --git a/.travis.yml b/.travis.yml index 5a0b74ee84..6465d133dd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,10 +16,9 @@ before_script: - npm install -g bower gulp-cli@1 - bower install - gulp lint -- gulp switch script: - xvfb-run wct -- if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct -s default; fi +- if [ "${TRAVIS_PULL_REQUEST}" = "false" ]; then wct -s 'windows 10/microsoftedge@14' -s 'windows 8.1/internet explorer@11' -s 'os x 10.11/safari@9' -s 'macos 10.12/safari@10' -s 'Linux/chrome@41'; fi env: global: - secure: bfF/o1ewpOxDNqTzWfvlwgRgGfP8OXhSQLLdEwZ6izO9tckMJuSNghk3qBXCEQJwTcUEyXP6EqfzIrRAvDXPa0H3OoinbrooDyV2wIDaVRK++WR2iZIqzqo3hGOdzm4tdrGJZe5av5Rk661Hls8aPfLbjdzcGuYXi8B4wZq2xMI= diff --git a/LICENSE.txt b/LICENSE.txt index 95987bac86..87c683b8e8 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,4 +1,4 @@ -// Copyright (c) 2014 The Polymer Authors. All rights reserved. +// Copyright (c) 2017 The Polymer Authors. All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are diff --git a/PRIMER.md b/PRIMER.md deleted file mode 100644 index 93079f6a93..0000000000 --- a/PRIMER.md +++ /dev/null @@ -1,2290 +0,0 @@ -# Polymer Primer - - -Below is a description of current Polymer features, followed by individual feature guides. - -See [the full Polymer.Base API documentation](http://polymer.github.io/polymer/) for details on specific methods and properties. - - -**Basic Custom Element sugaring** - -| Feature | Usage -|---------|------- -| [Custom element constructor](#element-constructor) | Polymer.Class({ … }); -| [Custom element registration](#register-element) | Polymer({ is: ‘...’, … }}; -| [Bespoke constructor support](#bespoke-constructor) | factoryImpl: function() { … } -| [Basic lifecycle callbacks](#basic-callbacks) | created, attached, detached, attributeChanged -| [Native HTML element extension](#type-extension) | extends: ‘…’ -| [Property configuration](#property-config) | properties: { … } -| [Attribute deserialization to property](#attribute-deserialization) | properties: { \: \ } -| [Static attributes on host](#host-attributes) | hostAttributes: { \: \ } -| [Behavior mixins](#behaviors) | behaviors: [ … ] - - -**Template stamped into "local DOM" and tree lifecycle** - -| Feature | Usage -|---------|------- -| [Template stamping into local DOM](#template-stamping) | \\\ -| [Scoped styling](#scoped-styling) | \ - - - - - - -``` - -Loading external stylesheets (as opposed to defining them inline in HTML) for styling local DOM is currently supported via a special [``](#external-stylesheets) import tag (as opposed to a ``). - - -## DOM (re-)distribution - -To support composition of an element's light DOM with its local DOM, Polymer supports the `` element. The `` element provides an insertion point at which an element's light DOM is combined with its local DOM. The `` element supports a `select` attribute which filters nodes via a simple selector. - -Polymer supports multiple local DOM implementations. On browsers that support ShadowDOM, ShadowDOM may be used to create local DOM. On other supported browsers, Polymer provides local DOM via a custom implementation called ShadyDOM which is inspired by and compatible with ShadowDOM. - -Example: - -```html - -``` - -## DOM API - -Polymer provides custom API for manipulating DOM such that local DOM and light DOM trees are properly maintained. - -**
Note: All DOM manipulation must use this API, as opposed to DOM API directly on nodes.
** - -The following methods are provided: - - * `Polymer.dom(parent).appendChild(node)` - * `Polymer.dom(parent).insertBefore(node, beforeNode)` - * `Polymer.dom(parent).removeChild(node)` - * `Polymer.dom(parent).replaceChild(oldNode, newNode)` - * `Polymer.dom(parent).querySelector(selector)` - * `Polymer.dom(parent).querySelectorAll(selector)` - * `Polymer.dom(parent).childNodes` - * `Polymer.dom(parent).firstChild` - * `Polymer.dom(parent).lastChild` - * `Polymer.dom(node).previousSibling` - * `Polymer.dom(node).nextSibling` - * `Polymer.dom(parent).children` - * `Polymer.dom(parent).firstElementChild` - * `Polymer.dom(parent).lastElementChild` - * `Polymer.dom(node).previousElementSibling` - * `Polymer.dom(node).nextElementSibling` - * `Polymer.dom(node).textContent` - * `Polymer.dom(node).innerHTML` - * `Polymer.dom(node).parentNode` - * `Polymer.dom(contentElement).getDistributedNodes()` - * `Polymer.dom(node).getDestinationInsertionPoints()` - * `Polymer.dom.flush()` - The insert, append, and remove operations are transacted lazily in certain cases for performance. In order to interrogate the dom (e.g. `offsetHeight`, `getComputedStyle`, etc.) immediately after one of these operations, call `Polymer.dom.flush()` first. - -Calling `append`/`insertBefore` where `parent` is a custom Polymer element adds the node to the light DOM of the element. In order to insert/append into the shadow root of a custom element, use `this.root` as the parent. - -`Polymer.dom` properties and methods that return a list of nodes return an `Array`, not a `NodeList` like the standard DOM equivalent. - -Example: - -```js -var toLight = document.createElement('div'); -Polymer.dom(this).appendChild(toLight); - -var toLocal = document.createElement('div'); -var beforeNode = Polymer.dom(this.root).childNodes[0]; -Polymer.dom(this.root).insertBefore(toLocal, beforeNode); - -var allSpans = Polymer.dom(this).querySelectorAll('span'); -``` - -You can use `Polymer.dom` on any node, whether or not it has a local DOM tree: - -Example: - -```html - - -... - -var insert = document.createElement('div'); -Polymer.dom(this.$.container).insertBefore(insert, this.$.first); - -``` - -Sometimes it's necessary to access the elements which have been distributed to a given `` insertion point or to know to which `` a given node has been distributed. The `getDistributedNodes` and `getDestinationInsertionPoints` respectively provide this information. - -Example: - -```html - -
-
- -// x-foo's template - -``` - -```js -var div = Polymer.dom(xFoo).querySelector('div'); -var content = Polymer.dom(xFoo.root).querySelector('content'); -var distributed = Polymer.dom(content).getDistributedNodes()[0]; -var insertedTo = Polymer.dom(div).getDestinationInsertionPoints(); - -// the following should be true: -assert.equal(distributed, div); -assert.equal(insertedTo, content) -``` - - -## Configuring default property values - -Default values for properties may be specified in the `properties` object using the `value` field. The value may either be a primitive value, or a function that returns a value (which should be used for initializing Objects and Arrays to avoid shared objects on instances). - -Example: - -```js -Polymer({ - - is: 'x-custom', - - properties: { - - mode: { - type: String, - value: 'auto' - }, - - data: { - type: Object, - notify: true, - value: function() { return {}; } - } - - } - -}); -``` - -## Ready callback - -The `ready` method is part of an element's lifecycle and is automatically called 'bottom-up' after the element's template has been stamped and all elements inside the element's local DOM have been configured (with values bound from parents, deserialized attributes, or else default values) and had their `ready` method called. Implement `ready` when it's necessary to manipulate an element's local DOM when the element is constructed. - -Example: - -```js -ready: function() { - this.$.ajax.go(); -} -``` - -# Declarative data binding, event handlers, and property effects - - -## Local node marshaling - -Polymer automatically builds a map of instance nodes stamped into its local DOM, to provide convenient access to frequently used nodes without the need to query for (and memoize) them manually. Any node specified in the element's template with an `id` is stored on the `this.$` hash by `id`. - -Example: - -```html - - - - - -``` - - -## Event listener setup - -Event listeners can be added to the host element by providing an object-valued `listeners` property that maps events to event handler function names. - -Example: - -```html - - - - - -``` - - -## Annotated event listener setup - -For adding event listeners to local-DOM children, a more convenient `on-` annotation syntax is supported directly in the template. This often eliminates the need to give an element an `id` solely for the purpose of binding an event listener. - -Example: - -```html - - - - - -``` - - -## Gesture events - -Polymer will generate and fire a custom "gesture" event for certain user interactions automatically when a declarative listener is added for the event type. These events will fire consistently on both touch and mouse environments, and so it is advised to listen for these events rather than their mouse- or touch-specific event counterparts for interoperability with both touch and desktop/mouse environments. For example, `tap` should be used instead of `click` for the most reliable cross-platform results. - -For convenience, calling `preventDefault` on gesture events will prevent the native event that generated that gesture. For example, calling `preventDefault` on a `tap` event will also call `preventDefault` on the `click` event that generated the `tap`. - -Certain gestures will be able to control scrolling direction for touch input. For example, nodes with a listener for the `track` event will prevent scrolling by default. Elements can be override scroll direction with `this.setScrollDirection(direction, node)`, where `direction` is one of `'x'`, `'y'`, `'none'`, or `'all'`, and `node` defaults to `this`. - -The following are the gesture event types supported, with a short description and list of detail properties available on `event.detail` for each type: - -* **down** - finger/button went down - * `x` - clientX coordinate for event - * `y` - clientY coordinate for event - * `prevent(type)` - a function that may be called to prevent the given gesture events. Currently supported gestures to prevent are `tap` and `track`. - * `sourceEvent` - the original DOM event that caused the `down` action -* **up** - finger/button went up - * `x` - clientX coordinate for event - * `y` - clientY coordinate for event - * `prevent(type)` - a function that may be called to prevent the given gesture events. Currently supported gestures to prevent are `tap` and `track`. - * `sourceEvent` - the original DOM event that caused the `up` action -* **tap** - down & up occurred - * `x` - clientX coordinate for event - * `y` - clientY coordinate for event - * `sourceEvent` - the original DOM event that caused the `tap` action -* **track** - moving while finger/button is down - * `state` - a string indicating the tracking state: - * `start` - fired when tracking is first detected (finger/button down and moved past a pre-set distance threshold) - * `track` - fired while tracking - * `end` - fired when tracking ends - * `x` - clientX coordinate for event - * `y` - clientY coordinate for event - * `dx` - change in pixels horizontally since the first track event - * `dy` - change in pixels vertically since the first track event - * `ddx` - change in pixels horizontally since last track event - * `ddy` - change in pixels vertically since last track event - * `hover()` - a function that may be called to determine the element currently being hovered over - -Example: - -```html - - - - - - -``` -Example with `listeners`: - -```html - - - - - -``` - - -## Property change callbacks (observers) - -### Single property observation - -Custom element properties may be observed for changes by specifying `observer` property in the `properties` for the property that gives the name of a function to call. When the property changes, the change handler will be called with the new and old values as arguments. - -Example: - -```js -Polymer({ - - is: 'x-custom', - - properties: { - disabled: { - type: Boolean, - observer: 'disabledChanged' - }, - highlight: { - observer: 'highlightChanged' - } - }, - - disabledChanged: function(newValue, oldValue) { - this.toggleClass('disabled', newValue); - this.highlight = true; - }, - - highlightChanged: function() { - this.classList.add('highlight'); - setTimeout(function() { - this.classList.remove('highlight'); - }, 300); - } - -}); -``` - -Note that property change observation is achieved in Polymer by installing setters on the custom element prototype for properties with registered interest (as opposed to observation via Object.observe or dirty checking, for example). - - -### Multiple property observation - -Observing changes to multiple properties is supported via the `observers` array on the prototype, using a string containing a method signature that includes any dependent arguments. Once all properties are defined (`!== undefined`), the observer method will be called once for each change to a dependent property. The current values of the dependent properties will be passed as arguments to the observer method in the order defined in the `observers` method signature. - -*Note, multiple-property observers will only be called once all dependent properties are defined (`!== undefined`). If one or more of the properties are optional, they would need default `value`'s defined in `properties` to ensure the observer is called.* - -*Note that any observers defined in the `observers` array will not receive `old` values as arguments, only new values. Only single-property observers defined in the `properties` object received both `old` and `new` values.* - -Example: - -```js -Polymer({ - - is: 'x-custom', - - properties: { - preload: Boolean, - src: String, - size: String - }, - - observers: [ - 'updateImage(preload, src, size)' - ], - - updateImage: function(preload, src, size) { - // ... do work using dependent values - } - -}); -``` - - -### Path observation - -Observing changes to object sub-properties is also supported via the same `observers` array, by specifying a path (e.g. `user.manager.name`). - -Example: - -```js -Polymer({ - - is: 'x-custom', - - properties: { - user: Object - }, - - observers: [ - 'userManagerChanged(user.manager)' - ], - - userManagerChanged: function(user) { - console.log('new manager name is ' + user.name); - } - -}); -``` - -*Note that observing changes to paths (object sub-properties) is dependent on one of two requirements: either the value at the path in question changed via a two-way Polymer [property binding](#property-binding) to another element, or the value was changed using the [`set`](#set-path) API, which provides the required notification to elements with registered interest.* - - -### Deep path observation - -Additionally, wildcard matching of path changes is also supported via the `observers` array, which allows notification when any (deep) sub-property of an object changes. Note that the argument passed for a path with a wildcard is a change record object containing the `path` that changed, the new `value` of the path that changed, and the `base` value of the wildcard expression. - -Example: - -```js -Polymer({ - - is: 'x-custom', - - properties: { - user: Object - }, - - observers: [ - 'userManagerChanged(user.manager.*)' - ], - - userManagerChanged: function(changeRecord) { - if (changeRecord.path == 'user.manager') { - // user.manager object itself changed - console.log('new manager name is ' + changeRecord.base.name); - } else { - // sub-property of user.manager changed - console.log(changeRecord.path + ' changed to ' + changeRecord.value); - } - } - -}); -``` - - -### Array observation - -Finally, mutations to arrays (e.g. changes resulting from calls to `push`, `pop`, `shift`, `unshift`, and `splice`, generally referred to as "splices") may be observed via the `observers` array by giving a path to an array followed by `.splices` as an argument to the observer function. The value received by the observer for the `splices` path of an array will be a change record containing `indexSplices` and `keySplices` arrays listing the set of changes that occurred to the array, either in terms of array indices or "keys" used by Polymer for identifying array elements. Each `indexSplices` record contains a start `index`, array of `removed` items, and `addedCount` of items inserted. Each `keySplices` record contains an array of `added` and `removed` keys). - -```js -Polymer({ - - is: 'x-custom', - - properties: { - users: Array - }, - - observers: [ - 'usersAddedOrRemoved(users.splices)' - ], - - usersAddedOrRemoved: function(changeRecord) { - changeRecord.indexSplices.forEach(function(s) { - s.removed.forEach(function(user) { - console.log(user.name + ' was removed'); - }); - console.log(s.addedCount + ' users were added'); - }, this); - } - -}); -``` - -*Note that observing changes to arrays is dependent on the change to the array being made through one of the [array mutation API's](#array-mutation) provided on Polymer elements, which provides the required notification to elements with registered interest.* - -Note that an observer for a wildcard path of an array will be called for both splices as well as array element sub-property changes. Hence, the observer in the following example will be called for all additions, removals, and deep changes that occur in the array: - -```js -Polymer({ - - is: 'x-custom', - - properties: { - users: Array - }, - - observers: [ - 'usersChanged(users.*)' - ], - - usersChanged: function(changeRecord) { - if (changeRecord.path == 'users.splices') { - // a user was added or removed - } else { - // an individual user or its sub-properties changed - // check `changeRecord.path` to determine what changed - } - } - -}); -``` - - -## Annotated property binding - -### Basic property binding - -Properties of the custom element may be bound into text content or properties of local DOM elements using binding annotations in the template. - -```html - - - - - - - - - -``` - -To bind to properties, the binding annotation should be provided as the value to an attribute with the same name of the JS property to bind to: - -```html - - - - - -``` - -As in the example above, paths to object sub-properties may also be specified in templates. See [Binding to structured data](#path-binding) for details. - -In order to bind to camel-case properties of elements, dash-case should be used in the attribute name. Example: - -```html - - -``` - -Note that while HTML attributes are used to specify bindings, values are assigned directly to JS properties, not to the HTML attributes of the elements unless specific [attribute bindings](#attribute-binding) are used. - - -### Property change notification and Two-way binding - -Polymer supports cooperative two-way binding between elements, allowing elements that "produce" data or changes to data to propagate those changes upwards to hosts when desired. - -When a Polymer elements changes a property that was configured in `properties` with the `notify` flag set to true, it automatically fires a non-bubbling DOM event to indicate those changes to interested hosts. These events follow a naming convention of `-changed`, and contain a `value` property in the `event.detail` object indicating the new value. - -As such, one could attach an `on--changed` listener to an element to be notified of changes to such properties, set the `event.detail.value` to a property on itself, and take necessary actions based on the new value. However, given this is a common pattern, bindings using "curly-braces" (e.g. `{{property}}`) will automatically perform this upwards binding automatically without the user needing to perform those tasks. This can be disabled by using "square-brace" syntax (e.g. `[[property]]`), which results in only one-way (downward) data-binding. - -To summarize, two-way data-binding is achieved when both the host and the child agree to participate, satisfying these three conditions: - -1. The host must use curly-brace `{{property}}` syntax. Square-brace `[[property]]` syntax results in one-way downward binding, regardless of the notify state of the child's property. -2. The child property being bound to must be configured with the `notify` flag set to true (or otherwise send a `-changed` custom event). If the property being bound does not have the `notify` flag set, only one-way (downward) binding will occur. -3. The child property being bound to must not be configured with the `readOnly` flag set to true. If the child property is `notify: true` and `readOnly:true`, and the host binding uses curly-brace syntax, the binding will effectively be one-way (upward). - -Example 1: Two-way binding - -```html - -... - - - - -``` - -Example 2: One-way binding (downward) - -```html - - -... - - - - -``` - -Example 3: One-way binding (downward) - -```html - -... - - - - -``` - -Example 4: One-way binding (upward) - -```html - - -... - - - - -``` - -Example 5: Error / non-sensical state - -```html - - -... - - - - - -``` - -### Custom notify event and binding to native elements - -As mentioned above, Polymer uses an event naming convention to achieve two-way binding. The act of two-way binding to a property using `target-prop={{hostProp}}` syntax results in Polymer adding a `-changed` event listener to the element by default. All properties of a Polymer element with `notify: true` send events using this convention to notify of changes. - -In order to two-way bind to native elements or non-Polymer elements that do not follow this event naming convention when notifying changes, you may specify a custom event name in the curly braces, delimited with `::`. - -Example: - -```html - - - - - - - -