New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

basic-component-mixins

Package Overview
Dependencies
Maintainers
2
Versions
10
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

basic-component-mixins - npm Package Compare versions

Comparing version 0.7.0 to 0.7.1

35

docs/AttributeMarshalling.md
<a name="AttributeMarshalling"></a>
## AttributeMarshalling
Mixin which marshalls attributes to properties (and eventually
vice versa)
Mixin which marshalls attributes to properties (and eventually vice versa).
This only supports string properties for now.
If your component exposes a setter for a property, it's generally a good
idea to let devs using your component be able to set that property in HTML
via an element attribute. You can code that yourself by writing an
`attributeChangedCallback`, or you can use this mixin to get a degree of
automatic support.
This mixin implements an `attributeChangedCallback` that will attempt to
convert a change in an element attribute into a call to the corresponding
property setter. Attributes typically follow hyphenated names ("foo-bar"),
whereas properties typically use camelCase names ("fooBar"). This mixin
respects that convention, automatically mapping the hyphenated attribute
name to the corresponding camelCase property name.
Example: You define a component using this mixin:
class MyElement extends AttributeMarshalling(HTMLElement) {
get fooBar() { return this._fooBar; }
set fooBar(value) { this._fooBar = value; }
}
document.registerElement('my-element', MyElement);
If someone then instantiates your component in HTML:
<my-element foo-bar="Hello"></my-element>
Then, after the element has been upgraded, the `fooBar` setter will
automatically be invoked with the initial value "Hello".
For the time being, this mixin only supports string-valued properties.
If you'd like to convert string attributes to other types (numbers,
booleans), you need to implement `attributeChangedCallback` yourself.
**Kind**: global class

11

docs/ClickSelection.md
<a name="ClickSelection"></a>
## ClickSelection
Mixin which maps a click (actually, a mousedown) to selection
Mixin which maps a click (actually, a mousedown) to a selection.
If the user clicks an element, and the element is an item in the list, then
the component's selectedIndex will be set to the index for that item.
This simple mixin is useful in list box-like elements, where a click on a
list item implicitly selects it.
This mixin expects the component to provide a method `indexOfItem(item)`.
You can provide that method yourself, or use the ContentAsItems mixin.
This mixin also expects the component to define a `selectedIndex`
property. You can provide that yourself, or use the ItemsSelection mixin.
**Kind**: global class
<a name="Collective"></a>
## Collective
A group of elements that have been joined together for the purpose of
A group of elements that have been associated for the purpose of
accomplishing some collective behavior, e.g., keyboard handling.
This is not a mixin, but a class used by the TargetInCollective mixin.
There are certain components that want to cooperatively handle the keyboard.
For example, the basic-arrow-selection and basic-page-dots components are
optional components that can augment the appearance and behavior of an inner
basic-carousel, adding arrow buttons and dot buttons, respectively. When
these components are nested together, they form an implicit unit called a
*collective*:
<basic-arrow-selection>
<basic-page-dots>
<basic-carousel>
... images, etc. ...
</basic-carousel>
</basic-page-dots>
</basic-arrow-selection>
In this configuration, the three components will all have a `this.collective`
reference that refers to a shared instance of the `Collective` class.
The Keyboard mixin they use is sensitive to the presence of
the collective. Among other things, it will ensure that only the outermost
element above — the basic-arrow-selection — will be a tab stop that can
receive the keyboard focus. This lets the user perceive the component
arrangement above as a single unit. The Keyboard mixin will also give each
element in the collective a chance to process any keyboard events. So, even
though the basic-arrow-selection element will have the focus, the standard
keyboard navigation provided by basic-carousel will continue to work.
The SelectionAriaActive component also respects collectives when using the
`aria-activedescendant` and `role` attributes. Those will be applied to the
outermost element (basic-arrow-selection, above) so that ARIA can correctly
understand the arrangement of the elements.
You can put elements into collectives yourself, or you can use the
TargetInCollective mixin.
**Kind**: global class
* [Collective](#Collective)
* [new Collective([elements])](#new_Collective_new)
* [.elements](#Collective+elements) : <code>Array.&lt;HTMLElement&gt;</code>
* [.outermostElement](#Collective+outermostElement)
* [.assimilate(target)](#Collective+assimilate)
* [.invokeMethod(method, [args])](#Collective+invokeMethod)
<a name="new_Collective_new"></a>
### new Collective([elements])
Create a collective.
| Param | Type | Description |
| --- | --- | --- |
| [elements] | <code>Array.&lt;HTMLELement&gt;</code> | Initial elements to add. |
<a name="Collective+elements"></a>
### collective.elements : <code>Array.&lt;HTMLElement&gt;</code>
The elements in the collective.
**Kind**: instance property of <code>[Collective](#Collective)</code>
<a name="Collective+outermostElement"></a>
### collective.outermostElement
The outermost element in the collective.
By convention, this is the first element in the `elements` array.
**Kind**: instance property of <code>[Collective](#Collective)</code>
<a name="Collective+assimilate"></a>
### collective.assimilate(target)
Add the indicated target to the collective.
By convention, if two elements wants to participate in a collective, and
one element is an ancestor of the other in the DOM, the ancestor should
assimilate the descendant instead of the other way around.
After assimilation, any element in the collective that defines a
`collectiveChanged` method will have that method invoked. This allows
the collective's elements to respond to changes in the collective.
**Kind**: instance method of <code>[Collective](#Collective)</code>
| Param | Type | Description |
| --- | --- | --- |
| target | <code>HTMLElement</code> &#124; <code>[Collective](#Collective)</code> | The element or collective to add. |
<a name="Collective+invokeMethod"></a>
### collective.invokeMethod(method, [args])
Invoke a method on all elements in the collective.
**Kind**: instance method of <code>[Collective](#Collective)</code>
| Param | Type | Description |
| --- | --- | --- |
| method | <code>string</code> | The name of the method to invoke on all elements. |
| [args] | <code>Array.&lt;object&gt;</code> | The arguments to the method |
<a name="Composable"></a>
## Composable
Mixin to make a class more easily composable with other mixins
Mixin to make a class more easily composable with other mixins.
The main contribution is the introduction of a `compose` method that applies
a set of mixin functions and returns the resulting new class. This sugar
can make the application of many mixins at once easier to read.
This mixin contributes a `compose` method that applies a set of mixin
functions and returns the resulting new class. This sugar can make the
application of many mixins at once easier to read.
**Kind**: global class
<a name="undefinedcompose"></a>
## undefinedcompose()
<a name="Composable.compose"></a>
### Composable.compose(...mixins)
Apply a set of mixin functions or mixin objects to the present class and
return the new class.
A call like
Instead of writing:
let MyClass = Mixin1(Mixin2(Mixin3(Mixin4(Mixin5(BaseClass)))));
Can be converted to:
You can write:

@@ -34,2 +34,10 @@ let MyClass = Composable(BaseClass).compose(

**Kind**: global function
In addition to providing syntactic sugar, this mixin can be used to
define a class in ES5, which lacks ES6's `class` keyword.
**Kind**: static method of <code>[Composable](#Composable)</code>
| Param | Type | Description |
| --- | --- | --- |
| ...mixins | <code>mixins</code> | A set of mixin functions or objects to apply. |
<a name="composeTemplates"></a>
## composeTemplates
Given two templates, this "folds" one inside the other
## composeTemplates(baseTemplate, subTemplate)
Given two templates, this "folds" one inside the other. This is
is useful for defining a component that wants to fill in slots in the
template of its base class.
For now, the folding process just entails putting the first inside the
location of the first <content> node in the second template.
location of the first <slot> node in the second template.
Example: if the first (sub) template is
Example: if the first (base) template is
<template>
Hello, <slot></slot>.
</template>
<template>
<b>
<slot></slot>
</b>
</template>
and the second (base) template is
and the second (subclass) template is
<template>
<b>
<slot></slot>
</b>
</template>
<template>
Hello, <slot></slot>.
</template>
Then the returned folded template is
Then the result of calling `composeTemplates(first, second)` is
<template>
<b>
Hello, <slot></slot>.
</b>
</template>
<template>
<b>
Hello, <slot></slot>.
</b>
</template>
**Kind**: global class
Note that this function is not a mixin, but a helper for creating web
components.
**Kind**: global function
| Param | Type | Description |
| --- | --- | --- |
| baseTemplate | <code>HTMLTemplate</code> &#124; <code>string</code> | The base class template. |
| subTemplate | <code>HTMLTemplate</code> &#124; <code>string</code> | The subclass template. |
<a name="ContentAsItems"></a>
## ContentAsItems
Mixin which maps content semantics (children) to list item
semantics
Mixin which maps content semantics (elements) to list item semantics.
Items differ from children in several ways:
This mixin expects a component to provide a `content` property returning a
raw set of elements. You can provide that yourself, or use the
`DistributedChildrenAsContent` mixin.
Items differ from element contents in several ways:
* They are often referenced via index.
* They may have a selection state.
* It's common to do work to initialize the appearance or state of a new item.
* It's common to do work to initialize the appearance or state of a new
item.
* Auxiliary invisible child elements are filtered out and not counted as
items. Auxiliary elements include link, script, style, and template
elements.
elements. This filtering ensures that those auxiliary elements can be
used in markup inside of a list without being treated as list items.
**Kind**: global class
<a name="undefineditems"></a>
## undefineditems
The current set of items in the list.
**Kind**: global variable
**Properties**
* [ContentAsItems](#ContentAsItems)
* [.items](#ContentAsItems+items) : <code>Array.&lt;HTMLElement&gt;</code>
* [.applySelection(item, selected)](#ContentAsItems+applySelection)
* [.indexOfItem(item)](#ContentAsItems+indexOfItem) ⇒ <code>number</code>
* [.itemAdded(item)](#ContentAsItems+itemAdded)
* [.itemsChanged()](#ContentAsItems+itemsChanged)
| Name | Type |
| --- | --- |
| items | <code>object</code> |
<a name="ContentAsItems+items"></a>
### contentAsItems.items : <code>Array.&lt;HTMLElement&gt;</code>
The current set of items in the list. See the top-level documentation for
mixin for a description of how items differ from plain content.
<a name="indexOfItem"></a>
## indexOfItem(item) ⇒ <code>number</code>
Returns the positional index for the indicated item.
**Kind**: instance property of <code>[ContentAsItems](#ContentAsItems)</code>
<a name="ContentAsItems+applySelection"></a>
### contentAsItems.applySelection(item, selected)
Apply the selection state to a single item.
Invoke this method to signal that the selected state of the indicated item
has changed. By default, this applies a `selected` CSS class if the item
is selected, and removed it if not selected.
**Kind**: instance method of <code>[ContentAsItems](#ContentAsItems)</code>
| Param | Type | Description |
| --- | --- | --- |
| item | <code>HTMLElement</code> | The item whose selection state has changed. |
| selected | <code>boolean</code> | True if the item is selected, false if not. |
<a name="ContentAsItems+indexOfItem"></a>
### contentAsItems.indexOfItem(item) ⇒ <code>number</code>
Return the positional index for the indicated item.
Because this acts like a getter, this does not invoke a base implementation.
**Kind**: global function
**Kind**: instance method of <code>[ContentAsItems](#ContentAsItems)</code>
**Returns**: <code>number</code> - The index of the item, or -1 if not found.

@@ -38,4 +61,24 @@

| --- | --- | --- |
| item | <code>object</code> | The item whose index is requested. |
| item | <code>HTMLElement</code> | The item whose index is requested. |
<a name="ContentAsItems+itemAdded"></a>
### contentAsItems.itemAdded(item)
This method is invoked whenever a new item is added to the list.
The default implementation of this method does nothing. You can override
this to perform per-item initialization.
**Kind**: instance method of <code>[ContentAsItems](#ContentAsItems)</code>
| Param | Type | Description |
| --- | --- | --- |
| item | <code>HTMLElement</code> | The item that was added. |
<a name="ContentAsItems+itemsChanged"></a>
### contentAsItems.itemsChanged()
This method is invoked when the underlying contents change. It is also
invoked on component initialization – since the items have "changed" from
being nothing.
**Kind**: instance method of <code>[ContentAsItems](#ContentAsItems)</code>
<a name="event_items-changed"></a>

@@ -42,0 +85,0 @@ ## "items-changed"

<a name="ContentFirstChildTarget"></a>
## ContentFirstChildTarget
Mixin that defines the target of a component -- the element the
component is managing or somehow responsible for -- as its first child
Mixin that defines the target of a component — the element the component is
managing or somehow responsible for — as its first child.
Some components serve to decorate or modify other elements. A common
pattern is to have one component wrap another, and have the outer, parent
component implicitly modify the child. This mixin facilitates this by
implicitly taking an element's first child as its "target".
Example:
<outer-element>
<inner-element></inner-element>
</outer-element>
If `outer-element` uses this mixin, then its `target` property will be
set to point to the `inner-element`, because that is its first child.
This mixin expects a `content` property that returns the element's content.
You can implement that yourself, or use the DistributedChildrenAsContent
mixin.
This mixin can be combined with the TargetInCollective mixin to have a
component participate in collective keyboard handling. *
**Kind**: global class
<a name="ContentFirstChildTarget+target"></a>
### contentFirstChildTarget.target : <code>HTMLElement</code>
Gets/sets the current target of the component.
**Kind**: instance property of <code>[ContentFirstChildTarget](#ContentFirstChildTarget)</code>
<a name="DirectionSelection"></a>
## DirectionSelection
Mixin which maps direction semantics (goLeft, goRight, etc.) to
selection semantics (selectPrevious, selectNext, etc.)
Mixin which maps direction semantics (goLeft, goRight, etc.) to selection
semantics (selectPrevious, selectNext, etc.).
This mixin can be used in conjunction with the KeyboardDirection mixin
(which maps keyboard events to directions) and a mixin that handles
selection like ItemsSelection.
**Kind**: global class
<a name="DistributedChildren"></a>
## DistributedChildren
Mixin which defines helpers for accessing a component's
distributed children as a flattened array or string.
Mixin which defines helpers for accessing a component's distributed
children as a flattened array or string.
The standard DOM API provides several ways of accessing child content:
`children`, `childNodes`, and `textContent`. None of these functions are
Shadow DOM aware. This mixin defines variations of those functions that
*are* Shadow DOM aware.
Example: you create a component `<count-children>` that displays a number
equal to the number of children placed inside that component. If someone
instantiates your component like:
<count-children>
<div></div>
<div></div>
<div></div>
</count-children>
Then the component should show "3", because there are three children. To
calculate the number of children, the component can just calculate
`this.children.length`. However, suppose someone instantiates your
component inside one of their own components, and puts a `<slot>` element
inside your component:
<count-children>
<slot></slot>
</count-children>
If your component only looks at `this.children`, it will always see exactly
one child — the `<slot>` element. But the user looking at the page will
*see* any nodes distributed to that slot. To match what the user sees, your
component should expand any `<slot>` elements it contains.
That is the problem this mixin solves. After applying this mixin, your
component code has access to `this.distributedChildren`, whose `length`
will return the total number of all children distributed to your component
in the composed tree.
Note: The latest Custom Elements API design calls for a new function,
`getAssignedNodes` that takes an optional `deep` parameter, that will solve
this problem at the API level.
**Kind**: global class
* [DistributedChildren](#DistributedChildren)
* [.distributedChildren](#DistributedChildren+distributedChildren) : <code>Array.&lt;HTMLElement&gt;</code>
* [.distributedChildNodes](#DistributedChildren+distributedChildNodes) : <code>Array.&lt;Node&gt;</code>
* [.distributedTextContent](#DistributedChildren+distributedTextContent) : <code>string</code>
<a name="DistributedChildren+distributedChildren"></a>
### distributedChildren.distributedChildren : <code>Array.&lt;HTMLElement&gt;</code>
An in-order collection of children, expanding any slot elements. Like the
standard children property, this skips text nodes.
**Kind**: instance property of <code>[DistributedChildren](#DistributedChildren)</code>
<a name="DistributedChildren+distributedChildNodes"></a>
### distributedChildren.distributedChildNodes : <code>Array.&lt;Node&gt;</code>
An in-order collection of child nodes, expanding any slot elements. Like
the standard childNodes property, this includes text nodes.
**Kind**: instance property of <code>[DistributedChildren](#DistributedChildren)</code>
<a name="DistributedChildren+distributedTextContent"></a>
### distributedChildren.distributedTextContent : <code>string</code>
The concatenated text content of all child nodes, expanding any slot
elements.
**Kind**: instance property of <code>[DistributedChildren](#DistributedChildren)</code>
<a name="DistributedChildrenAsContent"></a>
## DistributedChildrenAsContent
Mixin which defines a component's content as its children,
including any nodes distributed to the component's slots.
Mixin which defines a component's content as its children, expanding any
nodes distributed to the component's slots.
This mixin is intended for use with the DistributedChildren mixin. See that
mixin for a discussion of how that works. This DistributedChildrenAsContent
mixin provides an easy way of defining the "content" of a component as the
component's distributed children. That in turn lets mixins like
ContentAsItems manipulate the children as list items.
**Kind**: global class
<a name="undefinedcontent"></a>
## undefinedcontent : <code>Array</code>
<a name="DistributedChildrenAsContent+content"></a>
### distributedChildrenAsContent.content : <code>Array.&lt;HTMLElement&gt;</code>
The content of this component, defined to be the flattened array of
children distributed to the component.
**Kind**: global variable
**Properties**
| Name |
| --- |
| content |
**Kind**: instance property of <code>[DistributedChildrenAsContent](#DistributedChildrenAsContent)</code>
<a name="Generic"></a>
## Generic
Mixin that allows a component to support a "generic" style: a
minimalist style that can easily be removed to reset its visual appearance to
a baseline state
Mixin which allows a component to support a "generic" style: a minimalist
style that can easily be removed to reset its visual appearance to a
baseline state.

@@ -11,20 +11,20 @@ By default, a component should provide a minimal visual presentation that

in other settings. Each CSS rule has to be overridden. Worse, new CSS rules
added to the default style won't be overridden by default, making it hard to
know whether a new version of a component will still look okay.
added to the default style won't be overridden by default, making it hard
to know whether a new version of a component will still look okay.
As a compromise, the simple Polymer behavior here defines a "generic"
attribute. This attribute is normally set by default, and styles can be
written that apply only when the generic attribute is set. This allows the
construction of CSS rules that will only apply to generic components like
As a compromise, the mixin defines a `generic` attribute. This attribute is
normally set by default, and styles can be written that apply only when the
generic attribute is set. This allows the construction of CSS rules that
will only apply to generic components like:
:host([generic=""]) {
...
... Generic appearance defined here ...
}
This makes it easy to remove all default styling -- set the generic attribute
to false, and all default styling will be removed.
This makes it easy to remove all default styling — set the `generic`
attribute to false, and all default styling will be removed.
**Kind**: global class
<a name="undefinedgeneric"></a>
## undefinedgeneric : <code>Boolean</code>
<a name="Generic+generic"></a>
### generic.generic : <code>Boolean</code>
True if the component would like to receive generic styling.

@@ -36,9 +36,3 @@

**Kind**: global variable
**Kind**: instance property of <code>[Generic](#Generic)</code>
**Default**: <code>true</code>
**Properties**
| Name |
| --- |
| generic |
<a name="ItemsSelection"></a>
## ItemsSelection
Mixin which manages selection semantics for items in a list
Mixin which manages single-selection semantics for items in a list.
This mixin expects a component to provide an `items` array of all elements
in the list. A standard way to do that with is the ContentAsItems mixin,
which takes a component's content (typically its distributed children) as
the set of list items; see that mixin for details.
This mixin tracks a single selected item in the list, and provides means to
get and set that state by item position (`selectedIndex`) or item identity
(`selectedItem`). The selection can be moved in the list via the methods
`selectFirst`, `selectLast`, `selectNext`, and `selectPrevious`.
This mixin does not produce any user-visible effects to represent
selection. Other mixins, such as SelectionAriaActive, SelectionHighlight
and SelectionInView, modify the selected item in common ways to let the
user know a given item is selected or not selected.
**Kind**: global class
<a name="undefinedselectedIndex"></a>
## undefinedselectedIndex : <code>Number</code>
* [ItemsSelection](#ItemsSelection)
* [.canSelectNext](#ItemsSelection+canSelectNext) : <code>boolean</code>
* [.canSelectPrevious](#ItemsSelection+canSelectPrevious) : <code>boolean</code>
* [.selectedIndex](#ItemsSelection+selectedIndex) : <code>number</code>
* [.selectedItem](#ItemsSelection+selectedItem) : <code>object</code>
* [.selectionRequired](#ItemsSelection+selectionRequired) : <code>boolean</code>
* [.applySelection(item, selected)](#ItemsSelection+applySelection)
* [.itemAdded(item)](#ItemsSelection+itemAdded)
* [.selectFirst()](#ItemsSelection+selectFirst)
* [.selectLast()](#ItemsSelection+selectLast)
* [.selectNext()](#ItemsSelection+selectNext)
* [.selectPrevious()](#ItemsSelection+selectPrevious)
<a name="ItemsSelection+canSelectNext"></a>
### itemsSelection.canSelectNext : <code>boolean</code>
True if the selection can be moved to the next item, false if not (the
selected item is the last item in the list).
**Kind**: instance property of <code>[ItemsSelection](#ItemsSelection)</code>
<a name="ItemsSelection+canSelectPrevious"></a>
### itemsSelection.canSelectPrevious : <code>boolean</code>
True if the selection can be moved to the previous item, false if not
(the selected item is the first one in the list).
**Kind**: instance property of <code>[ItemsSelection](#ItemsSelection)</code>
<a name="ItemsSelection+selectedIndex"></a>
### itemsSelection.selectedIndex : <code>number</code>
The index of the item which is currently selected, or -1 if there is no
selection.
**Kind**: global variable
**Properties**
Setting the index to -1 deselects any current-selected item.
| Name |
| --- |
| selectedIndex |
<a name="undefinedselectedItem"></a>
## undefinedselectedItem : <code>Object</code>
**Kind**: instance property of <code>[ItemsSelection](#ItemsSelection)</code>
<a name="ItemsSelection+selectedItem"></a>
### itemsSelection.selectedItem : <code>object</code>
The currently selected item, or null if there is no selection.
**Kind**: global variable
**Properties**
Setting this property to null deselects any currently-selected item.
| Name |
| --- |
| selectedItem |
<a name="undefinedselectionRequired"></a>
## undefinedselectionRequired : <code>Boolean</code>
**Kind**: instance property of <code>[ItemsSelection](#ItemsSelection)</code>
<a name="ItemsSelection+selectionRequired"></a>
### itemsSelection.selectionRequired : <code>boolean</code>
True if the list should always have a selection (if it has items).
**Kind**: global variable
**Properties**
**Kind**: instance property of <code>[ItemsSelection](#ItemsSelection)</code>
<a name="ItemsSelection+applySelection"></a>
### itemsSelection.applySelection(item, selected)
Apply the indicate selection state to the item.
| Name |
| --- |
| selectionRequired |
The default implementation of this method does nothing. User-visible
effects will typically be handled by other mixins.
<a name="selectFirst"></a>
## selectFirst()
**Kind**: instance method of <code>[ItemsSelection](#ItemsSelection)</code>
| Param | Type | Description |
| --- | --- | --- |
| item | <code>HTMLElement</code> | the item being selected/deselected |
| selected | <code>boolean</code> | true if the item is selected, false if not |
<a name="ItemsSelection+itemAdded"></a>
### itemsSelection.itemAdded(item)
Handle a new item being added to the list.
The default implementation of this method simply sets the item's
selection state to false.
**Kind**: instance method of <code>[ItemsSelection](#ItemsSelection)</code>
| Param | Type | Description |
| --- | --- | --- |
| item | <code>HTMLElement</code> | the item being added |
<a name="ItemsSelection+selectFirst"></a>
### itemsSelection.selectFirst()
Select the first item in the list.
**Kind**: global function
<a name="selectLast"></a>
## selectLast()
**Kind**: instance method of <code>[ItemsSelection](#ItemsSelection)</code>
<a name="ItemsSelection+selectLast"></a>
### itemsSelection.selectLast()
Select the last item in the list.
**Kind**: global function
<a name="selectNext"></a>
## selectNext()
**Kind**: instance method of <code>[ItemsSelection](#ItemsSelection)</code>
<a name="ItemsSelection+selectNext"></a>
### itemsSelection.selectNext()
Select the next item in the list.
**Kind**: global function
<a name="selectPrevious"></a>
## selectPrevious()
**Kind**: instance method of <code>[ItemsSelection](#ItemsSelection)</code>
<a name="ItemsSelection+selectPrevious"></a>
### itemsSelection.selectPrevious()
Select the previous item in the list.
**Kind**: global function
**Kind**: instance method of <code>[ItemsSelection](#ItemsSelection)</code>
<a name="event_selected-item-changed"></a>

@@ -66,6 +120,6 @@ ## "selected-item-changed"

| Param | Description |
| --- | --- |
| detail.selectedItem | The new selected item. |
| detail.previousItem | The previously selected item. |
| Param | Type | Description |
| --- | --- | --- |
| detail.selectedItem | <code>HTMLElement</code> | The new selected item. |
| detail.previousItem | <code>HTMLElement</code> | The previously selected item. |

@@ -78,5 +132,5 @@ <a name="event_selected-item-changed"></a>

| Param | Description |
| --- | --- |
| detail.selectedIndex | The new selected index. |
| Param | Type | Description |
| --- | --- | --- |
| detail.selectedIndex | <code>number</code> | The new selected index. |
<a name="Keyboard"></a>
## Keyboard
Mixin which manages the keydown handling for a component
Mixin which manages the keydown handling for a component.
TODO: Document collective behavior.
TODO: Provide baseline behavior outside of a collective.
This mixin handles several keyboard-related features.
First, it wires up a single keydown event handler that can be shared by
multiple mixins on a component. The event handler will invoke a `keydown`
method with the event object, and any mixin along the prototype chain that
wants to handle that method can do so.
If a mixin wants to indicate that keyboard event has been handled, and that
other mixins should *not* handle it, the mixin's `keydown` handler should
return a value of true. The convention that seems to work well is that a
mixin should see if it wants to handle the event and, if not, then ask the
superclass to see if it wants to handle the event. This has the effect of
giving the mixin that was applied last the first chance at handling a
keyboard event.
Example:
keydown(event) {
let handled;
switch (event.keyCode) {
// Handle the keys you want, setting handled = true if appropriate.
}
// Prefer mixin result if it's defined, otherwise use base result.
return handled || (super.keydown && super.keydown(event));
}
A second feature provided by this mixin is that it implicitly makes the
component a tab stop if it isn't already, by setting `tabIndex` to 0. This
has the effect of adding the component to the tab order in document order.
Finally, this mixin is designed to work with Collective class via a mixin
like TargetInCollective. This allows a set of related component instances
to cooperatively handle the keyboard. See the Collective class for details.
NOTE: For the time being, this mixin should be used with
TargetInCollective. The intention is to allow this mixin to be used without
requiring collective keyboard support, so that this mixin can be used on
its own.
**Kind**: global class
<a name="Keyboard+keydown"></a>
### keyboard.keydown(event) ⇒ <code>boolean</code>
Handle the indicated keyboard event.
The default implementation of this method does nothing. This will
typically be handled by other mixins.
**Kind**: instance method of <code>[Keyboard](#Keyboard)</code>
**Returns**: <code>boolean</code> - true if the event was handled
| Param | Type | Description |
| --- | --- | --- |
| event | <code>KeyboardEvent</code> | the keyboard event |
<a name="KeyboardDirection"></a>
## KeyboardDirection
Mixin which maps direction keys (Left, Right, etc.) to direction
semantics (goLeft, goRight, etc.)
Mixin which maps direction keys (Left, Right, etc.) to direction semantics
(go left, go right, etc.).
This mixin expects the component to invoke a `keydown` method when a key is
pressed. You can use the Keyboard mixin for that purpose, or wire up your
own keyboard handling and call `keydown` yourself.
This mixin calls methods such as `goLeft` and `goRight`. You can define
what that means by implementing those methods yourself. If you want to use
direction keys to navigate a selection, use this mixin with the
DirectionSelection mixin.
**Kind**: global class
* [KeyboardDirection](#KeyboardDirection)
* [.goDown()](#KeyboardDirection+goDown)
* [.goEnd()](#KeyboardDirection+goEnd)
* [.goLeft()](#KeyboardDirection+goLeft)
* [.goRight()](#KeyboardDirection+goRight)
* [.goStart()](#KeyboardDirection+goStart)
* [.goUp()](#KeyboardDirection+goUp)
<a name="KeyboardDirection+goDown"></a>
### keyboardDirection.goDown()
Invoked when the user wants to go/navigate down.
The default implementation of this method does nothing.
**Kind**: instance method of <code>[KeyboardDirection](#KeyboardDirection)</code>
<a name="KeyboardDirection+goEnd"></a>
### keyboardDirection.goEnd()
Invoked when the user wants to go/navigate to the end (e.g., of a list).
The default implementation of this method does nothing.
**Kind**: instance method of <code>[KeyboardDirection](#KeyboardDirection)</code>
<a name="KeyboardDirection+goLeft"></a>
### keyboardDirection.goLeft()
Invoked when the user wants to go/navigate left.
The default implementation of this method does nothing.
**Kind**: instance method of <code>[KeyboardDirection](#KeyboardDirection)</code>
<a name="KeyboardDirection+goRight"></a>
### keyboardDirection.goRight()
Invoked when the user wants to go/navigate right.
The default implementation of this method does nothing.
**Kind**: instance method of <code>[KeyboardDirection](#KeyboardDirection)</code>
<a name="KeyboardDirection+goStart"></a>
### keyboardDirection.goStart()
Invoked when the user wants to go/navigate to the start (e.g., of a
list). The default implementation of this method does nothing.
**Kind**: instance method of <code>[KeyboardDirection](#KeyboardDirection)</code>
<a name="KeyboardDirection+goUp"></a>
### keyboardDirection.goUp()
Invoked when the user wants to go/navigate up.
The default implementation of this method does nothing.
**Kind**: instance method of <code>[KeyboardDirection](#KeyboardDirection)</code>
<a name="KeyboardPagedSelection"></a>
## KeyboardPagedSelection
Mixin which maps page keys (Page Up, Page Down) into operations
that move the selection by one page
Mixin which maps page keys (Page Up, Page Down) into operations that move
the selection by one page.

@@ -18,27 +18,31 @@ The keyboard interaction model generally follows that of Microsoft Windows'

To ensure the selected item is in view following use of Page Up/Down, use the
related SelectionInView mixin.
To ensure the selected item is in view following use of Page Up/Down, use
the related SelectionInView mixin.
This mixin expects the component to invoke a `keydown` method when a key is
pressed. You can use the Keyboard mixin for that purpose, or wire up your
own keyboard handling and call `keydown` yourself.
**Kind**: global class
<a name="undefinedscrollTarget"></a>
## undefinedscrollTarget
* [KeyboardPagedSelection](#KeyboardPagedSelection)
* [.scrollTarget](#KeyboardPagedSelection+scrollTarget) : <code>HTMLElement</code>
* [.pageDown()](#KeyboardPagedSelection+pageDown)
* [.pageUp()](#KeyboardPagedSelection+pageUp)
<a name="KeyboardPagedSelection+scrollTarget"></a>
### keyboardPagedSelection.scrollTarget : <code>HTMLElement</code>
The element that should be scrolled with the Page Up/Down keys.
Default is the current element.
**Kind**: global variable
**Properties**
| Name |
| --- |
| scrollTarget |
<a name="pageDown"></a>
## pageDown()
**Kind**: instance property of <code>[KeyboardPagedSelection](#KeyboardPagedSelection)</code>
<a name="KeyboardPagedSelection+pageDown"></a>
### keyboardPagedSelection.pageDown()
Scroll down one page.
**Kind**: global function
<a name="pageUp"></a>
## pageUp()
**Kind**: instance method of <code>[KeyboardPagedSelection](#KeyboardPagedSelection)</code>
<a name="KeyboardPagedSelection+pageUp"></a>
### keyboardPagedSelection.pageUp()
Scroll up one page.
**Kind**: global function
**Kind**: instance method of <code>[KeyboardPagedSelection](#KeyboardPagedSelection)</code>
<a name="KeyboardPrefixSelection"></a>
## KeyboardPrefixSelection
Mixin that handles list box-style prefix typing, in which the user
can type a string to select the first item that begins with that string
Mixin that handles list box-style prefix typing, in which the user can type
a string to select the first item that begins with that string.
Example: suppose a component using this mixin has the following items:
<sample-list-component>
<div>Apple</div>
<div>Apricot</div>
<div>Banana</div>
<div>Blackberry</div>
<div>Blueberry</div>
<div>Cantaloupe</div>
<div>Cherry</div>
<div>Lemon</div>
<div>Lime</div>
</sample-list-component>
If this component receives the focus, and the user presses the "b" or "B"
key, the "Banana" item will be selected, because it's the first item that
matches the prefix "b". (Matching is case-insensitive.) If the user now
presses the "l" or "L" key quickly, the prefix to match becomes "bl", so
"Blackberry" will be selected.
The prefix typing feature has a one second timeout — the prefix to match
will be reset after a second has passed since the user last typed a key.
If, in the above example, the user waits a second between typing "b" and
"l", the prefix will become "l", so "Lemon" would be selected.
This mixin expects the component to invoke a `keydown` method when a key is
pressed. You can use the Keyboard mixin for that purpose, or wire up your
own keyboard handling and call `keydown` yourself.
This mixin also expects the component to provide an `items` property. The
`textContent` of those items will be used for purposes of prefix matching.
**Kind**: global class
<a name="selectItemWithTextPrefix"></a>
## selectItemWithTextPrefix(prefix)
<a name="KeyboardPrefixSelection+selectItemWithTextPrefix"></a>
### keyboardPrefixSelection.selectItemWithTextPrefix(prefix)
Select the first item whose text content begins with the given prefix.
**Kind**: global function
**Kind**: instance method of <code>[KeyboardPrefixSelection](#KeyboardPrefixSelection)</code>
| Param | Description |
| --- | --- |
| prefix | [String] The string to search for |
| prefix | [String] The prefix string to search for |

@@ -1,7 +0,15 @@

<a name="microtask
<a name="microtask"></a>
## microtask(callback)
Add a callback to the microtask queue.
Adds a function to the microtask queue."></a>
## microtask
This uses a MutationObserver so that it works on IE 11.
Adds a function to the microtask queue.()
NOTE: IE 11 may actually use timeout timing with MutationObservers. This
needs more investigation.
**Kind**: global function
| Param | Type |
| --- | --- |
| callback | <code>function</code> |
<a name="ObserveContentChanges"></a>
## ObserveContentChanges
Wires up mutation observers to report any changes in a component's
content (direct children, or nodes distributed to slots).
Mixin which wires up mutation observers to report any changes in a
component's content (direct children, or nodes distributed to slots).

@@ -15,5 +15,27 @@ For the time being, this can only support a single level of distributed

For comparison, see Polymer's observeNodes API, which does solve the problem
of tracking changes in reprojected content.
For comparison, see Polymer's observeNodes API, which does solve the
problem of tracking changes in reprojected content.
Note: The web platform team creating the specifications for web components
plan to request that a new type of MutationObserver option be defined that
lets a component monitor changes in distributed children. This mixin will
be updated to take advantage of that MutationObserver option when that
becomes available.
**Kind**: global class
<a name="ObserveContentChanges+contentChanged"></a>
### observeContentChanges.contentChanged()
Invoked when the contents of the component (including distributed
children) have changed.
This method is also invoked when a component is first instantiated; the
contents have essentially "changed" from being nothing. This allows the
component to perform initial processing of its children.
**Kind**: instance method of <code>[ObserveContentChanges](#ObserveContentChanges)</code>
<a name="event_content-changed"></a>
## "content-changed"
This event is raised when the component's contents (including distributed
children) have changed.
**Kind**: event emitted
<a name="SelectionAriaActive"></a>
## SelectionAriaActive
Mixin which treats the selected item in a list as the active item
in ARIA accessibility terms
Mixin which treats the selected item in a list as the active item in ARIA
accessibility terms.
Handling ARIA selection state properly is actually quite complex. Not only
does the selected item need to be marked as selected; the other items should
be marked as *not* selected. Additionally, the outermost element with the
keyboard focus needs to have attributes set on it so that the selection is
knowable at the list level. That in turn requires that all items in the list
have ID attributes assigned to them. (To that end, this mixin will assign
generated IDs to any item that doesn't already have an ID.)
Handling ARIA selection state properly is actually quite complex:
* The items in the list need to be indicated as possible items via an ARIA
`role` attribute value such as "option".
* The selected item need to be marked as selected by setting the item's
`aria-selected` attribute to true *and* the other items need be marked as
*not* selected by setting `aria-selected` to false.
* The outermost element with the keyboard focus needs to have attributes
set on it so that the selection is knowable at the list level via the
`aria-activedescendant` attribute.
* Use of `aria-activedescendant` in turn requires that all items in the
list have ID attributes assigned to them.
This mixin tries to address all of the above requirements. To that end,
this mixin will assign generated IDs to any item that doesn't already have
an ID.
ARIA relies on elements to provide `role` attributes. This mixin will apply
a default role of "listbox" on the outer list if it doesn't already have an
explicit role. Similarly, this mixin will apply a default role of "option"
to any list item that does not already have a role specified.
This mixin expects a set of members that manage the state of the selection:
`applySelection`, `itemAdded`, and `selectedIndex`. You can supply these
yourself, or do so via the ItemsSelection mixin.
NOTE: For the time being, this mixin should be used with the
TargetInCollective mixin. The intention is to eventually allow this mixin
to be used without requiring collective keyboard support, so that this
mixin can be used on its own.
**Kind**: global class
<a name="SelectionHighlight"></a>
## SelectionHighlight
Mixin which applies standard highlight colors to a selected item
Mixin which applies standard highlight colors to a selected item.
This mixin highlights textual items (e.g., in a list) in a standard way by
using the CSS `highlight` and `highlighttext` color values. These values
respect operating system defaults and user preferences, and hence are good
default values for highlight colors.
This mixin expects an `applySelection` method to be called on an item when
its selected state changes. You can use the ItemsSelection mixin for that
purpose.
**Kind**: global class
<a name="SelectionInView"></a>
## SelectionInView
Mixin which scrolls a container to keep the selected item visible
Mixin which scrolls a container to ensure that a newly-selected item is
visible to the user.
When the selected item in a list-like component changes, it's easier for
the to confirm that the selection has changed to an appropriate item if the
user can actually see that item.
This mixin expects a `selectedItem` property to be set when the selection
changes. You can supply that yourself, or use the ItemsSelection mixin.
**Kind**: global class
<a name="undefinedscrollTarget"></a>
## undefinedscrollTarget
The element that should be scrolled with the Page Up/Down keys.
Default is the current element.
**Kind**: global variable
**Properties**
* [SelectionInView](#SelectionInView)
* [.scrollTarget](#SelectionInView+scrollTarget) : <code>HTMLElement</code>
* [.scrollItemIntoView(item)](#SelectionInView+scrollItemIntoView)
| Name |
| --- |
| scrollTarget |
<a name="SelectionInView+scrollTarget"></a>
### selectionInView.scrollTarget : <code>HTMLElement</code>
The element that should be scrolled to bring an item into view.
<a name="scrollItemIntoView"></a>
## scrollItemIntoView()
The default value of this property is the element itself.
**Kind**: instance property of <code>[SelectionInView](#SelectionInView)</code>
<a name="SelectionInView+scrollItemIntoView"></a>
### selectionInView.scrollItemIntoView(item)
Scroll the given element completely into view, minimizing the degree of
scrolling performed.
Blink has a scrollIntoViewIfNeeded() function that almost the same thing,
but unfortunately it's non-standard, and in any event often ends up
scrolling more than is absolutely necessary.
Blink has a `scrollIntoViewIfNeeded()` function that does something
similar, but unfortunately it's non-standard, and in any event often ends
up scrolling more than is absolutely necessary.
**Kind**: global function
**Kind**: instance method of <code>[SelectionInView](#SelectionInView)</code>
| Param | Type | Description |
| --- | --- | --- |
| item | <code>HTMLElement</code> | the item to scroll into view. |
<a name="ShadowElementReferences"></a>
## ShadowElementReferences
Mixin to create references to elements in a component's Shadow
DOM subtree
Mixin to create references to elements in a component's Shadow DOM subtree.
This adds a member on the component called `$` that can be used to reference
shadow elements with IDs. E.g., if component's shadow contains an element
`<button id="foo">`, then this mixin will create a member `this.$.foo` that
points to that button. Such references simplify a component's access to its
own elements.
This adds a member on the component called `this.$` that can be used to
reference shadow elements with IDs. E.g., if component's shadow contains an
element `<button id="foo">`, then this mixin will create a member
`this.$.foo` that points to that button.
This trades off a one-time cost of querying all elements in the shadow tree
against having to query for an element each time the component wants to
inspect or manipulate it.
Such references simplify a component's access to its own elements. In
exchange, this mixin trades off a one-time cost of querying all elements in
the shadow tree instead of paying an ongoing cost to query for an element
each time the component wants to inspect or manipulate it.
This mixin is inspired by Polymer's automatic node finding feature.
See https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#node-finding.
This mixin expects the component to define a Shadow DOM subtree. You can
create that tree yourself, or make use of the ShadowTemplate mixin.
This mixin is inspired by Polymer's [automatic
node finding](https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#node-finding)
feature.
**Kind**: global class
<a name="ShadowTemplate"></a>
## ShadowTemplate
Mixin for stamping a template into a Shadow DOM subtree upon
component instantiation
Mixin for stamping a template into a Shadow DOM subtree upon component
instantiation.
If a component defines a template property (as a string or referencing a HTML
template), when the component class is instantiated, a shadow root will be
created on the instance, and the contents of the template will be cloned into
the shadow root.
To use this mixin, define a `template` property as a string or HTML
`<template>` element:
For the time being, this extension retains support for Shadow DOM v0.
That will eventually be deprecated as browsers implement Shadow DOM v1.
class MyElement extends ShadowTemplate(HTMLElement) {
get template() {
return `Hello, <em>world</em>.`;
}
}
When your component class is instantiated, a shadow root will be created on
the instance, and the contents of the template will be cloned into the
shadow root. If your component does not define a `template` property, this
mixin has no effect.
For the time being, this extension retains support for Shadow DOM v0. That
will eventually be deprecated as browsers (and the Shadow DOM polyfill)
implement Shadow DOM v1.
**Kind**: global class
<a name="SwipeDirection"></a>
## SwipeDirection
Mixin which maps touch gestures (swipe left, swipe right) to direction
semantics (goRight, goLeft)
semantics (go right, go left).
By default, this mixin presents no user-visible effects; it just indicates a
direction in which the user is currently swiping or has finished swiping. To
map the direction to a change in selection, use the DirectionSelection mixin.
**Kind**: global class
<a name="undefinedposition"></a>
## undefinedposition : <code>Number</code>
* [SwipeDirection](#SwipeDirection)
* [.position](#SwipeDirection+position) : <code>number</code>
* [.goLeft()](#SwipeDirection+goLeft)
* [.goRight()](#SwipeDirection+goRight)
* [.showTransition(value)](#SwipeDirection+showTransition)
<a name="SwipeDirection+position"></a>
### swipeDirection.position : <code>number</code>
The distance the user has moved the first touchpoint since the beginning
of a drag, expressed as a fraction of the element's width.
**Kind**: global variable
**Properties**
**Kind**: instance property of <code>[SwipeDirection](#SwipeDirection)</code>
<a name="SwipeDirection+goLeft"></a>
### swipeDirection.goLeft()
Invoked when the user wants to go/navigate left.
The default implementation of this method does nothing.
| Name |
| --- |
| position |
**Kind**: instance method of <code>[SwipeDirection](#SwipeDirection)</code>
<a name="SwipeDirection+goRight"></a>
### swipeDirection.goRight()
Invoked when the user wants to go/navigate right.
The default implementation of this method does nothing.
**Kind**: instance method of <code>[SwipeDirection](#SwipeDirection)</code>
<a name="SwipeDirection+showTransition"></a>
### swipeDirection.showTransition(value)
Determine whether a transition should be shown during a swipe.
Components like carousels often define animated CSS transitions for
sliding effects. Such a transition should usually *not* be applied while
the user is dragging, because a CSS animation will introduce a lag that
makes the swipe feel sluggish. Instead, as long as the user is dragging
with their finger down, the transition should be suppressed. When the
user releases their finger, the transition can be restored, allowing the
animation to show the carousel sliding into its final position.
**Kind**: instance method of <code>[SwipeDirection](#SwipeDirection)</code>
| Param | Type | Description |
| --- | --- | --- |
| value | <code>boolean</code> | true if a component-provided transition should be shown, false if not. |
<a name="TargetInCollective"></a>
## TargetInCollective
Mixin which allows a component to provide aggregate behavior with
other elements, e.g., for keyboard handling
Mixin which allows a component to provide aggregate behavior with other
elements, e.g., for keyboard handling.
This mixin implicitly creates a collective for a component so that it can
participate in collective keyboard handling. See the Collective class for
details.
You can use this mixin in conjunction with ContentFirstChildTarget to
automatically have the component's collective extended to its first child.
**Kind**: global class
<a name="TargetInCollective+target"></a>
### targetInCollective.target : <code>HTMLElement</code>
Gets/sets the current target of the component.
Set this to point to another element. That target element will be
implicitly added to the component's collective. That is, the component
and its target will share responsibility for handling keyboard events.
You can set this property yourself, or you can use the
ContentFirstChildTarget mixin to automatically set the target to the
component's first child.
**Kind**: instance property of <code>[TargetInCollective](#TargetInCollective)</code>
<a name="TargetSelection"></a>
## TargetSelection
Mixin that allows a component to delegate its own selection
semantics to a target element
Mixin which allows a component to delegate its own selection semantics to a
target element.
This is useful when defining components that act as optional decorators for a
component that acts like a list.
This is useful when defining components that act as optional features for a
component that acts like a list. See basic-arrow-selection and
basic-page-dots for examples of components used as optional features for
components like basic-carousel. A typical usage might be:
<basic-arrow-selection>
<basic-carousel>
... images, etc. ...
</basic-carousel>
</basic-arrow-selection>
Because basic-arrow-selection uses the TargetSelection mixin, it exposes
members to access a selection: `selectNext`, `selectPrevious`,
`selectedIndex`, etc. These are all delegated to the child component (here,
a basic-carousel).
This mixin expects a `target` property to be set to the element actually
managing the selection. You can set that property yourself, or you can use
the ContentFirstChildTarget mixin to implicitly take the component's first
child as the target. This is what basic-arrow-selection (above) does.
**Kind**: global class
<a name="undefinedselectedIndex"></a>
## undefinedselectedIndex : <code>Number</code>
* [TargetSelection](#TargetSelection)
* [.items](#TargetSelection+items) : <code>Array.&lt;HTMLElement&gt;</code>
* [.selectedIndex](#TargetSelection+selectedIndex) : <code>number</code>
* [.selectedItem](#TargetSelection+selectedItem) : <code>HTMLElement</code>
* [.target](#TargetSelection+target) : <code>HTMLElement</code>
* [.indexOfItem(item)](#TargetSelection+indexOfItem) ⇒ <code>number</code>
* [.itemsChanged()](#TargetSelection+itemsChanged)
<a name="TargetSelection+items"></a>
### targetSelection.items : <code>Array.&lt;HTMLElement&gt;</code>
The current set of items in the list.
**Kind**: instance property of <code>[TargetSelection](#TargetSelection)</code>
<a name="TargetSelection+selectedIndex"></a>
### targetSelection.selectedIndex : <code>number</code>
The index of the item which is currently selected, or -1 if there is no
selection.
**Kind**: global variable
**Properties**
**Kind**: instance property of <code>[TargetSelection](#TargetSelection)</code>
<a name="TargetSelection+selectedItem"></a>
### targetSelection.selectedItem : <code>HTMLElement</code>
The currently selected item, or null if there is no selection.
| Name |
| --- |
| selectedIndex |
**Kind**: instance property of <code>[TargetSelection](#TargetSelection)</code>
<a name="TargetSelection+target"></a>
### targetSelection.target : <code>HTMLElement</code>
Gets/sets the target element to which this component will delegate
selection actions.
<a name="undefinedselectedItem"></a>
## undefinedselectedItem : <code>Object</code>
The currently selected item, or null if there is no selection.
**Kind**: instance property of <code>[TargetSelection](#TargetSelection)</code>
<a name="TargetSelection+indexOfItem"></a>
### targetSelection.indexOfItem(item) ⇒ <code>number</code>
Return the positional index for the indicated item.
**Kind**: global variable
**Properties**
**Kind**: instance method of <code>[TargetSelection](#TargetSelection)</code>
**Returns**: <code>number</code> - The index of the item, or -1 if not found.
| Name |
| --- |
| selectedItem |
| Param | Type | Description |
| --- | --- | --- |
| item | <code>HTMLElement</code> | The item whose index is requested. |
<a name="TargetSelection+itemsChanged"></a>
### targetSelection.itemsChanged()
This method is invoked when the underlying contents change. It is also
invoked on component initialization – since the items have "changed" from
being nothing.
**Kind**: instance method of <code>[TargetSelection](#TargetSelection)</code>
<a name="TimerSelection"></a>
## TimerSelection
Mixin provides for automatic timed changes in selection, as in a
automated slideshow
Mixin which provides for automatic timed changes in selection.
This mixin is useful for creating slideshow-like elements.
This mixin expects the component to define an `items` property, as well as
`selectFirst` and `selectNext` methods. You can implement those yourself,
or use the ContentAsItems and ItemsSelection mixins.
**Kind**: global class
<a name="undefinedplaying"></a>
## undefinedplaying : <code>Boolean</code>
True if the selection is being automatically advanced.
**Kind**: global variable
**Properties**
* [TimerSelection](#TimerSelection)
* [.playing](#TimerSelection+playing) : <code>boolean</code>
* [.play()](#TimerSelection+play)
* [.pause()](#TimerSelection+pause)
| Name |
| --- |
| playing |
<a name="TimerSelection+playing"></a>
### timerSelection.playing : <code>boolean</code>
True if the selection is being automatically advanced.
<a name="play"></a>
## play()
**Kind**: instance property of <code>[TimerSelection](#TimerSelection)</code>
<a name="TimerSelection+play"></a>
### timerSelection.play()
Begin automatic progression of the selection.
**Kind**: global function
<a name="pause"></a>
## pause()
**Kind**: instance method of <code>[TimerSelection](#TimerSelection)</code>
<a name="TimerSelection+pause"></a>
### timerSelection.pause()
Pause automatic progression of the selection.
**Kind**: global function
**Kind**: instance method of <code>[TimerSelection](#TimerSelection)</code>
<a name="TrackpadDirection"></a>
## TrackpadDirection
Mixin which maps a horizontal trackpad swipe gestures (or
horizontal mouse wheel actions) to direction semantics
Mixin which maps a horizontal trackpad swipe gestures (or horizontal mouse
wheel actions) to direction semantics.
To respond to the trackpad, we can listen to the DOM's "wheel" events. These
events are fired as the user drags their fingers across a trackpad.
Unfortunately, this scheme is missing a critical event — there is no event
when the user *stops* a gestured on the trackpad.
You can use this mixin with a mixin like DirectionSelection to let the user
change the selection with the trackpad or mouse wheel.
To complicate matters, the mainstream browsers continue to generate wheel
events even after the user has stopped dragging their fingers. These fake
events simulate the user gradually slowing down the drag until they come to a
smooth stop. In some contexts, these fake wheel events might be helpful, but
in trying to supply typical trackpad swipe navigation, these fake events get
in the way.
To respond to the trackpad, we can listen to the DOM's "wheel" events.
These events are fired as the user drags their fingers across a trackpad.
Unfortunately, browsers are missing a critical event — there is no event
when the user *stops* a gestured on the trackpad or mouse wheel.
This component uses some heuristics to work around these problems, but the
complex nature of the problem make it extremely difficult to achieve the same
degree of trackpad responsiveness possible with native applications.
To make things worse, the mainstream browsers continue to generate fake
wheel events even after the user has stopped dragging their fingers. These
fake events simulate the user gradually slowing down the drag until they
come to a smooth stop. In some contexts, these fake wheel events might be
helpful, but in trying to supply typical trackpad swipe navigation, these
fake events get in the way.
This component uses heuristics to work around these problems, but the
complex nature of the problem make it extremely difficult to achieve the
same degree of trackpad responsiveness possible with native applications.
**Kind**: global class
* [TrackpadDirection](#TrackpadDirection)
* [.position](#TrackpadDirection+position) : <code>number</code>
* [.goLeft()](#TrackpadDirection+goLeft)
* [.goRight()](#TrackpadDirection+goRight)
<a name="TrackpadDirection+position"></a>
### trackpadDirection.position : <code>number</code>
The distance the user has moved the first touchpoint since the beginning
of a trackpad/wheel operation, expressed as a fraction of the element's
width.
**Kind**: instance property of <code>[TrackpadDirection](#TrackpadDirection)</code>
<a name="TrackpadDirection+goLeft"></a>
### trackpadDirection.goLeft()
Invoked when the user wants to go/navigate left.
The default implementation of this method does nothing.
**Kind**: instance method of <code>[TrackpadDirection](#TrackpadDirection)</code>
<a name="TrackpadDirection+goRight"></a>
### trackpadDirection.goRight()
Invoked when the user wants to go/navigate right.
The default implementation of this method does nothing.
**Kind**: instance method of <code>[TrackpadDirection](#TrackpadDirection)</code>
{
"name": "basic-component-mixins",
"version": "0.7.0",
"version": "0.7.1",
"description": "Mixins for creating web components in plain JavaScript",

@@ -5,0 +5,0 @@ "homepage": "https://component.kitchen",

@@ -1,38 +0,59 @@

This package implements common web component features as mixins. It uses mixins
to achieve the same results as a monolithic component framework, while
permitting more flexibility and a pay-as-you-go approach to complexity and
performance.
# basic-component-mixins
Design goals:
Mixin library for creating web components in plain JavaScript (ES5 or ES6)
1. Have each web component mixins focus on solving a single, common task. They
should be well-factored. They should be able to be used on their own, or in
combination.
2. Introduce as few new concepts as possible. Any developer who understands the
DOM API should find this architecture appealing, without having to learn many
proprietary concepts (beyond mixins, see below).
3. Focus on native browser support for ES6 and web components. The architecture
should be useful in a production application today, but should also feel
correct in a future world in which native ES6 and web components are
everywhere.
[![npm version](https://img.shields.io/npm/v/basic-component-mixins.svg?style=flat)](https://www.npmjs.com/package/basic-component-mixins)
This library implements common web component features as JavaScript mixins. It
is designed for people who would like to create web components in plain
JavaScript while avoiding much of the boilerplate that comes up in component
creation. The mixins permit flexibility and a pay-as-you-go approach to
complexity and performance.
# Building
Design goals:
After cloning this repository:
1. **Focus each mixin on solving a single, common component task.**
Each mixin should be useful on its own, or in combination.
2. **Introduce as few new concepts as possible.**
A developer who understands the DOM API should be able to work with these
mixins without having to substantially change the way they write code. They
shouldn't have to learn many proprietary concepts beyond those listed below,
chiefly the notion of defining a mixin as a function.
3. **Anticipate native browser support for ES6 and web components.**
The architecture should be useful in an ES5 application today, but should
also feel correct in a future world in which native ES6 and web components
are everywhere.
> npm install
> grunt build
All of the top-level Basic Web Components are constructed with these mixins. In
fact, by design, most of those components are little more than combinations of
mixins. That factoring allows you to create your own web components in the
likely event that your needs differ from those driving the design of the Basic
Web Components. You can use these mixins without using those components.
# Composing web component classes with mixins as functions
# Mixin concepts
Web components can be expressed as compositions of base classes and mixins.
Mixins here are defined as a function that takes a base class and returns a
subclass defining the new features:
## Mixins as functions
The mixins in this library all take the form of a function. Each function takes
a base class and returns a subclass defining the desired features:
let MyMixin = (base) => class MyMixin extends base {
// Mixin defines properties and methods here.
greet() {
return "Hello";
}
};
class MyBaseClass {}
let NewClass = MyMixin(MyBaseClass);
let obj = new NewClass();
obj.greet(); // "Hello"
Many JavaScript mixin implementations destructively modify a class prototype,
but mixins of the functional style shown above do not. Rather, functional mixins
extend the prototype chain and return a new class. Such functions have been
called "higher-order components", but we prefer the term "mixin" for brevity.
The mixins in this package take care to ensure that base class properties and

@@ -46,16 +67,188 @@ methods are not broken by the mixin. In particular, if a mixin wants to add a

A virtue of a functional mixin is that you do not need to use any library to
apply it. This increases the chance that mixins can be shared across projects.
If a common extension/mixin solution can be agreed upon, frameworks sharing that
solution gain a certain degree of code sharing, interoperability, and can share
conceptual materials. This reduces the learning curve for dealing with any one
framework.
A core virtue of a functional mixin is that you do not need to use any library
to apply it. This lets you use these mixins with any conventional means of
defining JavaScript classes — you don't have to invoke a proprietary class
factory, nor do you have to load a separate framework or runtime.
Frameworks can still make their own decisions about which features they want to
offer by virtue of which mixins they incorporate into their base classes.
Because mixins define behavior through composition, you're not limited by the
constraints of a single-inheritance class hierarchy. That said, you can still
use a class hierarchy if you feel that's suitable for your application. For
example, you can compose a set of mixins to create a custom base class from
which your other classes derive. But the use of such a base class is not
dictated here.
## Semantic mixin factoring
In a number of areas, this package factors high-level component services into
mixins that work together to deliver the overall service. This is done to
increase flexibility.
For example, this library includes three mixins that work in concert. When
applied to a custom element, these mixins take care of mapping presses on
keyboard arrows (Left/Right) into selection actions (select previous/next).
They each take care of a different piece of the problem:
* The [Keyboard](docs/Keyboard.md) mixin wires up a single keydown listener on
the component that can be shared by multiple mixins. When the component has
the focus, a keypress will result in the invocation of a `keydown` method. By
default, that method does nothing.
* The [KeyboardDirection](docs/KeyboardDirection.md) mixin maps keyboard
semantics to direction semantics. It defines a `keydown` method that maps
Left/Right arrow key presses into calls to methods `goLeft` and `goRight`,
respectively. By default, those methods do nothing.
* The [DirectionSelection](docs/DirectionSelection.md) mixin maps direction
semantics to selection semantics. It defines `goLeft` and `goRight` methods
which respectively invoke methods `selectPrevious` and `selectNext`. Again, by
default, those methods do nothing.
If all three mixins are applied to a component, then when the user presses, say,
the Right arrow key, the following sequence happens:
(keyboard event) → keydown() → goRight() → selectNext()
Other mixins can map selection semantics to user-visible effects, such as
highlighting the selected item, ensure the selected item is in view, or do
something entirely new which you define.
Such factoring may initially feel overly complex, but permits a critical degree
of developer freedom. You might want to handle the keyboard a different way,
for example. Or you may want to create a component that handles arrow
keypresses for something other than selection, for example. Or you may want to
let the user manipulate the selection through other modalities, such as touch
gestures, mouse actions, speech commands, etc.
As one example of another mode of user input, the
[SwipeDirection](docs/SwipeDirection.md) mixin maps touch gestures to `goLeft`
and `goRight` method calls. It can therefore be used in combination with the
DirectionSelection mixin above, with the result that swipes will change the
selection:
(touch event) → goRight() → selectNext()
The SwipeDirection and KeyboardDirection mixins are compatible, and can be
applied to the same component. Users of that component will be able to change
the selection with both touch gestures and the keyboard.
This factoring allows components with radically different presentations to
nevertheless share a considerable amount of user interface logic. For example,
the [basic-carousel](../packages/basic-carousel) and
[basic-list-box](packages/basic-list-box) components look very different, but
both make use of same mixins to support changing the selection with the
keyboard. In fact, nearly all of those components' behavior is defined through
shared mixins factored this way.
# Using the mixins to create web components
## Installation of the mixins package via npm
Add a `dependencies` key for `basic-component-mixins` in your project's
package.json file. Until native Shadow DOM support is available on all browsers
you want to support, you'll want to include the [webcomponents.js
polyfill](https://github.com/webcomponents/webcomponentsjs) as well:
{
...
"dependencies": {
"basic-component-mixins": "^0.7",
"webcomponents.js": "^0.7.2"
},
}
Then issue an `npm install` as usual.
A [sample-component](https://github.com/basic-web-components/sample-component)
project demonstrates the use of npm to depend on the basic-component-mixins
package. It shows the creation of a simple component in both ES6 and ES5.
## ES6
Your ES6 code can reference the mixin as a function exported by the
corresponding file in this package's /src folder. You then apply the mixin to
the element base class you'd like to use. This can be `HTMLElement`, or an
element class of your own creation.
As a very simple example, if you'd like to create a web component that puts the
word "Hello" before its tag contents, you can use the ShadowTemplate mixin. This
will look for a `template` property on the component, attach a new Shadow DOM
subtree to the component, then copy the template into the shadow subtree.
import ShadowTemplate from 'basic-component-mixins/src/ShadowTemplate';
// Create a simple custom element that supports a template.
class GreetElement extends ShadowTemplate(HTMLElement) {
get template() {
return `
Hello, <slot></slot>.
`;
}
}
// Register the custom element with the browser.
document.registerElement('greet-element', GreetElement);
Compile this source with your favorite ES6 processor (e.g.,
[Babel](https://babeljs.org)), then load the result into a page.
<html>
<head>
<script src="node_modules/webcomponents.js/webcomponents.js"></script>
<script src="greet-element.js"></script>
</head>
<body>
<!-- Hello, world. -->
<greet-element>world</greet-element>
</body>
</html>
## ES5
This package's /dist folder contains a JavaScript file that defines all the
mixins as globals available via `window.Basic`. For example, the ShadowTemplate
mixin shown above is available as `window.Basic.ShadowTemplate`.
You can create your custom element class by hand:
// greet-element.js
var GreetElement = Basic.ShadowTemplate(HTMLElement);
GreetElement.prototype.template = 'Hello, <slot></slot>.';
document.registerElement('greet-element', GreetElement);
You can also use Composable mixin to create the class:
// greet-element.js
var GreetElement = Basic.Composable(HTMLElement).compose(
ShadowTemplate,
{
template: 'Hello, <slot></slot>.'
}
);
document.registerElement('greet-element', GreetElement);
Then load the script defining the element into your page:
<html>
<head>
<script src="node_modules/webcomponents.js/webcomponents.js"></script>
<script src="node_modules/basic-component-mixins/dist/basic-component-mixins.js"></script>
<script src="greet-element.js"></script>
</head>
<body>
<!-- Hello, world. -->
<greet-element>world</greet-element>
</body>
</html>
# Web component mixins
The /src folder includes mixins for common web component features:
The /src folder includes the complete set of mixins, each of which address some
common web component feature:

@@ -73,2 +266,5 @@ * [AttributeMarshalling](docs/AttributeMarshalling.md).

Facilitates the application of a set of mixins.
* [composeTemplates](docs/composeTemplates.md).
Not a mixin, but a helper function for letting a component insert its template
inside a template defined by a base class.
* [ContentAsItems](docs/ContentAsItems.md).

@@ -131,5 +327,2 @@ Lets a component treat its content as items in a list.

semantics.
* [composeTemplates](docs/composeTemplates.md).
Not a mixin, but a helper function for letting a component insert its template
inside a template defined by a base class.

@@ -136,0 +329,0 @@

@@ -1,32 +0,71 @@

/**
* @class AttributeMarshalling
* @classdesc Mixin which marshalls attributes to properties (and eventually
* vice versa)
*
* This only supports string properties for now.
*/
/* Exported function extends a base class with AttributeMarshalling. */
export default (base) => {
/**
* Mixin which marshalls attributes to properties (and eventually vice versa).
*
* If your component exposes a setter for a property, it's generally a good
* idea to let devs using your component be able to set that property in HTML
* via an element attribute. You can code that yourself by writing an
* `attributeChangedCallback`, or you can use this mixin to get a degree of
* automatic support.
*
* This mixin implements an `attributeChangedCallback` that will attempt to
* convert a change in an element attribute into a call to the corresponding
* property setter. Attributes typically follow hyphenated names ("foo-bar"),
* whereas properties typically use camelCase names ("fooBar"). This mixin
* respects that convention, automatically mapping the hyphenated attribute
* name to the corresponding camelCase property name.
*
* Example: You define a component using this mixin:
*
* class MyElement extends AttributeMarshalling(HTMLElement) {
* get fooBar() { return this._fooBar; }
* set fooBar(value) { this._fooBar = value; }
* }
* document.registerElement('my-element', MyElement);
*
* If someone then instantiates your component in HTML:
*
* <my-element foo-bar="Hello"></my-element>
*
* Then, after the element has been upgraded, the `fooBar` setter will
* automatically be invoked with the initial value "Hello".
*
* For the time being, this mixin only supports string-valued properties.
* If you'd like to convert string attributes to other types (numbers,
* booleans), you need to implement `attributeChangedCallback` yourself.
*/
class AttributeMarshalling extends base {
export default (base) => class AttributeMarshalling extends base {
/*
* Handle a change to the attribute with the given name.
*/
attributeChangedCallback(name, oldValue, newValue) {
if (super.attributeChangedCallback) { super.attributeChangedCallback(); }
// If the attribute name corresponds to a property name, then set that
// property. Ignore changes in standard HTMLElement properties.
let propertyName = attributeToPropertyName(name);
if (propertyName in this && !(propertyName in HTMLElement.prototype)) {
this[propertyName] = newValue;
}
}
/*
* Handle a change to the attribute with the given name.
*/
attributeChangedCallback(name, oldValue, newValue) {
if (super.attributeChangedCallback) { super.attributeChangedCallback(); }
// If the attribute name corresponds to a property name, then set that
// property. Ignore changes in standard HTMLElement properties.
let propertyName = attributeToPropertyName(name);
if (propertyName in this && !(propertyName in HTMLElement.prototype)) {
this[propertyName] = newValue;
/*
* Generate an initial call to attributeChangedCallback for each attribute
* on the element.
*
* TODO: The plan for Custom Elements v1 is for the browser to handle this.
* Once that's handled (including in polyfills), this call can go away.
*/
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
[].forEach.call(this.attributes, attribute => {
this.attributeChangedCallback(attribute.name, undefined, attribute.value);
});
}
}
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
[].forEach.call(this.attributes, attribute => {
this.attributeChangedCallback(attribute.name, undefined, attribute.value);
});
}
return AttributeMarshalling;
};

@@ -33,0 +72,0 @@

@@ -1,40 +0,49 @@

/**
* @class ClickSelection
* @classdesc Mixin which maps a click (actually, a mousedown) to selection
*
* If the user clicks an element, and the element is an item in the list, then
* the component's selectedIndex will be set to the index for that item.
*/
/* Exported function extends a base class with ClickSelection. */
export default (base) => {
/**
* Mixin which maps a click (actually, a mousedown) to a selection.
*
* This simple mixin is useful in list box-like elements, where a click on a
* list item implicitly selects it.
*
* This mixin expects the component to provide a method `indexOfItem(item)`.
* You can provide that method yourself, or use the ContentAsItems mixin.
* This mixin also expects the component to define a `selectedIndex`
* property. You can provide that yourself, or use the ItemsSelection mixin.
*/
class ClickSelection extends base {
export default (base) => class ClickSelection extends base {
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
/*
* REVIEW: Which event should we listen to here?
*
* The standard use for this mixin is in list boxes. List boxes don't
* appear to be consistent with regard to whether they select on mousedown
* or click/mouseup.
*/
this.addEventListener('mousedown', event => {
selectTarget(this, event.target);
// Note: We don't call preventDefault here. The default behavior for
// mousedown includes setting keyboard focus if the element doesn't
// already have the focus, and we want to preserve that behavior.
event.stopPropagation();
});
}
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
/*
* REVIEW: Which event should we listen to here?
*
* The standard use for this mixin is in list boxes. List boxes don't
* appear to be consistent with regard to whether they select on mousedown
* or click/mouseup.
*/
this.addEventListener('mousedown', event => {
selectTarget(this, event.target);
// Note: We don't call preventDefault here. The default behavior for
// mousedown includes setting keyboard focus if the element doesn't
// already have the focus, and we want to preserve that behavior.
event.stopPropagation();
});
}
// Default implementation. This will typically be handled by other mixins.
get selectedIndex() {
return super.selectedIndex;
}
set selectedIndex(index) {
if ('selectedIndex' in base.prototype) { super.selectedIndex = index; }
}
// Default implementation. This will typically be handled by other mixins.
get selectedIndex() {
return super.selectedIndex;
}
set selectedIndex(index) {
if ('selectedIndex' in base.prototype) { super.selectedIndex = index; }
}
return ClickSelection;
};
// TODO: Handle the case where a list item has subelements. Walk up the DOM

@@ -41,0 +50,0 @@ // hierarchy until we find an item in the list, or come back to this element,

/**
* @class Collective
* @classdesc A group of elements that have been joined together for the purpose of
* A group of elements that have been associated for the purpose of
* accomplishing some collective behavior, e.g., keyboard handling.
*
* This is not a mixin, but a class used by the TargetInCollective mixin.
* There are certain components that want to cooperatively handle the keyboard.
* For example, the basic-arrow-selection and basic-page-dots components are
* optional components that can augment the appearance and behavior of an inner
* basic-carousel, adding arrow buttons and dot buttons, respectively. When
* these components are nested together, they form an implicit unit called a
* *collective*:
*
* <basic-arrow-selection>
* <basic-page-dots>
* <basic-carousel>
* ... images, etc. ...
* </basic-carousel>
* </basic-page-dots>
* </basic-arrow-selection>
*
* In this configuration, the three components will all have a `this.collective`
* reference that refers to a shared instance of the `Collective` class.
*
* The Keyboard mixin they use is sensitive to the presence of
* the collective. Among other things, it will ensure that only the outermost
* element above — the basic-arrow-selection — will be a tab stop that can
* receive the keyboard focus. This lets the user perceive the component
* arrangement above as a single unit. The Keyboard mixin will also give each
* element in the collective a chance to process any keyboard events. So, even
* though the basic-arrow-selection element will have the focus, the standard
* keyboard navigation provided by basic-carousel will continue to work.
*
* The SelectionAriaActive component also respects collectives when using the
* `aria-activedescendant` and `role` attributes. Those will be applied to the
* outermost element (basic-arrow-selection, above) so that ARIA can correctly
* understand the arrangement of the elements.
*
* You can put elements into collectives yourself, or you can use the
* TargetInCollective mixin.
*/
class Collective {
export default class Collective {
/**
* Create a collective.
*
* @param {HTMLELement[]} [elements] - Initial elements to add.
*/
constructor(...elements) {
/**
* The elements in the collective.
*
* @type {HTMLElement[]}
*/
this.elements = [];

@@ -17,2 +57,15 @@ elements.forEach(element => this.assimilate(element));

/**
* Add the indicated target to the collective.
*
* By convention, if two elements wants to participate in a collective, and
* one element is an ancestor of the other in the DOM, the ancestor should
* assimilate the descendant instead of the other way around.
*
* After assimilation, any element in the collective that defines a
* `collectiveChanged` method will have that method invoked. This allows
* the collective's elements to respond to changes in the collective.
*
* @param {(HTMLElement|Collective)} target - The element or collective to add.
*/
assimilate(target) {

@@ -35,2 +88,8 @@ let collectiveChanged;

/**
* Invoke a method on all elements in the collective.
*
* @param {string} method - The name of the method to invoke on all elements.
* @param {object[]} [args] - The arguments to the method
*/
invokeMethod(method, ...args) {

@@ -47,2 +106,6 @@ // Invoke from innermost to outermost.

/**
* The outermost element in the collective.
* By convention, this is the first element in the `elements` array.
*/
get outermostElement() {

@@ -85,1 +148,4 @@ return this.elements[0];

}
export default Collective;

@@ -1,44 +0,52 @@

/**
* @class Composable
* @classdesc Mixin to make a class more easily composable with other mixins
*
* The main contribution is the introduction of a `compose` method that applies
* a set of mixin functions and returns the resulting new class. This sugar
* can make the application of many mixins at once easier to read.
*/
/* Exported function extends a base class with Composable. */
export default (base) => {
export default (base) => class Composable extends base {
/**
* Apply a set of mixin functions or mixin objects to the present class and
* return the new class.
* Mixin to make a class more easily composable with other mixins.
*
* A call like
*
* let MyClass = Mixin1(Mixin2(Mixin3(Mixin4(Mixin5(BaseClass)))));
*
* Can be converted to:
*
* let MyClass = Composable(BaseClass).compose(
* Mixin1,
* Mixin2,
* Mixin3,
* Mixin4,
* Mixin5
* );
*
* This function can also take mixin objects. A mixin object is just a
* shorthand for a mixin function that creates a new subclass with the given
* members. The mixin object's members are *not* copied directly onto the
* prototype of the base class, as with traditional mixins.
* This mixin contributes a `compose` method that applies a set of mixin
* functions and returns the resulting new class. This sugar can make the
* application of many mixins at once easier to read.
*/
static compose(...mixins) {
// We create a new subclass for each mixin in turn. The result becomes
// the base class extended by any subsequent mixins. It turns out that
// we can use Array.reduce() to concisely express this, using the current
// object as the seed for reduce().
return mixins.reduce(composeClass, this);
class Composable extends base {
/**
* Apply a set of mixin functions or mixin objects to the present class and
* return the new class.
*
* Instead of writing:
*
* let MyClass = Mixin1(Mixin2(Mixin3(Mixin4(Mixin5(BaseClass)))));
*
* You can write:
*
* let MyClass = Composable(BaseClass).compose(
* Mixin1,
* Mixin2,
* Mixin3,
* Mixin4,
* Mixin5
* );
*
* This function can also take mixin objects. A mixin object is just a
* shorthand for a mixin function that creates a new subclass with the given
* members. The mixin object's members are *not* copied directly onto the
* prototype of the base class, as with traditional mixins.
*
* In addition to providing syntactic sugar, this mixin can be used to
* define a class in ES5, which lacks ES6's `class` keyword.
*
* @param {...mixins} mixins - A set of mixin functions or objects to apply.
*/
static compose(...mixins) {
// We create a new subclass for each mixin in turn. The result becomes
// the base class extended by any subsequent mixins. It turns out that
// we can use Array.reduce() to concisely express this, using the current
// object as the seed for reduce().
return mixins.reduce(composeClass, this);
}
}
return Composable;
};

@@ -45,0 +53,0 @@

/**
* @class composeTemplates
* @classdesc Given two templates, this "folds" one inside the other
* @method composeTemplates
* @description Given two templates, this "folds" one inside the other. This is
* is useful for defining a component that wants to fill in slots in the
* template of its base class.
*
* For now, the folding process just entails putting the first inside the
* location of the first <content> node in the second template.
* location of the first <slot> node in the second template.
*
* Example: if the first (sub) template is
* Example: if the first (base) template is
*
* <template>
* Hello, <slot></slot>.
* </template>
* <template>
* <b>
* <slot></slot>
* </b>
* </template>
*
* and the second (base) template is
* and the second (subclass) template is
*
* <template>
* <b>
* <slot></slot>
* </b>
* </template>
* <template>
* Hello, <slot></slot>.
* </template>
*
* Then the returned folded template is
* Then the result of calling `composeTemplates(first, second)` is
*
* <template>
* <b>
* Hello, <slot></slot>.
* </b>
* </template>
* <template>
* <b>
* Hello, <slot></slot>.
* </b>
* </template>
*
* Note that this function is not a mixin, but a helper for creating web
* components.
*
* @param {(HTMLTemplate|string)} baseTemplate - The base class template.
* @param {(HTMLTemplate|string)} subTemplate - The subclass template.
*/
export default function composeTemplates(baseTemplate, mixinTemplate) {
export default function composeTemplates(baseTemplate, subTemplate) {
if (!baseTemplate) {
// No folding necessary.
return mixinTemplate;
return subTemplate;
}
baseTemplate = makeTemplate(baseTemplate);
mixinTemplate = makeTemplate(mixinTemplate);
subTemplate = makeTemplate(subTemplate);
let baseElement = baseTemplate && baseTemplate.content.cloneNode(true);
let mixinElement = mixinTemplate && mixinTemplate.content.cloneNode(true);
let mixinElement = subTemplate && subTemplate.content.cloneNode(true);

@@ -44,0 +52,0 @@ let folded = document.createElement('template');

@@ -1,75 +0,109 @@

/**
* @class ContentAsItems
* @classdesc Mixin which maps content semantics (children) to list item
* semantics
*
* Items differ from children in several ways:
*
* * They are often referenced via index.
* * They may have a selection state.
* * It's common to do work to initialize the appearance or state of a new item.
* * Auxiliary invisible child elements are filtered out and not counted as
* items. Auxiliary elements include link, script, style, and template
* elements.
*/
/* Exported function extends a base class with ContentAsItems. */
export default (base) => {
export default (base) => class ContentAsItems extends base {
applySelection(item, selected) {
if (super.applySelection) { super.applySelection(item, selected); }
item.classList.toggle('selected', selected);
}
contentChanged() {
if (super.contentChanged) { super.contentChanged(); }
this._items = null;
this.itemsChanged();
}
/**
* Returns the positional index for the indicated item.
* Mixin which maps content semantics (elements) to list item semantics.
*
* Because this acts like a getter, this does not invoke a base implementation.
* This mixin expects a component to provide a `content` property returning a
* raw set of elements. You can provide that yourself, or use the
* `DistributedChildrenAsContent` mixin.
*
* @method indexOfItem
* @param {object} item The item whose index is requested.
* @returns {number} The index of the item, or -1 if not found.
* Items differ from element contents in several ways:
*
* * They are often referenced via index.
* * They may have a selection state.
* * It's common to do work to initialize the appearance or state of a new
* item.
* * Auxiliary invisible child elements are filtered out and not counted as
* items. Auxiliary elements include link, script, style, and template
* elements. This filtering ensures that those auxiliary elements can be
* used in markup inside of a list without being treated as list items.
*/
indexOfItem(item) {
return this.items.indexOf(item);
}
class ContentAsItems extends base {
// Default implementation does nothing.
itemAdded(item) {
if (super.itemAdded) { super.itemAdded(item); }
}
/**
* Apply the selection state to a single item.
*
* Invoke this method to signal that the selected state of the indicated item
* has changed. By default, this applies a `selected` CSS class if the item
* is selected, and removed it if not selected.
*
* @param {HTMLElement} item - The item whose selection state has changed.
* @param {boolean} selected - True if the item is selected, false if not.
*/
applySelection(item, selected) {
if (super.applySelection) { super.applySelection(item, selected); }
item.classList.toggle('selected', selected);
}
itemsChanged() {
if (super.itemsChanged) { super.itemsChanged(); }
contentChanged() {
if (super.contentChanged) { super.contentChanged(); }
this._items = null;
this.itemsChanged();
}
// Perform per-item initialization.
this.items.forEach(item => {
if (!item._itemInitialized) {
this.itemAdded(item);
item._itemInitialized = true;
/**
* Return the positional index for the indicated item.
*
* Because this acts like a getter, this does not invoke a base implementation.
*
* @param {HTMLElement} item The item whose index is requested.
* @returns {number} The index of the item, or -1 if not found.
*/
indexOfItem(item) {
return this.items.indexOf(item);
}
/**
* This method is invoked whenever a new item is added to the list.
*
* The default implementation of this method does nothing. You can override
* this to perform per-item initialization.
*
* @param {HTMLElement} item - The item that was added.
*/
itemAdded(item) {
if (super.itemAdded) { super.itemAdded(item); }
}
/**
* The current set of items in the list. See the top-level documentation for
* mixin for a description of how items differ from plain content.
*
* @type {HTMLElement[]}
*/
get items() {
if (this._items == null) {
this._items = filterAuxiliaryElements(this.content);
}
});
return this._items;
}
this.dispatchEvent(new CustomEvent('items-changed'));
}
/**
* This method is invoked when the underlying contents change. It is also
* invoked on component initialization – since the items have "changed" from
* being nothing.
*/
itemsChanged() {
if (super.itemsChanged) { super.itemsChanged(); }
/**
* The current set of items in the list.
*
* @property {object} items
*/
// TODO: property notifications so elements can bind to this property
get items() {
if (this._items == null) {
this._items = filterAuxiliaryElements(this.content);
// Perform per-item initialization.
this.items.forEach(item => {
if (!item._itemInitialized) {
this.itemAdded(item);
item._itemInitialized = true;
}
});
this.dispatchEvent(new CustomEvent('items-changed'));
}
return this._items;
/*
* @event items-changed
*
* This event is raised when the set of items changes.
*/
}
return ContentAsItems;
};

@@ -76,0 +110,0 @@

@@ -1,27 +0,56 @@

/**
* @class ContentFirstChildTarget
* @classdesc Mixin that defines the target of a component -- the element the
* component is managing or somehow responsible for -- as its first child
*/
/* Exported function extends a base class with ContentFirstChildTarget. */
export default (base) => {
/**
* Mixin that defines the target of a component — the element the component is
* managing or somehow responsible for — as its first child.
*
* Some components serve to decorate or modify other elements. A common
* pattern is to have one component wrap another, and have the outer, parent
* component implicitly modify the child. This mixin facilitates this by
* implicitly taking an element's first child as its "target".
*
* Example:
*
* <outer-element>
* <inner-element></inner-element>
* </outer-element>
*
* If `outer-element` uses this mixin, then its `target` property will be
* set to point to the `inner-element`, because that is its first child.
*
* This mixin expects a `content` property that returns the element's content.
* You can implement that yourself, or use the DistributedChildrenAsContent
* mixin.
*
* This mixin can be combined with the TargetInCollective mixin to have a
* component participate in collective keyboard handling. *
*/
class ContentFirstChildTarget extends base {
export default (base) => class ContentFirstChildTarget extends base {
contentChanged() {
if (super.contentChanged) { super.contentChanged(); }
let content = this.content;
let target = content && content[0];
if (target) {
this.target = target;
}
}
contentChanged() {
if (super.contentChanged) { super.contentChanged(); }
let content = this.content;
let target = content && content[0];
if (target) {
this.target = target;
/**
* Gets/sets the current target of the component.
*
* @type {HTMLElement}
*/
get target() {
return this._target;
}
}
set target(element) {
if ('target' in base.prototype) { super.target = element; }
this._target = element;
}
get target() {
return this._target;
}
set target(element) {
if ('target' in base.prototype) { super.target = element; }
this._target = element;
}
return ContentFirstChildTarget;
};

@@ -1,55 +0,61 @@

/**
* @class DirectionSelection
* @classdesc Mixin which maps direction semantics (goLeft, goRight, etc.) to
* selection semantics (selectPrevious, selectNext, etc.)
*/
/* Exported function extends a base class with DirectionSelection. */
export default (base) => {
/**
* Mixin which maps direction semantics (goLeft, goRight, etc.) to selection
* semantics (selectPrevious, selectNext, etc.).
*
* This mixin can be used in conjunction with the KeyboardDirection mixin
* (which maps keyboard events to directions) and a mixin that handles
* selection like ItemsSelection.
*/
class DirectionSelection extends base {
export default (base) => class DirectionSelection extends base {
goDown() {
if (super.goDown) { super.goDown(); }
return this.selectNext();
}
goDown() {
if (super.goDown) { super.goDown(); }
return this.selectNext();
}
goEnd() {
if (super.goEnd) { super.goEnd(); }
return this.selectLast();
}
goEnd() {
if (super.goEnd) { super.goEnd(); }
return this.selectLast();
}
goLeft() {
if (super.goLeft) { super.goLeft(); }
return this.selectPrevious();
}
goLeft() {
if (super.goLeft) { super.goLeft(); }
return this.selectPrevious();
}
goRight() {
if (super.goRight) { super.goRight(); }
return this.selectNext();
}
goRight() {
if (super.goRight) { super.goRight(); }
return this.selectNext();
}
goStart() {
if (super.goStart) { super.goStart(); }
return this.selectFirst();
}
goStart() {
if (super.goStart) { super.goStart(); }
return this.selectFirst();
}
goUp() {
if (super.goUp) { super.goUp(); }
return this.selectPrevious();
}
goUp() {
if (super.goUp) { super.goUp(); }
return this.selectPrevious();
}
// Default implementations. These will typically be handled by other mixins.
selectFirst() {
if (super.selectFirst) { return super.selectFirst(); }
}
selectLast() {
if (super.selectLast) { return super.selectLast(); }
}
selectNext() {
if (super.selectNext) { return super.selectNext(); }
}
selectPrevious() {
if (super.selectPrevious) { return super.selectPrevious(); }
}
// Default implementations. These will typically be handled by other mixins.
selectFirst() {
if (super.selectFirst) { return super.selectFirst(); }
}
selectLast() {
if (super.selectLast) { return super.selectLast(); }
}
selectNext() {
if (super.selectNext) { return super.selectNext(); }
}
selectPrevious() {
if (super.selectPrevious) { return super.selectPrevious(); }
}
return DirectionSelection;
};

@@ -1,41 +0,89 @@

/**
* @class DistributedChildren
* @classdesc Mixin which defines helpers for accessing a component's
* distributed children as a flattened array or string.
*/
// TODO: Rationalize with new Custom Elements API.
// TODO: Consider renaming to match Custom Elements API.
export default (base) => class DistributedChildren extends base {
/* Exported function extends a base class with DistributedChildren. */
export default (base) => {
/*
* Returns an in-order collection of children, expanding any content nodes.
* Like the standard children property, this skips text nodes.
/**
* Mixin which defines helpers for accessing a component's distributed
* children as a flattened array or string.
*
* TODO: This walks the whole content tree every time the list is requested.
* It'd be nice to cache the answer and invalidate it only when content
* actually changes.
* The standard DOM API provides several ways of accessing child content:
* `children`, `childNodes`, and `textContent`. None of these functions are
* Shadow DOM aware. This mixin defines variations of those functions that
* *are* Shadow DOM aware.
*
* Example: you create a component `<count-children>` that displays a number
* equal to the number of children placed inside that component. If someone
* instantiates your component like:
*
* <count-children>
* <div></div>
* <div></div>
* <div></div>
* </count-children>
*
* Then the component should show "3", because there are three children. To
* calculate the number of children, the component can just calculate
* `this.children.length`. However, suppose someone instantiates your
* component inside one of their own components, and puts a `<slot>` element
* inside your component:
*
* <count-children>
* <slot></slot>
* </count-children>
*
* If your component only looks at `this.children`, it will always see exactly
* one child — the `<slot>` element. But the user looking at the page will
* *see* any nodes distributed to that slot. To match what the user sees, your
* component should expand any `<slot>` elements it contains.
*
* That is the problem this mixin solves. After applying this mixin, your
* component code has access to `this.distributedChildren`, whose `length`
* will return the total number of all children distributed to your component
* in the composed tree.
*
* Note: The latest Custom Elements API design calls for a new function,
* `getAssignedNodes` that takes an optional `deep` parameter, that will solve
* this problem at the API level.
*/
get distributedChildren() {
return expandContentElements(this.children, false);
}
class DistributedChildren extends base {
/*
* Returns an in-order collection of child nodes, expanding any content nodes.
* Like the standard childNodes property, this includes text nodes.
*/
get distributedChildNodes() {
return expandContentElements(this.childNodes, true);
}
/**
* An in-order collection of children, expanding any slot elements. Like the
* standard children property, this skips text nodes.
*
* @type {HTMLElement[]}
*/
get distributedChildren() {
return expandContentElements(this.children, false);
}
/*
* Returns the concatenated text content of all child nodes, expanding any
* content nodes.
*/
get distributedTextContent() {
let strings = this.distributedChildNodes.map(function(child) {
return child.textContent;
});
return strings.join('');
/**
* An in-order collection of child nodes, expanding any slot elements. Like
* the standard childNodes property, this includes text nodes.
*
* @type {Node[]}
*/
get distributedChildNodes() {
return expandContentElements(this.childNodes, true);
}
/**
* The concatenated text content of all child nodes, expanding any slot
* elements.
*
* @type {string}
*/
get distributedTextContent() {
let strings = this.distributedChildNodes.map(function(child) {
return child.textContent;
});
return strings.join('');
}
}
return DistributedChildren;
};

@@ -42,0 +90,0 @@

@@ -1,24 +0,32 @@

/**
* @class DistributedChildrenAsContent
* @classdesc Mixin which defines a component's content as its children,
* including any nodes distributed to the component's slots.
*/
/* Exported function extends a base class with DistributedChildrenAsContent. */
export default (base) => {
export default (base) => class DistributedChildrenAsContent extends base {
/**
* The content of this component, defined to be the flattened array of
* children distributed to the component.
* Mixin which defines a component's content as its children, expanding any
* nodes distributed to the component's slots.
*
* @property content
* @type Array
* This mixin is intended for use with the DistributedChildren mixin. See that
* mixin for a discussion of how that works. This DistributedChildrenAsContent
* mixin provides an easy way of defining the "content" of a component as the
* component's distributed children. That in turn lets mixins like
* ContentAsItems manipulate the children as list items.
*/
get content() {
return this.distributedChildren;
class DistributedChildrenAsContent extends base {
/**
* The content of this component, defined to be the flattened array of
* children distributed to the component.
*
* @type {HTMLElement[]}
*/
get content() {
return this.distributedChildren;
}
set content(value) {
if ('content' in base.prototype) { super.content = value; }
}
}
set content(value) {
if ('content' in base.prototype) { super.content = value; }
}
return DistributedChildrenAsContent;
};

@@ -1,69 +0,71 @@

/**
* @class Generic
* @classdesc Mixin that allows a component to support a "generic" style: a
* minimalist style that can easily be removed to reset its visual appearance to
* a baseline state
*
* By default, a component should provide a minimal visual presentation that
* allows the component to function. However, the more styling the component
* provides by default, the harder it becomes to get the component to fit in
* in other settings. Each CSS rule has to be overridden. Worse, new CSS rules
* added to the default style won't be overridden by default, making it hard to
* know whether a new version of a component will still look okay.
*
* As a compromise, the simple Polymer behavior here defines a "generic"
* attribute. This attribute is normally set by default, and styles can be
* written that apply only when the generic attribute is set. This allows the
* construction of CSS rules that will only apply to generic components like
*
* :host([generic=""]) {
* ...
* }
*
* This makes it easy to remove all default styling -- set the generic attribute
* to false, and all default styling will be removed.
*/
/* Exported function extends a base class with Generic. */
export default (base) => {
export default (base) => class Generic extends base {
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
this.generic = this.getAttribute('generic') || true;
}
/**
* True if the component would like to receive generic styling.
* Mixin which allows a component to support a "generic" style: a minimalist
* style that can easily be removed to reset its visual appearance to a
* baseline state.
*
* This property is true by default — set it to false to turn off all
* generic styles. This makes it easier to apply custom styling; you won't
* have to explicitly override styling you don't want.
* By default, a component should provide a minimal visual presentation that
* allows the component to function. However, the more styling the component
* provides by default, the harder it becomes to get the component to fit in
* in other settings. Each CSS rule has to be overridden. Worse, new CSS rules
* added to the default style won't be overridden by default, making it hard
* to know whether a new version of a component will still look okay.
*
* @property generic
* @type Boolean
* @default true
* As a compromise, the mixin defines a `generic` attribute. This attribute is
* normally set by default, and styles can be written that apply only when the
* generic attribute is set. This allows the construction of CSS rules that
* will only apply to generic components like:
*
* :host([generic=""]) {
* ... Generic appearance defined here ...
* }
*
* This makes it easy to remove all default styling — set the `generic`
* attribute to false, and all default styling will be removed.
*/
get generic() {
return this._generic;
}
set generic(value) {
if ('generic' in base.prototype) { super.generic = value; }
// We roll our own attribute setting so that an explicitly false value shows
// up as generic="false".
if (typeof value === 'string') {
value = (value !== 'false');
class Generic extends base {
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
this.generic = this.getAttribute('generic') || true;
}
this._generic = value;
if (value === false) {
// Explicitly use false string.
this.setAttribute('generic', 'false');
} else if (value == null) {
// Explicitly remove attribute.
this.removeAttribute('generic');
} else {
// Use the empty string to get attribute to appear with no value.
this.setAttribute('generic', '');
/**
* True if the component would like to receive generic styling.
*
* This property is true by default — set it to false to turn off all
* generic styles. This makes it easier to apply custom styling; you won't
* have to explicitly override styling you don't want.
*
* @type Boolean
* @default true
*/
get generic() {
return this._generic;
}
set generic(value) {
if ('generic' in base.prototype) { super.generic = value; }
// We roll our own attribute setting so that an explicitly false value
// shows up as generic="false".
if (typeof value === 'string') {
value = (value !== 'false');
}
this._generic = value;
if (value === false) {
// Explicitly use false string.
this.setAttribute('generic', 'false');
} else if (value == null) {
// Explicitly remove attribute.
this.removeAttribute('generic');
} else {
// Use the empty string to get attribute to appear with no value.
this.setAttribute('generic', '');
}
}
}
return Generic;
};

@@ -1,203 +0,243 @@

/**
* @class ItemsSelection
* @classdesc Mixin which manages selection semantics for items in a list
*/
/* Exported function extends a base class with ItemsSelection. */
export default (base) => {
/**
* Mixin which manages single-selection semantics for items in a list.
*
* This mixin expects a component to provide an `items` array of all elements
* in the list. A standard way to do that with is the ContentAsItems mixin,
* which takes a component's content (typically its distributed children) as
* the set of list items; see that mixin for details.
*
* This mixin tracks a single selected item in the list, and provides means to
* get and set that state by item position (`selectedIndex`) or item identity
* (`selectedItem`). The selection can be moved in the list via the methods
* `selectFirst`, `selectLast`, `selectNext`, and `selectPrevious`.
*
* This mixin does not produce any user-visible effects to represent
* selection. Other mixins, such as SelectionAriaActive, SelectionHighlight
* and SelectionInView, modify the selected item in common ways to let the
* user know a given item is selected or not selected.
*/
class ItemsSelection extends base {
/**
* Fires when the selectedItem property changes.
*
* @event selected-item-changed
* @param detail.selectedItem The new selected item.
* @param detail.previousItem The previously selected item.
*/
/**
* Apply the indicate selection state to the item.
*
* The default implementation of this method does nothing. User-visible
* effects will typically be handled by other mixins.
*
* @param {HTMLElement} item - the item being selected/deselected
* @param {boolean} selected - true if the item is selected, false if not
*/
applySelection(item, selected) {
if (super.applySelection) { super.applySelection(item, selected); }
}
/**
* Fires when the selectedIndex property changes.
*
* @event selected-item-changed
* @param detail.selectedIndex The new selected index.
*/
/**
* True if the selection can be moved to the next item, false if not (the
* selected item is the last item in the list).
*
* @type {boolean}
*/
get canSelectNext() {
return this._canSelectNext;
}
set canSelectNext(canSelectNext) {
if ('canSelectNext' in base.prototype) { super.canSelectNext = canSelectNext; }
this._canSelectNext = canSelectNext;
}
export default (base) => class ItemsSelection extends base {
/**
* True if the selection can be moved to the previous item, false if not
* (the selected item is the first one in the list).
*
* @type {boolean}
*/
get canSelectPrevious() {
return this._canSelectPrevious;
}
set canSelectPrevious(canSelectPrevious) {
if ('canSelectPrevious' in base.prototype) { super.canSelectPrevious = canSelectPrevious; }
this._canSelectPrevious = canSelectPrevious;
}
// Default implementation. This will typically be handled by other mixins.
applySelection(item, selected) {
if (super.applySelection) { super.applySelection(item, selected); }
}
/**
* Handle a new item being added to the list.
*
* The default implementation of this method simply sets the item's
* selection state to false.
*
* @param {HTMLElement} item - the item being added
*/
itemAdded(item) {
if (super.itemAdded) { super.itemAdded(item); }
this.applySelection(item, item === this.selectedItem);
}
get canSelectNext() {
return this._canSelectNext;
}
set canSelectNext(canSelectNext) {
if ('canSelectNext' in base.prototype) { super.canSelectNext = canSelectNext; }
this._canSelectNext = canSelectNext;
}
itemsChanged() {
if (super.itemsChanged) { super.itemsChanged(); }
let index = this.items.indexOf(this.selectedItem);
if (index < 0) {
// Selected item is no longer in the current set of items.
this.selectedItem = null;
if (this.selectionRequired) {
// Ensure selection, but do this in the next tick to give other
// mixins a chance to do their own itemsChanged work.
setTimeout(function() {
ensureSelection(this);
}.bind(this));
}
}
get canSelectPrevious() {
return this._canSelectPrevious;
}
set canSelectPrevious(canSelectPrevious) {
if ('canSelectPrevious' in base.prototype) { super.canSelectPrevious = canSelectPrevious; }
this._canSelectPrevious = canSelectPrevious;
}
// The change in items may have affected which navigations are possible.
updatePossibleNavigations(this, index);
}
itemAdded(item) {
if (super.itemAdded) { super.itemAdded(item); }
this.applySelection(item, item === this.selectedItem);
}
/**
* The index of the item which is currently selected, or -1 if there is no
* selection.
*
* Setting the index to -1 deselects any current-selected item.
*
* @type {number}
*/
get selectedIndex() {
let selectedItem = this.selectedItem;
itemsChanged() {
if (super.itemsChanged) { super.itemsChanged(); }
let index = this.items.indexOf(this.selectedItem);
if (index < 0) {
// Selected item is no longer in the current set of items.
this.selectedItem = null;
if (this.selectionRequired) {
// Ensure selection, but do this in the next tick to give other
// mixins a chance to do their own itemsChanged work.
setTimeout(function() {
ensureSelection(this);
}.bind(this));
if (selectedItem == null) {
return -1;
}
}
// The change in items may have affected which navigations are possible.
updatePossibleNavigations(this, index);
}
// TODO: Memoize
let index = this.indexOfItem(selectedItem);
/**
* The index of the item which is currently selected, or -1 if there is no
* selection.
*
* @property selectedIndex
* @type Number
*/
get selectedIndex() {
let selectedItem = this.selectedItem;
// If index = -1, selection wasn't found. Most likely cause is that the
// DOM was manipulated from underneath us.
// TODO: Once we track content changes, turn this into an exception.
return index;
}
set selectedIndex(index) {
if ('selectedIndex' in base.prototype) { super.selectedIndex = index; }
let items = this.items;
let item;
if (index < 0 || items.length === 0) {
item = null;
} else {
item = items[index];
}
this.selectedItem = item;
if (selectedItem == null) {
return -1;
let event = new CustomEvent('selected-index-changed', {
detail: {
selectedIndex: index,
value: index // for Polymer binding
}
});
this.dispatchEvent(event);
}
// TODO: Memoize
let index = this.indexOfItem(selectedItem);
// If index = -1, selection wasn't found. Most likely cause is that the
// DOM was manipulated from underneath us.
// TODO: Once we track content changes, turn this into an exception.
return index;
}
set selectedIndex(index) {
if ('selectedIndex' in base.prototype) { super.selectedIndex = index; }
let items = this.items;
let item;
if (index < 0 || items.length === 0) {
item = null;
} else {
item = items[index];
/**
* The currently selected item, or null if there is no selection.
*
* Setting this property to null deselects any currently-selected item.
*
* @type {object}
*/
get selectedItem() {
return this._selectedItem || null;
}
this.selectedItem = item;
set selectedItem(item) {
if ('selectedItem' in base.prototype) { super.selectedItem = item; }
let previousItem = this._selectedItem;
if (previousItem) {
// Remove previous selection.
this.applySelection(previousItem, false);
}
let event = new CustomEvent('selected-index-changed', {
detail: {
selectedIndex: index,
value: index // for Polymer binding
// TODO: Confirm item is actually in the list before selecting.
this._selectedItem = item;
if (item) {
this.applySelection(item, true);
}
});
this.dispatchEvent(event);
}
/**
* The currently selected item, or null if there is no selection.
*
* @property selectedItem
* @type Object
*/
// TODO: Confirm item is in items before selecting.
get selectedItem() {
return this._selectedItem || null;
}
set selectedItem(item) {
if ('selectedItem' in base.prototype) { super.selectedItem = item; }
let previousItem = this._selectedItem;
if (previousItem) {
// Remove previous selection.
this.applySelection(previousItem, false);
// TODO: Rationalize with selectedIndex so we're not recalculating item
// or index in each setter.
let index = this.indexOfItem(item);
updatePossibleNavigations(this, index);
let event = new CustomEvent('selected-item-changed', {
detail: {
selectedItem: item,
previousItem: previousItem,
value: item // for Polymer binding
}
});
this.dispatchEvent(event);
}
this._selectedItem = item;
if (item) {
this.applySelection(item, true);
/**
* Select the first item in the list.
*/
selectFirst() {
if (super.selectFirst) { super.selectFirst(); }
return selectIndex(this, 0);
}
// TODO: Rationalize with selectedIndex so we're not recalculating item
// or index in each setter.
let index = this.indexOfItem(item);
updatePossibleNavigations(this, index);
/**
* True if the list should always have a selection (if it has items).
*
* @type {boolean}
*/
get selectionRequired() {
return this._selectionRequired;
}
set selectionRequired(selectionRequired) {
if ('selectionRequired' in base.prototype) { super.selectionRequired = selectionRequired; }
this._selectionRequired = selectionRequired;
ensureSelection(this);
}
let event = new CustomEvent('selected-item-changed', {
detail: {
selectedItem: item,
previousItem: previousItem,
value: item // for Polymer binding
}
});
this.dispatchEvent(event);
}
/**
* Select the last item in the list.
*/
selectLast() {
if (super.selectLast) { super.selectLast(); }
return selectIndex(this, this.items.length - 1);
}
/**
* Select the first item in the list.
*
* @method selectFirst
*/
selectFirst() {
if (super.selectFirst) { super.selectFirst(); }
return selectIndex(this, 0);
}
/**
* Select the next item in the list.
*/
selectNext() {
if (super.selectNext) { super.selectNext(); }
return selectIndex(this, this.selectedIndex + 1);
}
/**
* True if the list should always have a selection (if it has items).
*
* @property selectionRequired
* @type Boolean
*/
get selectionRequired() {
return this._selectionRequired;
}
set selectionRequired(selectionRequired) {
if ('selectionRequired' in base.prototype) { super.selectionRequired = selectionRequired; }
this._selectionRequired = selectionRequired;
ensureSelection(this);
}
/**
* Select the previous item in the list.
*/
selectPrevious() {
if (super.selectPrevious) { super.selectPrevious(); }
return selectIndex(this, this.selectedIndex - 1);
}
/**
* Select the last item in the list.
*
* @method selectLast
*/
selectLast() {
if (super.selectLast) { super.selectLast(); }
return selectIndex(this, this.items.length - 1);
}
/**
* Fires when the selectedItem property changes.
*
* @event selected-item-changed
* @param {HTMLElement} detail.selectedItem The new selected item.
* @param {HTMLElement} detail.previousItem The previously selected item.
*/
/**
* Select the next item in the list.
*
* @method selectNext
*/
selectNext() {
if (super.selectNext) { super.selectNext(); }
return selectIndex(this, this.selectedIndex + 1);
}
/**
* Fires when the selectedIndex property changes.
*
* @event selected-item-changed
* @param {number} detail.selectedIndex The new selected index.
*/
/**
* Select the previous item in the list.
*
* @method selectPrevious
*/
selectPrevious() {
if (super.selectPrevious) { super.selectPrevious(); }
return selectIndex(this, this.selectedIndex - 1);
}
return ItemsSelection;
};

@@ -204,0 +244,0 @@

@@ -1,45 +0,97 @@

/**
* @class Keyboard
* @classdesc Mixin which manages the keydown handling for a component
*
* TODO: Document collective behavior.
* TODO: Provide baseline behavior outside of a collective.
*/
// TODO: Provide baseline behavior for this mixin when used outside of a
// collective.
export default (base) => class Keyboard extends base {
/* Exported function extends a base class with Keyboard. */
export default (base) => {
// Default keydown handler. This will typically be handled by other mixins.
keydown(event) {
if (super.keydown) { return super.keydown(event); }
}
/*
* If we're now the outermost element of the collective, set up to receive
* keyboard events. If we're no longer the outermost element, stop listening.
/**
* Mixin which manages the keydown handling for a component.
*
* This mixin handles several keyboard-related features.
*
* First, it wires up a single keydown event handler that can be shared by
* multiple mixins on a component. The event handler will invoke a `keydown`
* method with the event object, and any mixin along the prototype chain that
* wants to handle that method can do so.
*
* If a mixin wants to indicate that keyboard event has been handled, and that
* other mixins should *not* handle it, the mixin's `keydown` handler should
* return a value of true. The convention that seems to work well is that a
* mixin should see if it wants to handle the event and, if not, then ask the
* superclass to see if it wants to handle the event. This has the effect of
* giving the mixin that was applied last the first chance at handling a
* keyboard event.
*
* Example:
*
* keydown(event) {
* let handled;
* switch (event.keyCode) {
* // Handle the keys you want, setting handled = true if appropriate.
* }
* // Prefer mixin result if it's defined, otherwise use base result.
* return handled || (super.keydown && super.keydown(event));
* }
*
* A second feature provided by this mixin is that it implicitly makes the
* component a tab stop if it isn't already, by setting `tabIndex` to 0. This
* has the effect of adding the component to the tab order in document order.
*
* Finally, this mixin is designed to work with Collective class via a mixin
* like TargetInCollective. This allows a set of related component instances
* to cooperatively handle the keyboard. See the Collective class for details.
*
* NOTE: For the time being, this mixin should be used with
* TargetInCollective. The intention is to allow this mixin to be used without
* requiring collective keyboard support, so that this mixin can be used on
* its own.
*/
collectiveChanged() {
if (super.collectiveChanged) { super.collectiveChanged(); }
class Keyboard extends base {
if (this.collective.outermostElement !== this) {
// We're no longer the outermost element; stop listening.
if (isListeningToKeydown(this)) {
stopListeningToKeydown(this);
}
return;
/**
* Handle the indicated keyboard event.
*
* The default implementation of this method does nothing. This will
* typically be handled by other mixins.
*
* @param {KeyboardEvent} event - the keyboard event
* @return {boolean} true if the event was handled
*/
keydown(event) {
if (super.keydown) { return super.keydown(event); }
}
if (!this.getAttribute('aria-label')) {
// Since we're going to handle the keyboard, see if we can adopt an ARIA
// label from an inner element of the collective.
let label = getCollectiveAriaLabel(this.collective);
if (label) {
this.setAttribute('aria-label', label);
/*
* If we're now the outermost element of the collective, set up to receive
* keyboard events. If we're no longer the outermost element, stop
* listening.
*/
collectiveChanged() {
if (super.collectiveChanged) { super.collectiveChanged(); }
if (this.collective.outermostElement !== this) {
// We're no longer the outermost element; stop listening.
if (isListeningToKeydown(this)) {
stopListeningToKeydown(this);
}
return;
}
if (!this.getAttribute('aria-label')) {
// Since we're going to handle the keyboard, see if we can adopt an ARIA
// label from an inner element of the collective.
let label = getCollectiveAriaLabel(this.collective);
if (label) {
this.setAttribute('aria-label', label);
}
}
if (!isListeningToKeydown(this)) {
startListeningToKeydown(this);
}
}
if (!isListeningToKeydown(this)) {
startListeningToKeydown(this);
}
}
return Keyboard;
};

@@ -46,0 +98,0 @@

@@ -1,62 +0,102 @@

/**
* @class KeyboardDirection
* @classdesc Mixin which maps direction keys (Left, Right, etc.) to direction
* semantics (goLeft, goRight, etc.)
*/
/* Exported function extends a base class with KeyboardDirection. */
export default (base) => {
/**
* Mixin which maps direction keys (Left, Right, etc.) to direction semantics
* (go left, go right, etc.).
*
* This mixin expects the component to invoke a `keydown` method when a key is
* pressed. You can use the Keyboard mixin for that purpose, or wire up your
* own keyboard handling and call `keydown` yourself.
*
* This mixin calls methods such as `goLeft` and `goRight`. You can define
* what that means by implementing those methods yourself. If you want to use
* direction keys to navigate a selection, use this mixin with the
* DirectionSelection mixin.
*/
class KeyboardDirection extends base {
export default (base) => class KeyboardDirection extends base {
/**
* Invoked when the user wants to go/navigate down.
* The default implementation of this method does nothing.
*/
goDown() {
if (super.goDown) { return super.goDown(); }
}
// Default implementations. These will typically be handled by other mixins.
goDown() {
if (super.goDown) { return super.goDown(); }
}
goEnd() {
if (super.goEnd) { return super.goEnd(); }
}
goLeft() {
if (super.goLeft) { return super.goLeft(); }
}
goRight() {
if (super.goRight) { return super.goRight(); }
}
goStart() {
if (super.goStart) { return super.goStart(); }
}
goUp() {
if (super.goUp) { return super.goUp(); }
}
/**
* Invoked when the user wants to go/navigate to the end (e.g., of a list).
* The default implementation of this method does nothing.
*/
goEnd() {
if (super.goEnd) { return super.goEnd(); }
}
keydown(event) {
let handled;
// Ignore Left/Right keys when metaKey or altKey modifier is also pressed,
// as the user may be trying to navigate back or forward in the browser.
switch (event.keyCode) {
case 35: // End
handled = this.goEnd();
break;
case 36: // Home
handled = this.goStart();
break;
case 37: // Left
if (!event.metaKey && !event.altKey) {
handled = this.goLeft();
}
break;
case 38: // Up
handled = event.altKey ? this.goStart() : this.goUp();
break;
case 39: // Right
if (!event.metaKey && !event.altKey) {
handled = this.goRight();
}
break;
case 40: // Down
handled = event.altKey ? this.goEnd() : this.goDown();
break;
/**
* Invoked when the user wants to go/navigate left.
* The default implementation of this method does nothing.
*/
goLeft() {
if (super.goLeft) { return super.goLeft(); }
}
// Prefer mixin result if it's defined, otherwise use base result.
return handled || (super.keydown && super.keydown(event));
/**
* Invoked when the user wants to go/navigate right.
* The default implementation of this method does nothing.
*/
goRight() {
if (super.goRight) { return super.goRight(); }
}
/**
* Invoked when the user wants to go/navigate to the start (e.g., of a
* list). The default implementation of this method does nothing.
*/
goStart() {
if (super.goStart) { return super.goStart(); }
}
/**
* Invoked when the user wants to go/navigate up.
* The default implementation of this method does nothing.
*/
goUp() {
if (super.goUp) { return super.goUp(); }
}
keydown(event) {
let handled;
// Ignore Left/Right keys when metaKey or altKey modifier is also pressed,
// as the user may be trying to navigate back or forward in the browser.
switch (event.keyCode) {
case 35: // End
handled = this.goEnd();
break;
case 36: // Home
handled = this.goStart();
break;
case 37: // Left
if (!event.metaKey && !event.altKey) {
handled = this.goLeft();
}
break;
case 38: // Up
handled = event.altKey ? this.goStart() : this.goUp();
break;
case 39: // Right
if (!event.metaKey && !event.altKey) {
handled = this.goRight();
}
break;
case 40: // Down
handled = event.altKey ? this.goEnd() : this.goDown();
break;
}
// Prefer mixin result if it's defined, otherwise use base result.
return handled || (super.keydown && super.keydown(event));
}
}
return KeyboardDirection;
};

@@ -1,72 +0,75 @@

/**
* @class KeyboardPagedSelection
* @classdesc Mixin which maps page keys (Page Up, Page Down) into operations
* that move the selection by one page
*
* The keyboard interaction model generally follows that of Microsoft Windows'
* list boxes instead of those in OS X:
*
* * The Page Up/Down and Home/End keys actually change the selection, rather
* than just scrolling. The former behavior seems more generally useful for
* keyboard users.
*
* * Pressing Page Up/Down will change the selection to the topmost/bottommost
* visible item if the selection is not already there. Thereafter, the key
* will move the selection up/down by a page, and (per the above point) make
* the selected item visible.
*
* To ensure the selected item is in view following use of Page Up/Down, use the
* related SelectionInView mixin.
*/
/* Exported function extends a base class with KeyboardPagedSelection. */
export default (base) => {
/**
* Mixin which maps page keys (Page Up, Page Down) into operations that move
* the selection by one page.
*
* The keyboard interaction model generally follows that of Microsoft Windows'
* list boxes instead of those in OS X:
*
* * The Page Up/Down and Home/End keys actually change the selection, rather
* than just scrolling. The former behavior seems more generally useful for
* keyboard users.
*
* * Pressing Page Up/Down will change the selection to the topmost/bottommost
* visible item if the selection is not already there. Thereafter, the key
* will move the selection up/down by a page, and (per the above point) make
* the selected item visible.
*
* To ensure the selected item is in view following use of Page Up/Down, use
* the related SelectionInView mixin.
*
* This mixin expects the component to invoke a `keydown` method when a key is
* pressed. You can use the Keyboard mixin for that purpose, or wire up your
* own keyboard handling and call `keydown` yourself.
*/
class KeyboardPagedSelection extends base {
export default (base) => class KeyboardPagedSelection extends base {
keydown(event) {
let handled;
switch (event.keyCode) {
case 33: // Page Up
handled = this.pageUp();
break;
case 34: // Page Down
handled = this.pageDown();
break;
}
// Prefer mixin result if it's defined, otherwise use base result.
return handled || (super.keydown && super.keydown(event));
}
keydown(event) {
let handled;
switch (event.keyCode) {
case 33: // Page Up
handled = this.pageUp();
break;
case 34: // Page Down
handled = this.pageDown();
break;
/**
* Scroll down one page.
*/
pageDown() {
if (super.pageDown) { super.pageDown(); }
return scrollOnePage(this, true);
}
// Prefer mixin result if it's defined, otherwise use base result.
return handled || (super.keydown && super.keydown(event));
}
/**
* Scroll down one page.
*
* @method pageDown
*/
pageDown() {
if (super.pageDown) { super.pageDown(); }
return scrollOnePage(this, true);
}
/**
* Scroll up one page.
*/
pageUp() {
if (super.pageUp) { super.pageUp(); }
return scrollOnePage(this, false);
}
/**
* Scroll up one page.
*
* @method pageUp
*/
pageUp() {
if (super.pageUp) { super.pageUp(); }
return scrollOnePage(this, false);
/**
* The element that should be scrolled with the Page Up/Down keys.
* Default is the current element.
*
* @type {HTMLElement}
*/
get scrollTarget() {
// Prefer base result.
return 'scrollTarget' in base.prototype ? super.scrollTarget : this;
}
set scrollTarget(element) {
if ('scrollTarget' in base.prototype) { super.scrollTarget = element; }
}
}
/**
* The element that should be scrolled with the Page Up/Down keys.
* Default is the current element.
*
* @property scrollTarget
*/
get scrollTarget() {
// Prefer base result.
return 'scrollTarget' in base.prototype ? super.scrollTarget : this;
}
set scrollTarget(element) {
if ('scrollTarget' in base.prototype) { super.scrollTarget = element; }
}
return KeyboardPagedSelection;
};

@@ -73,0 +76,0 @@

@@ -1,64 +0,99 @@

/**
* @class KeyboardPrefixSelection
* @classdesc Mixin that handles list box-style prefix typing, in which the user
* can type a string to select the first item that begins with that string
*/
// TODO: If the selection is changed by some other means (e.g., arrow keys)
// other than prefix typing, then that act should reset the prefix.
/* Exported function extends a base class with KeyboardPrefixSelection. */
export default (base) => {
// TODO: If the selection is changed by some other means (e.g., arrow keys) other
// than prefix typing, then that act should reset the prefix.
/**
* Mixin that handles list box-style prefix typing, in which the user can type
* a string to select the first item that begins with that string.
*
* Example: suppose a component using this mixin has the following items:
*
* <sample-list-component>
* <div>Apple</div>
* <div>Apricot</div>
* <div>Banana</div>
* <div>Blackberry</div>
* <div>Blueberry</div>
* <div>Cantaloupe</div>
* <div>Cherry</div>
* <div>Lemon</div>
* <div>Lime</div>
* </sample-list-component>
*
* If this component receives the focus, and the user presses the "b" or "B"
* key, the "Banana" item will be selected, because it's the first item that
* matches the prefix "b". (Matching is case-insensitive.) If the user now
* presses the "l" or "L" key quickly, the prefix to match becomes "bl", so
* "Blackberry" will be selected.
*
* The prefix typing feature has a one second timeout — the prefix to match
* will be reset after a second has passed since the user last typed a key.
* If, in the above example, the user waits a second between typing "b" and
* "l", the prefix will become "l", so "Lemon" would be selected.
*
* This mixin expects the component to invoke a `keydown` method when a key is
* pressed. You can use the Keyboard mixin for that purpose, or wire up your
* own keyboard handling and call `keydown` yourself.
*
* This mixin also expects the component to provide an `items` property. The
* `textContent` of those items will be used for purposes of prefix matching.
*/
class KeyboardPrefixSelection extends base {
export default (base) => class KeyboardPrefixSelection extends base {
// TODO: If the set of items is changed, reset the prefix.
// itemsChanged() {
// this._itemTextContents = null;
// resetTypedPrefix(this);
// }
// itemsChanged() {
// this._itemTextContents = null;
// resetTypedPrefix(this);
// }
keydown(event) {
let handled;
let resetPrefix = true;
keydown(event) {
let handled;
let resetPrefix = true;
switch (event.keyCode) {
case 8: // Backspace
handleBackspace(this);
handled = true;
resetPrefix = false;
break;
case 27: // Escape
handled = true;
break;
default:
if (!event.ctrlKey && !event.metaKey && !event.altKey &&
event.which !== 32 /* Space */) {
handlePlainCharacter(this, String.fromCharCode(event.which));
}
resetPrefix = false;
}
switch (event.keyCode) {
case 8: // Backspace
handleBackspace(this);
handled = true;
resetPrefix = false;
break;
case 27: // Escape
handled = true;
break;
default:
if (!event.ctrlKey && !event.metaKey && !event.altKey &&
event.which !== 32 /* Space */) {
handlePlainCharacter(this, String.fromCharCode(event.which));
}
resetPrefix = false;
if (resetPrefix) {
resetTypedPrefix(this);
}
// Prefer mixin result if it's defined, otherwise use base result.
return handled || (super.keydown && super.keydown(event));
}
if (resetPrefix) {
resetTypedPrefix(this);
/**
* Select the first item whose text content begins with the given prefix.
*
* @param prefix [String] The prefix string to search for
*/
selectItemWithTextPrefix(prefix) {
if (super.selectItemWithTextPrefix) { super.selectItemWithTextPrefix(prefix); }
if (prefix == null || prefix.length === 0) {
return;
}
let index = getIndexOfItemWithTextPrefix(this, prefix);
if (index >= 0) {
this.selectedIndex = index;
}
}
// Prefer mixin result if it's defined, otherwise use base result.
return handled || (super.keydown && super.keydown(event));
}
/**
* Select the first item whose text content begins with the given prefix.
*
* @method selectItemWithTextPrefix
* @param prefix [String] The string to search for
*/
selectItemWithTextPrefix(prefix) {
if (super.selectItemWithTextPrefix) { super.selectItemWithTextPrefix(prefix); }
if (prefix == null || prefix.length === 0) {
return;
}
let index = getIndexOfItemWithTextPrefix(this, prefix);
if (index >= 0) {
this.selectedIndex = index;
}
}
return KeyboardPrefixSelection;
};

@@ -65,0 +100,0 @@

@@ -27,5 +27,11 @@ /*

/**
* Add a callback to the microtask queue.
*
* This uses a MutationObserver so that it works on IE 11.
*
* NOTE: IE 11 may actually use timeout timing with MutationObservers. This
* needs more investigation.
*
* @function microtask
*
* Adds a function to the microtask queue.
* @param {function} callback
*/

@@ -32,0 +38,0 @@ export default function microtask(callback) {

@@ -1,47 +0,68 @@

/**
* @class ObserveContentChanges
* @classdesc Wires up mutation observers to report any changes in a component's
* content (direct children, or nodes distributed to slots).
*
* For the time being, this can only support a single level of distributed
* content. That is, if a component contains a slot, and the set of nodes
* directly assigned to that slot changes, then this mixin will detect the
* change. However, this mixin does not yet detect changes in reprojected
* nodes. If a component's host places a slot as a child of the component
* instance, nodes assigned to the outer host will be assigned to the host's
* slot, then reassigned to the slot element inside the component. Changes in
* such reprojected nodes will not (yet) be detected by this mixin.
*
* For comparison, see Polymer's observeNodes API, which does solve the problem
* of tracking changes in reprojected content.
*/
import microtask from './microtask';
// TODO: Should this be renamed to something like DistributedChildrenChanged?
import microtask from './microtask';
/* Exported function extends a base class with ObserveContentChanges. */
export default (base) => {
/**
* Mixin which wires up mutation observers to report any changes in a
* component's content (direct children, or nodes distributed to slots).
*
* For the time being, this can only support a single level of distributed
* content. That is, if a component contains a slot, and the set of nodes
* directly assigned to that slot changes, then this mixin will detect the
* change. However, this mixin does not yet detect changes in reprojected
* nodes. If a component's host places a slot as a child of the component
* instance, nodes assigned to the outer host will be assigned to the host's
* slot, then reassigned to the slot element inside the component. Changes in
* such reprojected nodes will not (yet) be detected by this mixin.
*
* For comparison, see Polymer's observeNodes API, which does solve the
* problem of tracking changes in reprojected content.
*
* Note: The web platform team creating the specifications for web components
* plan to request that a new type of MutationObserver option be defined that
* lets a component monitor changes in distributed children. This mixin will
* be updated to take advantage of that MutationObserver option when that
* becomes available.
*/
class ObserveContentChanges extends base {
// TODO: Don't respond to changes in attributes, or at least offer that as an
// option.
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
observeContentChanges(this);
export default (base) => class ObserveContentChanges extends base {
// Make an initial call to contentChanged() so that the component can do
// initialization that it normally does when content changes.
//
// This will invoke contentChanged() handlers in other mixins. In order
// that those mixins have a chance to complete their own initialization,
// we add the contentChanged() call to the microtask queue.
microtask(() => this.contentChanged());
}
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
observeContentChanges(this);
/**
* Invoked when the contents of the component (including distributed
* children) have changed.
*
* This method is also invoked when a component is first instantiated; the
* contents have essentially "changed" from being nothing. This allows the
* component to perform initial processing of its children.
*/
contentChanged() {
if (super.contentChanged) { super.contentChanged(); }
let event = new CustomEvent('content-changed');
this.dispatchEvent(event);
}
// Make an initial call to contentChanged() so that the component can do
// initialization that it normally does when content changes.
//
// This will invoke contentChanged() handlers in other mixins. In order that
// those mixins have a chance to complete their own initialization, we add
// the contentChanged() call to the microtask queue.
microtask(() => this.contentChanged());
/**
* This event is raised when the component's contents (including distributed
* children) have changed.
*
* @event content-changed
*/
}
contentChanged() {
if (super.contentChanged) { super.contentChanged(); }
let event = new CustomEvent('content-changed');
this.dispatchEvent(event);
}
return ObserveContentChanges;
};

@@ -48,0 +69,0 @@

@@ -1,16 +0,1 @@

/**
* @class SelectionAriaActive
* @classdesc Mixin which treats the selected item in a list as the active item
* in ARIA accessibility terms
*
* Handling ARIA selection state properly is actually quite complex. Not only
* does the selected item need to be marked as selected; the other items should
* be marked as *not* selected. Additionally, the outermost element with the
* keyboard focus needs to have attributes set on it so that the selection is
* knowable at the list level. That in turn requires that all items in the list
* have ID attributes assigned to them. (To that end, this mixin will assign
* generated IDs to any item that doesn't already have an ID.)
*/
// Used to assign unique IDs to item elements without IDs.

@@ -20,81 +5,122 @@ let idCount = 0;

export default (base) => class SelectionAriaActive extends base {
/* Exported function extends a base class with SelectionAriaActive. */
export default (base) => {
applySelection(item, selected) {
if (super.applySelection) { super.applySelection(item, selected); }
item.setAttribute('aria-selected', selected);
let itemId = item.getAttribute('id');
if (itemId) {
this.collective.outermostElement.setAttribute('aria-activedescendant', itemId);
/**
* Mixin which treats the selected item in a list as the active item in ARIA
* accessibility terms.
*
* Handling ARIA selection state properly is actually quite complex:
*
* * The items in the list need to be indicated as possible items via an ARIA
* `role` attribute value such as "option".
* * The selected item need to be marked as selected by setting the item's
* `aria-selected` attribute to true *and* the other items need be marked as
* *not* selected by setting `aria-selected` to false.
* * The outermost element with the keyboard focus needs to have attributes
* set on it so that the selection is knowable at the list level via the
* `aria-activedescendant` attribute.
* * Use of `aria-activedescendant` in turn requires that all items in the
* list have ID attributes assigned to them.
*
* This mixin tries to address all of the above requirements. To that end,
* this mixin will assign generated IDs to any item that doesn't already have
* an ID.
*
* ARIA relies on elements to provide `role` attributes. This mixin will apply
* a default role of "listbox" on the outer list if it doesn't already have an
* explicit role. Similarly, this mixin will apply a default role of "option"
* to any list item that does not already have a role specified.
*
* This mixin expects a set of members that manage the state of the selection:
* `applySelection`, `itemAdded`, and `selectedIndex`. You can supply these
* yourself, or do so via the ItemsSelection mixin.
*
* NOTE: For the time being, this mixin should be used with the
* TargetInCollective mixin. The intention is to eventually allow this mixin
* to be used without requiring collective keyboard support, so that this
* mixin can be used on its own.
*/
class SelectionAriaActive extends base {
applySelection(item, selected) {
if (super.applySelection) { super.applySelection(item, selected); }
item.setAttribute('aria-selected', selected);
let itemId = item.getAttribute('id');
if (itemId) {
this.collective.outermostElement.setAttribute('aria-activedescendant', itemId);
}
}
}
collectiveChanged() {
if (super.collectiveChanged) { super.collectiveChanged(); }
collectiveChanged() {
if (super.collectiveChanged) { super.collectiveChanged(); }
// Ensure the outermost aspect has an ARIA role.
let outermostElement = this.collective.outermostElement;
if (!outermostElement.getAttribute('role')) {
// Try to promote an ARIA role from an inner element. If none is found,
// use a default role.
let role = getCollectiveAriaRole(this.collective) || 'listbox';
outermostElement.setAttribute('role', role);
}
if (!outermostElement.getAttribute('aria-activedescendant')) {
// Try to promote an ARIA activedescendant value from an inner element.
let descendant = getCollectiveAriaActiveDescendant(this.collective);
if (descendant) {
outermostElement.setAttribute('aria-activedescendant', descendant);
// Ensure the outermost aspect has an ARIA role.
let outermostElement = this.collective.outermostElement;
if (!outermostElement.getAttribute('role')) {
// Try to promote an ARIA role from an inner element. If none is found,
// use a default role.
let role = getCollectiveAriaRole(this.collective) || 'listbox';
outermostElement.setAttribute('role', role);
}
if (!outermostElement.getAttribute('aria-activedescendant')) {
// Try to promote an ARIA activedescendant value from an inner element.
let descendant = getCollectiveAriaActiveDescendant(this.collective);
if (descendant) {
outermostElement.setAttribute('aria-activedescendant', descendant);
}
}
// Remove the ARIA role and activedescendant values from the collective's
// inner elements.
this.collective.elements.forEach(element => {
if (element !== outermostElement) {
element.removeAttribute('aria-activedescendant');
element.removeAttribute('role');
}
});
}
// Remove the ARIA role and activedescendant values from the collective's
// inner elements.
this.collective.elements.forEach(element => {
if (element !== outermostElement) {
element.removeAttribute('aria-activedescendant');
element.removeAttribute('role');
}
});
}
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
// Determine a base item ID based on this component's host's own ID. This
// will be combined with a unique integer to assign IDs to items that
// don't have an explicit ID. If the basic-list-box has ID "foo", then its
// items will have IDs that look like "_fooOption1". If the list has no ID
// itself, its items will get IDs that look like "_option1". Item IDs are
// prefixed with an underscore to differentiate them from
// manually-assigned IDs, and to minimize the potential for ID conflicts.
let elementId = this.getAttribute( "id" );
this.itemBaseId = elementId ?
"_" + elementId + "Option" :
"_option";
}
// Determine a base item ID based on this component's host's own ID. This
// will be combined with a unique integer to assign IDs to items that don't
// have an explicit ID. If the basic-list-box has ID "foo", then its items
// will have IDs that look like "_fooOption1". If the list has no ID itself,
// its items will get IDs that look like "_option1". Item IDs are prefixed
// with an underscore to differentiate them from manually-assigned IDs, and
// to minimize the potential for ID conflicts.
let elementId = this.getAttribute( "id" );
this.itemBaseId = elementId ?
"_" + elementId + "Option" :
"_option";
}
itemAdded(item) {
if (super.itemAdded) { super.itemAdded(item); }
itemAdded(item) {
if (super.itemAdded) { super.itemAdded(item); }
item.setAttribute('role', 'option');
item.setAttribute('role', 'option');
// Ensure each item has an ID so we can set aria-activedescendant on the
// overall list whenever the selection changes.
if (!item.getAttribute('id')) {
item.setAttribute('id', this.itemBaseId + idCount++);
}
}
// Ensure each item has an ID so we can set aria-activedescendant on the
// overall list whenever the selection changes.
if (!item.getAttribute('id')) {
item.setAttribute('id', this.itemBaseId + idCount++);
get selectedItem() {
return super.selectedItem;
}
}
set selectedItem(item) {
if ('selectedItem' in base.prototype) { super.selectedItem = item; }
// Catch the case where the selection is removed.
if (item == null && this.collective) {
this.collective.outermostElement.removeAttribute('aria-activedescendant');
}
}
get selectedItem() {
return super.selectedItem;
}
set selectedItem(item) {
if ('selectedItem' in base.prototype) { super.selectedItem = item; }
// Catch the case where the selection is removed.
if (item == null && this.collective) {
this.collective.outermostElement.removeAttribute('aria-activedescendant');
}
}
return SelectionAriaActive;
};

@@ -101,0 +127,0 @@

@@ -1,15 +0,27 @@

/**
* @class SelectionHighlight
* @classdesc Mixin which applies standard highlight colors to a selected item
*/
/* Exported function extends a base class with SelectionHighlight. */
export default (base) => {
export default (base) => class SelectionHighlight extends base {
/**
* Mixin which applies standard highlight colors to a selected item.
*
* This mixin highlights textual items (e.g., in a list) in a standard way by
* using the CSS `highlight` and `highlighttext` color values. These values
* respect operating system defaults and user preferences, and hence are good
* default values for highlight colors.
*
* This mixin expects an `applySelection` method to be called on an item when
* its selected state changes. You can use the ItemsSelection mixin for that
* purpose.
*/
class SelectionHighlight extends base {
applySelection(item, selected) {
if (super.applySelection) { super.applySelection(item, selected); }
item.style.backgroundColor = selected ? 'highlight' : '';
item.style.color = selected ? 'highlighttext' : '';
applySelection(item, selected) {
if (super.applySelection) { super.applySelection(item, selected); }
item.style.backgroundColor = selected ? 'highlight' : '';
item.style.color = selected ? 'highlighttext' : '';
}
}
return SelectionHighlight;
};

@@ -1,65 +0,77 @@

/**
* @class SelectionInView
* @classdesc Mixin which scrolls a container to keep the selected item visible
*/
/* Exported function extends a base class with SelectionInView. */
export default (base) => {
export default (base) => class SelectionInView extends base {
get selectedItem() {
return super.selectedItem;
}
set selectedItem(item) {
if ('selectedItem' in base.prototype) { super.selectedItem = item; }
if (item) {
// Keep the selected item in view.
this.scrollItemIntoView(item);
}
}
/**
* Scroll the given element completely into view, minimizing the degree of
* scrolling performed.
* Mixin which scrolls a container to ensure that a newly-selected item is
* visible to the user.
*
* Blink has a scrollIntoViewIfNeeded() function that almost the same thing,
* but unfortunately it's non-standard, and in any event often ends up
* scrolling more than is absolutely necessary.
* When the selected item in a list-like component changes, it's easier for
* the to confirm that the selection has changed to an appropriate item if the
* user can actually see that item.
*
* @method scrollItemIntoView
* This mixin expects a `selectedItem` property to be set when the selection
* changes. You can supply that yourself, or use the ItemsSelection mixin.
*/
scrollItemIntoView(item) {
if (super.scrollItemIntoView) { super.scrollItemIntoView(); }
// Get the relative position of the item with respect to the top of the
// list's scrollable canvas. An item at the top of the list will have a
// elementTop of 0.
class SelectionInView extends base {
let scrollTarget = this.scrollTarget;
let elementTop = item.offsetTop - scrollTarget.offsetTop - scrollTarget.clientTop;
let elementBottom = elementTop + item.offsetHeight;
// Determine the bottom of the scrollable canvas.
let scrollBottom = scrollTarget.scrollTop + scrollTarget.clientHeight;
if (elementBottom > scrollBottom) {
// Scroll up until item is entirely visible.
scrollTarget.scrollTop += elementBottom - scrollBottom;
get selectedItem() {
return super.selectedItem;
}
else if (elementTop < scrollTarget.scrollTop) {
// Scroll down until item is entirely visible.
scrollTarget.scrollTop = elementTop;
set selectedItem(item) {
if ('selectedItem' in base.prototype) { super.selectedItem = item; }
if (item) {
// Keep the selected item in view.
this.scrollItemIntoView(item);
}
}
}
/**
* The element that should be scrolled with the Page Up/Down keys.
* Default is the current element.
*
* @property scrollTarget
*/
get scrollTarget() {
// Prefer base result.
return 'scrollTarget' in base.prototype ? super.scrollTarget : this;
/**
* Scroll the given element completely into view, minimizing the degree of
* scrolling performed.
*
* Blink has a `scrollIntoViewIfNeeded()` function that does something
* similar, but unfortunately it's non-standard, and in any event often ends
* up scrolling more than is absolutely necessary.
*
* @param {HTMLElement} item - the item to scroll into view.
*/
scrollItemIntoView(item) {
if (super.scrollItemIntoView) { super.scrollItemIntoView(); }
// Get the relative position of the item with respect to the top of the
// list's scrollable canvas. An item at the top of the list will have a
// elementTop of 0.
let scrollTarget = this.scrollTarget;
let elementTop = item.offsetTop - scrollTarget.offsetTop - scrollTarget.clientTop;
let elementBottom = elementTop + item.offsetHeight;
// Determine the bottom of the scrollable canvas.
let scrollBottom = scrollTarget.scrollTop + scrollTarget.clientHeight;
if (elementBottom > scrollBottom) {
// Scroll up until item is entirely visible.
scrollTarget.scrollTop += elementBottom - scrollBottom;
}
else if (elementTop < scrollTarget.scrollTop) {
// Scroll down until item is entirely visible.
scrollTarget.scrollTop = elementTop;
}
}
/**
* The element that should be scrolled to bring an item into view.
*
* The default value of this property is the element itself.
*
* @type {HTMLElement}
*/
get scrollTarget() {
// Prefer base result.
return 'scrollTarget' in base.prototype ? super.scrollTarget : this;
}
set scrollTarget(element) {
if ('scrollTarget' in base.prototype) { super.scrollTarget = element; }
}
}
set scrollTarget(element) {
if ('scrollTarget' in base.prototype) { super.scrollTarget = element; }
}
return SelectionInView;
};

@@ -1,35 +0,47 @@

/**
* @class ShadowElementReferences
* @classdesc Mixin to create references to elements in a component's Shadow
* DOM subtree
*
* This adds a member on the component called `$` that can be used to reference
* shadow elements with IDs. E.g., if component's shadow contains an element
* `<button id="foo">`, then this mixin will create a member `this.$.foo` that
* points to that button. Such references simplify a component's access to its
* own elements.
*
* This trades off a one-time cost of querying all elements in the shadow tree
* against having to query for an element each time the component wants to
* inspect or manipulate it.
*
* This mixin is inspired by Polymer's automatic node finding feature.
* See https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#node-finding.
*/
/* Exported function extends a base class with ShadowElementReferences. */
export default (base) => {
/**
* Mixin to create references to elements in a component's Shadow DOM subtree.
*
* This adds a member on the component called `this.$` that can be used to
* reference shadow elements with IDs. E.g., if component's shadow contains an
* element `<button id="foo">`, then this mixin will create a member
* `this.$.foo` that points to that button.
*
* Such references simplify a component's access to its own elements. In
* exchange, this mixin trades off a one-time cost of querying all elements in
* the shadow tree instead of paying an ongoing cost to query for an element
* each time the component wants to inspect or manipulate it.
*
* This mixin expects the component to define a Shadow DOM subtree. You can
* create that tree yourself, or make use of the ShadowTemplate mixin.
*
* This mixin is inspired by Polymer's [automatic
* node finding](https://www.polymer-project.org/1.0/docs/devguide/local-dom.html#node-finding)
* feature.
*/
class ShadowElementReferences extends base {
export default (base) => class ShadowElementReferences extends base {
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
if (this.shadowRoot) {
// Look for elements in the shadow subtree that have id attributes.
// An alternatively implementation of this mixin would be to just define
// a this.$ getter that lazily does this search the first time someone
// tries to access this.$. That might introduce some complexity – if the
// the tree changed after it was first populated, the result of
// searching for a node might be somewhat unpredictable.
this.$ = {};
let nodesWithIds = this.shadowRoot.querySelectorAll('[id]');
[].forEach.call(nodesWithIds, node => {
let id = node.getAttribute('id');
this.$[id] = node;
});
}
}
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
if (this.shadowRoot) {
this.$ = {};
let nodesWithIds = this.shadowRoot.querySelectorAll('[id]');
[].forEach.call(nodesWithIds, node => {
let id = node.getAttribute('id');
this.$[id] = node;
});
}
}
return ShadowElementReferences;
};

@@ -1,16 +0,1 @@

/**
* @class ShadowTemplate
* @classdesc Mixin for stamping a template into a Shadow DOM subtree upon
* component instantiation
*
* If a component defines a template property (as a string or referencing a HTML
* template), when the component class is instantiated, a shadow root will be
* created on the instance, and the contents of the template will be cloned into
* the shadow root.
*
* For the time being, this extension retains support for Shadow DOM v0.
* That will eventually be deprecated as browsers implement Shadow DOM v1.
*/
// Feature detection for old Shadow DOM v0.

@@ -20,37 +5,65 @@ const USING_SHADOW_DOM_V0 = (typeof HTMLElement.prototype.createShadowRoot !== 'undefined');

export default (base) => class ShadowTemplate extends base {
/* Exported function extends a base class with ShadowTemplate. */
export default (base) => {
/*
* If the component defines a template, a shadow root will be created on the
* component instance, and the template stamped into it.
/**
* Mixin for stamping a template into a Shadow DOM subtree upon component
* instantiation.
*
* To use this mixin, define a `template` property as a string or HTML
* `<template>` element:
*
* class MyElement extends ShadowTemplate(HTMLElement) {
* get template() {
* return `Hello, <em>world</em>.`;
* }
* }
*
* When your component class is instantiated, a shadow root will be created on
* the instance, and the contents of the template will be cloned into the
* shadow root. If your component does not define a `template` property, this
* mixin has no effect.
*
* For the time being, this extension retains support for Shadow DOM v0. That
* will eventually be deprecated as browsers (and the Shadow DOM polyfill)
* implement Shadow DOM v1.
*/
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
let template = this.template;
// TODO: Save the processed template with the component's class prototype
// so it doesn't need to be processed with every instantiation.
if (template) {
class ShadowTemplate extends base {
if (typeof template === 'string') {
// Upgrade plain string to real template.
template = createTemplateWithInnerHTML(template);
}
/*
* If the component defines a template, a shadow root will be created on the
* component instance, and the template stamped into it.
*/
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
let template = this.template;
// TODO: Save the processed template with the component's class prototype
// so it doesn't need to be processed with every instantiation.
if (template) {
if (USING_SHADOW_DOM_V0) {
polyfillSlotWithContent(template);
}
if (typeof template === 'string') {
// Upgrade plain string to real template.
template = createTemplateWithInnerHTML(template);
}
if (window.ShadowDOMPolyfill) {
shimTemplateStyles(template, this.localName);
if (USING_SHADOW_DOM_V0) {
polyfillSlotWithContent(template);
}
if (window.ShadowDOMPolyfill) {
shimTemplateStyles(template, this.localName);
}
// this.log("cloning template into shadow root");
let root = USING_SHADOW_DOM_V0 ?
this.createShadowRoot() : // Shadow DOM v0
this.attachShadow({ mode: 'open' }); // Shadow DOM v1
let clone = document.importNode(template.content, true);
root.appendChild(clone);
}
}
// this.log("cloning template into shadow root");
let root = USING_SHADOW_DOM_V0 ?
this.createShadowRoot() : // Shadow DOM v0
this.attachShadow({ mode: 'open' }); // Shadow DOM v1
let clone = document.importNode(template.content, true);
root.appendChild(clone);
}
}
return ShadowTemplate;
};

@@ -57,0 +70,0 @@

@@ -1,78 +0,106 @@

/**
* @class SwipeDirection
* @classdesc Mixin which maps touch gestures (swipe left, swipe right) to direction
* semantics (goRight, goLeft)
*/
/* Exported function extends a base class with SwipeDirection. */
export default (base) => {
/**
* Mixin which maps touch gestures (swipe left, swipe right) to direction
* semantics (go right, go left).
*
* By default, this mixin presents no user-visible effects; it just indicates a
* direction in which the user is currently swiping or has finished swiping. To
* map the direction to a change in selection, use the DirectionSelection mixin.
*/
class SwipeDirection extends base {
export default (base) => class SwipeDirection extends base {
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
this.position = 0;
this.position = 0;
// TODO: Touch events could be factored out into its own mixin.
// TODO: Touch events could be factored out into its own mixin.
// In all touch events, only handle single touches. We don't want to
// inadvertently do work when the user's trying to pinch-zoom for example.
// TODO: Even better approach than below would be to ignore touches after
// the first if the user has already begun a swipe.
this.addEventListener('touchstart', event => {
if (this._multiTouch) {
return;
} else if (event.touches.length === 1) {
touchStart(this, event);
} else {
this._multiTouch = true;
}
});
this.addEventListener('touchmove', event => {
if (!this._multiTouch && event.touches.length === 1) {
let handled = touchMove(this, event);
if (handled) {
event.preventDefault();
// In all touch events, only handle single touches. We don't want to
// inadvertently do work when the user's trying to pinch-zoom for example.
// TODO: Even better approach than below would be to ignore touches after
// the first if the user has already begun a swipe.
this.addEventListener('touchstart', event => {
if (this._multiTouch) {
return;
} else if (event.touches.length === 1) {
touchStart(this, event);
} else {
this._multiTouch = true;
}
}
});
this.addEventListener('touchend', event => {
if (event.touches.length === 0) {
// All touches removed; gesture is complete.
if (!this._multiTouch) {
// Single-touch swipe has finished.
touchEnd(this, event);
});
this.addEventListener('touchmove', event => {
if (!this._multiTouch && event.touches.length === 1) {
let handled = touchMove(this, event);
if (handled) {
event.preventDefault();
}
}
this._multiTouch = false;
}
});
}
});
this.addEventListener('touchend', event => {
if (event.touches.length === 0) {
// All touches removed; gesture is complete.
if (!this._multiTouch) {
// Single-touch swipe has finished.
touchEnd(this, event);
}
this._multiTouch = false;
}
});
}
// Default implementations
goLeft() {
if (super.goLeft) { return super.goLeft(); }
}
goRight() {
if (super.goRight) { return super.goRight(); }
}
/**
* Invoked when the user wants to go/navigate left.
* The default implementation of this method does nothing.
*/
goLeft() {
if (super.goLeft) { return super.goLeft(); }
}
/**
* The distance the user has moved the first touchpoint since the beginning
* of a drag, expressed as a fraction of the element's width.
*
* @property position
* @type Number
*/
get position() {
return this._position;
}
set position(position) {
if ('position' in base.prototype) { super.position = position; }
this._position = position;
}
/**
* Invoked when the user wants to go/navigate right.
* The default implementation of this method does nothing.
*/
goRight() {
if (super.goRight) { return super.goRight(); }
}
// Default implementation
showTransition(value) {
if (super.showTransition) { super.showTransition(value); }
/**
* The distance the user has moved the first touchpoint since the beginning
* of a drag, expressed as a fraction of the element's width.
*
* @type number
*/
get position() {
return this._position;
}
set position(position) {
if ('position' in base.prototype) { super.position = position; }
this._position = position;
}
/**
* Determine whether a transition should be shown during a swipe.
*
* Components like carousels often define animated CSS transitions for
* sliding effects. Such a transition should usually *not* be applied while
* the user is dragging, because a CSS animation will introduce a lag that
* makes the swipe feel sluggish. Instead, as long as the user is dragging
* with their finger down, the transition should be suppressed. When the
* user releases their finger, the transition can be restored, allowing the
* animation to show the carousel sliding into its final position.
*
* @param {boolean} value - true if a component-provided transition should
* be shown, false if not.
*/
// TODO: Rename (and flip meaning) to something like dragging()?
showTransition(value) {
if (super.showTransition) { super.showTransition(value); }
}
}
return SwipeDirection;
};

@@ -79,0 +107,0 @@

@@ -1,25 +0,49 @@

/**
* @class TargetInCollective
* @classdesc Mixin which allows a component to provide aggregate behavior with
* other elements, e.g., for keyboard handling
*/
import Collective from './Collective';
import Collective from './Collective';
/* Exported function extends a base class with TargetInCollective. */
export default (base) => {
export default (base) => class TargetInCollective extends base {
/**
* Mixin which allows a component to provide aggregate behavior with other
* elements, e.g., for keyboard handling.
*
* This mixin implicitly creates a collective for a component so that it can
* participate in collective keyboard handling. See the Collective class for
* details.
*
* You can use this mixin in conjunction with ContentFirstChildTarget to
* automatically have the component's collective extended to its first child.
*/
class TargetInCollective extends base {
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
this.collective = new Collective(this);
}
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
this.collective = new Collective(this);
}
get target() {
return super.target;
/**
* Gets/sets the current target of the component.
*
* Set this to point to another element. That target element will be
* implicitly added to the component's collective. That is, the component
* and its target will share responsibility for handling keyboard events.
*
* You can set this property yourself, or you can use the
* ContentFirstChildTarget mixin to automatically set the target to the
* component's first child.
*
* @type {HTMLElement}
*/
get target() {
return super.target;
}
set target(element) {
if ('target' in base.prototype) { super.target = element; }
this.collective.assimilate(element);
}
}
set target(element) {
if ('target' in base.prototype) { super.target = element; }
this.collective.assimilate(element);
}
return TargetInCollective;
};

@@ -1,111 +0,152 @@

/**
* @class TargetSelection
* @classdesc Mixin that allows a component to delegate its own selection
* semantics to a target element
*
* This is useful when defining components that act as optional decorators for a
* component that acts like a list.
*/
/* Exported function extends a base class with TargetSelection. */
export default (base) => {
/**
* Mixin which allows a component to delegate its own selection semantics to a
* target element.
*
* This is useful when defining components that act as optional features for a
* component that acts like a list. See basic-arrow-selection and
* basic-page-dots for examples of components used as optional features for
* components like basic-carousel. A typical usage might be:
*
* <basic-arrow-selection>
* <basic-carousel>
* ... images, etc. ...
* </basic-carousel>
* </basic-arrow-selection>
*
* Because basic-arrow-selection uses the TargetSelection mixin, it exposes
* members to access a selection: `selectNext`, `selectPrevious`,
* `selectedIndex`, etc. These are all delegated to the child component (here,
* a basic-carousel).
*
* This mixin expects a `target` property to be set to the element actually
* managing the selection. You can set that property yourself, or you can use
* the ContentFirstChildTarget mixin to implicitly take the component's first
* child as the target. This is what basic-arrow-selection (above) does.
*/
class TargetSelection extends base {
export default (base) => class TargetSelection extends base {
// attachedCallback() {
// // Apply any selection made before assimilation.
// if (this._prematureSelectedIndex
// && 'selectedIndex' in this && this.selectedIndex === -1) {
// this.selectedIndex = this._prematureSelectedIndex;
// this._prematureSelectedIndex = null;
// }
// }
// attachedCallback() {
// // Apply any selection made before assimilation.
// if (this._prematureSelectedIndex
// && 'selectedIndex' in this && this.selectedIndex === -1) {
// this.selectedIndex = this._prematureSelectedIndex;
// this._prematureSelectedIndex = null;
// }
// }
/**
* Return the positional index for the indicated item.
*
* @param {HTMLElement} item The item whose index is requested.
* @returns {number} The index of the item, or -1 if not found.
*/
indexOfItem(item) {
if (super.indexOfItem) { super.indexOfItem(item); }
let target = this.target;
return target ?
target.indexOfItem(item) :
-1;
}
indexOfItem(item) {
if (super.indexOfItem) { super.indexOfItem(item); }
let target = this.target;
return target ?
target.indexOfItem(item) :
-1;
}
/**
* The current set of items in the list.
*
* @type {HTMLElement[]}
*/
get items() {
let target = this.target;
let items = target && target.items;
return items || [];
}
get items() {
let target = this.target;
let items = target && target.items;
return items || [];
}
/**
* This method is invoked when the underlying contents change. It is also
* invoked on component initialization – since the items have "changed" from
* being nothing.
*/
itemsChanged() {
if (super.itemsChanged) { super.itemsChanged(); }
this.dispatchEvent(new CustomEvent('items-changed'));
}
itemsChanged() {
if (super.itemsChanged) { super.itemsChanged(); }
this.dispatchEvent(new CustomEvent('items-changed'));
}
/**
* The index of the item which is currently selected, or -1 if there is no
* selection.
*
* @type {number}
*/
get selectedIndex() {
let target = this.target;
return target && target.selectedIndex;
}
set selectedIndex(index) {
if ('selectedIndex' in base.prototype) { super.selectedIndex = index; }
// if ('selectedIndex' in this {
// this.selectedIndex = index;
// } else {
// // Selection is being made before the collective supports it.
// this._prematureSelectedIndex = index;
// }
let target = this.target;
if (target && target.selectedIndex !== index) {
target.selectedIndex = index;
}
}
/**
* The index of the item which is currently selected, or -1 if there is no
* selection.
*
* @property selectedIndex
* @type Number
*/
get selectedIndex() {
let target = this.target;
return target && target.selectedIndex;
}
set selectedIndex(index) {
if ('selectedIndex' in base.prototype) { super.selectedIndex = index; }
// if ('selectedIndex' in this {
// this.selectedIndex = index;
// } else {
// // Selection is being made before the collective supports it.
// this._prematureSelectedIndex = index;
// }
let target = this.target;
if (target && target.selectedIndex !== index) {
target.selectedIndex = index;
/**
* The currently selected item, or null if there is no selection.
*
* @type {HTMLElement}
*/
get selectedItem() {
let target = this.target;
return target && target.selectedItem;
}
}
set selectedItem(item) {
if ('selectedItem' in base.prototype) { super.selectedItem = item; }
let target = this.target;
if (target) {
target.selectedItem = item;
}
}
/**
* The currently selected item, or null if there is no selection.
*
* @property selectedItem
* @type Object
*/
get selectedItem() {
let target = this.target;
return target && target.selectedItem;
}
set selectedItem(item) {
if ('selectedItem' in base.prototype) { super.selectedItem = item; }
let target = this.target;
if (target) {
target.selectedItem = item;
selectedItemChanged() {
if (super.selectedItemChanged) { super.selectedItemChanged(); }
}
}
selectedItemChanged() {
if (super.selectedItemChanged) { super.selectedItemChanged(); }
}
get target() {
return super.target;
}
set target(element) {
if ('target' in base.prototype) { super.target = element; }
if (this._itemsChangedListener) {
this.removeEventListener('items-changed', this._itemsChangedListener);
/**
* Gets/sets the target element to which this component will delegate
* selection actions.
*
* @type {HTMLElement}
*/
get target() {
return super.target;
}
if (this._selectedItemChangedListener) {
this.removeEventListener('selected-item-changed', this._selectedItemChangedListener);
set target(element) {
if ('target' in base.prototype) { super.target = element; }
if (this._itemsChangedListener) {
this.removeEventListener('items-changed', this._itemsChangedListener);
}
if (this._selectedItemChangedListener) {
this.removeEventListener('selected-item-changed', this._selectedItemChangedListener);
}
this._itemsChangedListener = element.addEventListener('items-changed', event => {
this.itemsChanged();
});
this._selectedItemChangedListener = element.addEventListener('selected-item-changed', event => {
// Let the component know the target's selection changed, but without
// re-invoking the selectIndex/selectedItem setter.
this.selectedItemChanged();
});
// Force initial refresh.
this.itemsChanged();
}
this._itemsChangedListener = element.addEventListener('items-changed', event => {
this.itemsChanged();
});
this._selectedItemChangedListener = element.addEventListener('selected-item-changed', event => {
// Let the component know the target's selection changed, but without
// re-invoking the selectIndex/selectedItem setter.
this.selectedItemChanged();
});
// Force initial refresh.
this.itemsChanged();
}
return TargetSelection;
};

@@ -1,72 +0,80 @@

/**
* @class TimerSelection
* @classdesc Mixin provides for automatic timed changes in selection, as in a
* automated slideshow
*/
/* Exported function extends a base class with TimerSelection. */
export default (base) => {
export default (base) => class TimerSelection extends base {
contentChanged() {
if (super.contentChanged) { super.contentChanged(); }
this.play();
}
/**
* Begin automatic progression of the selection.
* Mixin which provides for automatic timed changes in selection.
*
* @method play
*/
play() {
if (super.play) { super.play(); }
this._playing = true;
setTimer(this);
}
/**
* Pause automatic progression of the selection.
* This mixin is useful for creating slideshow-like elements.
*
* @method pause
* This mixin expects the component to define an `items` property, as well as
* `selectFirst` and `selectNext` methods. You can implement those yourself,
* or use the ContentAsItems and ItemsSelection mixins.
*/
pause() {
if (super.pause) { super.pause(); }
clearTimer(this);
this._playing = false;
}
class TimerSelection extends base {
/**
* True if the selection is being automatically advanced.
*
* @property playing
* @type Boolean
*/
get playing() {
return this._playing;
}
set playing(playing) {
if ('playing' in base.prototype) { super.playing = playing; }
if (playing && !this._playing) {
contentChanged() {
if (super.contentChanged) { super.contentChanged(); }
this.play();
} else if (!playing && this._playing) {
this.pause();
}
}
// Whether the user has selected an item manually, or we've automatically
// advanced the selection, we wait for a bit before advancing again.
get selectedItem() {
return super.selectedItem;
}
set selectedItem(item) {
if ('selectedItem' in base.prototype) { super.selectedItem = item; }
clearTimer(this);
if (this.playing) {
/**
* Begin automatic progression of the selection.
*/
play() {
if (super.play) { super.play(); }
this._playing = true;
setTimer(this);
}
/**
* Pause automatic progression of the selection.
*/
pause() {
if (super.pause) { super.pause(); }
clearTimer(this);
this._playing = false;
}
/**
* True if the selection is being automatically advanced.
*
* @type {boolean}
*/
get playing() {
return this._playing;
}
set playing(playing) {
if ('playing' in base.prototype) { super.playing = playing; }
if (playing && !this._playing) {
this.play();
} else if (!playing && this._playing) {
this.pause();
}
}
/*
* When the selected item changes (because of something this mixin did,
* or was changed by an outside agent like the user), we wait a bit before
* advancing to the next item. By triggering the next item this way,
* we implicitly get a desirable behavior: if the user changes the selection
* (e.g., in a carousel), we let them see that selection state for a while
* before advancing the selection ourselves.
*/
get selectedItem() {
return super.selectedItem;
}
set selectedItem(item) {
if ('selectedItem' in base.prototype) { super.selectedItem = item; }
clearTimer(this);
if (this.playing) {
setTimer(this);
}
}
}
return TimerSelection;
};
function clearTimer(element) {

@@ -73,0 +81,0 @@ if (element._timeout) {

@@ -1,57 +0,78 @@

/**
* @class TrackpadDirection
* @classdesc Mixin which maps a horizontal trackpad swipe gestures (or
* horizontal mouse wheel actions) to direction semantics
*
* To respond to the trackpad, we can listen to the DOM's "wheel" events. These
* events are fired as the user drags their fingers across a trackpad.
* Unfortunately, this scheme is missing a critical event — there is no event
* when the user *stops* a gestured on the trackpad.
*
* To complicate matters, the mainstream browsers continue to generate wheel
* events even after the user has stopped dragging their fingers. These fake
* events simulate the user gradually slowing down the drag until they come to a
* smooth stop. In some contexts, these fake wheel events might be helpful, but
* in trying to supply typical trackpad swipe navigation, these fake events get
* in the way.
*
* This component uses some heuristics to work around these problems, but the
* complex nature of the problem make it extremely difficult to achieve the same
* degree of trackpad responsiveness possible with native applications.
*/
/* Exported function extends a base class with TrackpadDirection. */
export default (base) => {
/**
* Mixin which maps a horizontal trackpad swipe gestures (or horizontal mouse
* wheel actions) to direction semantics.
*
* You can use this mixin with a mixin like DirectionSelection to let the user
* change the selection with the trackpad or mouse wheel.
*
* To respond to the trackpad, we can listen to the DOM's "wheel" events.
* These events are fired as the user drags their fingers across a trackpad.
* Unfortunately, browsers are missing a critical event — there is no event
* when the user *stops* a gestured on the trackpad or mouse wheel.
*
* To make things worse, the mainstream browsers continue to generate fake
* wheel events even after the user has stopped dragging their fingers. These
* fake events simulate the user gradually slowing down the drag until they
* come to a smooth stop. In some contexts, these fake wheel events might be
* helpful, but in trying to supply typical trackpad swipe navigation, these
* fake events get in the way.
*
* This component uses heuristics to work around these problems, but the
* complex nature of the problem make it extremely difficult to achieve the
* same degree of trackpad responsiveness possible with native applications.
*/
class TrackpadDirection extends base {
export default (base) => class TrackpadDirection extends base {
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
this.addEventListener('wheel', event => {
let handled = wheel(this, event);
if (handled) {
event.preventDefault();
}
});
resetWheelTracking(this);
}
createdCallback() {
if (super.createdCallback) { super.createdCallback(); }
this.addEventListener('wheel', event => {
let handled = wheel(this, event);
if (handled) {
event.preventDefault();
}
});
resetWheelTracking(this);
}
/**
* Invoked when the user wants to go/navigate left.
* The default implementation of this method does nothing.
*/
goLeft() {
if (super.goLeft) { return super.goLeft(); }
}
// Default implementations
goLeft() {
if (super.goLeft) { return super.goLeft(); }
}
goRight() {
if (super.goRight) { return super.goRight(); }
}
/**
* Invoked when the user wants to go/navigate right.
* The default implementation of this method does nothing.
*/
goRight() {
if (super.goRight) { return super.goRight(); }
}
get position() {
return super.position;
}
set position(position) {
if ('position' in base.prototype) { super.position = position; }
}
/**
* The distance the user has moved the first touchpoint since the beginning
* of a trackpad/wheel operation, expressed as a fraction of the element's
* width.
*
* @type number
*/
get position() {
return super.position;
}
set position(position) {
if ('position' in base.prototype) { super.position = position; }
}
// Default implementation
showTransition(value) {
if (super.showTransition) { super.showTransition(value); }
// Default implementation
showTransition(value) {
if (super.showTransition) { super.showTransition(value); }
}
}
return TrackpadDirection;
};

@@ -58,0 +79,0 @@

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc