@vaadin/vaadin-list-mixin
Advanced tools
Comparing version 2.5.1 to 20.0.0-alpha1
{ | ||
"name": "@vaadin/vaadin-list-mixin", | ||
"version": "20.0.0-alpha1", | ||
"description": "vaadin-list-mixin", | ||
"main": "vaadin-list-mixin.js", | ||
"module": "vaadin-list-mixin.js", | ||
"repository": "vaadin/vaadin-list-mixin", | ||
"keywords": [ | ||
@@ -10,7 +15,2 @@ "Vaadin", | ||
], | ||
"repository": "vaadin/vaadin-list-mixin", | ||
"homepage": "https://vaadin.com/elements", | ||
"name": "@vaadin/vaadin-list-mixin", | ||
"version": "2.5.1", | ||
"main": "vaadin-list-mixin.js", | ||
"author": "Vaadin Ltd", | ||
@@ -21,2 +21,3 @@ "license": "Apache-2.0", | ||
}, | ||
"homepage": "https://vaadin.com/elements", | ||
"files": [ | ||
@@ -26,25 +27,16 @@ "*.d.ts", | ||
], | ||
"resolutions": { | ||
"es-abstract": "1.17.6", | ||
"@types/doctrine": "0.0.3", | ||
"inherits": "2.0.3", | ||
"samsam": "1.1.3", | ||
"supports-color": "3.1.2", | ||
"type-detect": "1.0.0" | ||
}, | ||
"dependencies": { | ||
"@polymer/polymer": "^3.0.0", | ||
"@vaadin/vaadin-element-mixin": "^2.4.1" | ||
"@vaadin/vaadin-element-mixin": "^20.0.0-alpha1" | ||
}, | ||
"scripts": { | ||
"generate-typings": "gen-typescript-declarations --outDir . --verify" | ||
}, | ||
"devDependencies": { | ||
"@esm-bundle/chai": "^4.1.5", | ||
"@open-wc/testing-helpers": "^1.8.12", | ||
"@polymer/iron-test-helpers": "^3.0.0", | ||
"wct-browser-legacy": "^1.0.1", | ||
"@webcomponents/webcomponentsjs": "^2.0.0", | ||
"@vaadin/vaadin-context-menu": "^4.3.13", | ||
"@vaadin/vaadin-select": "^2.1.6", | ||
"@vaadin/vaadin-tabs": "^3.0.5" | ||
} | ||
"sinon": "^9.2.4" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"gitHead": "93c8e0ec03a178c6d74261261f985bd07f7cc79c" | ||
} |
@@ -1,9 +0,8 @@ | ||
![Bower version](https://img.shields.io/bower/v/vaadin-list-mixin.svg) | ||
[![Published on webcomponents.org](https://img.shields.io/badge/webcomponents.org-published-blue.svg)](https://www.webcomponents.org/element/vaadin/vaadin-list-mixin) | ||
[![Build Status](https://travis-ci.org/vaadin/vaadin-list-mixin.svg?branch=master)](https://travis-ci.org/vaadin/vaadin-list-mixin) | ||
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/vaadin/web-components?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) | ||
# vaadin-list-mixin | ||
# vaadin-list-mixin | ||
`vaadin-list-mixin` is a mixin for `nav` elements, facilitating navigation and selection of childNodes. | ||
[![npm version](https://badgen.net/npm/v/@vaadin/vaadin-list-mixin)](https://www.npmjs.com/package/@vaadin/vaadin-list-mixin) | ||
[![Discord](https://img.shields.io/discord/732335336448852018?label=discord)](https://discord.gg/PHmkCKC) | ||
## Running tests in browser | ||
@@ -13,23 +12,17 @@ | ||
1. Make sure you have [npm](https://www.npmjs.com/) and [Bower](https://bower.io) installed. | ||
1. Make sure you have [npm](https://www.npmjs.com/). | ||
1. When in the `vaadin-list-mixin` directory, run `npm install` and then `bower install` to install dependencies. | ||
1. When in the `vaadin-list-mixin` directory, run `npm install` to install dependencies. | ||
1. Make sure you have [polymer-cli](https://www.npmjs.com/package/polymer-cli) installed globally: `npm i -g polymer-cli`. | ||
1. Run `npm test` to start the tests in Chrome. | ||
1. Run `npm start`. | ||
1. Navigate to http://127.0.0.1:3000/components/vaadin-list-mixin/test/index.html | ||
## Debugging tests in browser | ||
1. Run `npm run debug`, then choose manual mode (M) and open the link in browser. | ||
## Running tests from the command line | ||
1. Install [web-component-tester](https://www.npmjs.com/package/web-component-tester): `npm install -g web-component-tester` | ||
1. When in the `vaadin-list-mixin` directory, run `wct` or `npm test` | ||
## Following the coding style | ||
We are using [ESLint](http://eslint.org/) for linting JavaScript code. You can check if your code is following our standards by running `npm run lint`, which will automatically lint all `.js` files as well as JavaScript snippets inside `.html` files. | ||
We are using [ESLint](http://eslint.org/) for linting JavaScript code. You can check if your code is following our standards by running `npm run lint`, which will automatically lint all `.js` files. | ||
@@ -36,0 +29,0 @@ |
@@ -1,26 +0,3 @@ | ||
/** | ||
* DO NOT EDIT | ||
* | ||
* This file was automatically generated by | ||
* https://github.com/Polymer/tools/tree/master/packages/gen-typescript-declarations | ||
* | ||
* To modify these typings, edit the source file(s): | ||
* vaadin-list-mixin.js | ||
*/ | ||
import { ListOrientation } from './interfaces'; | ||
// tslint:disable:variable-name Describing an API that's defined elsewhere. | ||
// tslint:disable:no-any describes the API as best we are able today | ||
import {FlattenedNodesObserver} from '@polymer/polymer/lib/utils/flattened-nodes-observer.js'; | ||
import {DirHelper} from '@vaadin/vaadin-element-mixin/vaadin-dir-helper.js'; | ||
import {Debouncer} from '@polymer/polymer/lib/utils/debounce.js'; | ||
import {timeOut} from '@polymer/polymer/lib/utils/async.js'; | ||
export {ListMixin}; | ||
/** | ||
@@ -32,11 +9,12 @@ * A mixin for `nav` elements, facilitating navigation and selection of childNodes. | ||
interface ListMixinConstructor { | ||
new(...args: any[]): ListMixin; | ||
new (...args: any[]): ListMixin; | ||
} | ||
export {ListMixinConstructor}; | ||
interface ListMixin { | ||
readonly focused: Element | null; | ||
interface ListMixin { | ||
readonly focused: Element|null; | ||
readonly _isRTL: boolean; | ||
readonly _scrollerElement: HTMLElement; | ||
readonly _vertical: boolean; | ||
@@ -53,3 +31,3 @@ | ||
*/ | ||
selected: number|null|undefined; | ||
selected: number | null | undefined; | ||
@@ -74,12 +52,22 @@ /** | ||
*/ | ||
readonly items: Element[]|undefined; | ||
readonly items: Element[] | undefined; | ||
ready(): void; | ||
_filterItems(array: Element[]): Element[]; | ||
_onClick(event: MouseEvent): void; | ||
_searchKey(currentIdx: number, key: string): number; | ||
_onKeydown(event: KeyboardEvent): void; | ||
_getAvailableIndex(idx: number, increment: number, condition: (p0: Element) => boolean): number; | ||
_isItemHidden(item: Element): boolean; | ||
_setFocusable(idx: number): void; | ||
_focus(idx: number): void; | ||
focus(): void; | ||
@@ -91,5 +79,6 @@ | ||
_scrollToItem(idx: number): void; | ||
_scroll(pixels: number): void; | ||
} | ||
import {ListOrientation} from './interfaces'; | ||
export { ListMixin, ListMixinConstructor }; |
/** | ||
@license | ||
Copyright (c) 2017 Vaadin Ltd. | ||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
* @license | ||
* Copyright (c) 2021 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { FlattenedNodesObserver } from '@polymer/polymer/lib/utils/flattened-nodes-observer.js'; | ||
import { DirHelper } from '@vaadin/vaadin-element-mixin/vaadin-dir-helper.js'; | ||
@@ -17,346 +16,357 @@ import { Debouncer } from '@polymer/polymer/lib/utils/debounce.js'; | ||
*/ | ||
export const ListMixin = superClass => class VaadinListMixin extends superClass { | ||
static get properties() { | ||
return { | ||
/** | ||
* Used for mixin detection because `instanceof` does not work with mixins. | ||
* @type {boolean} | ||
*/ | ||
_hasVaadinListMixin: { | ||
value: true | ||
}, | ||
export const ListMixin = (superClass) => | ||
class VaadinListMixin extends superClass { | ||
static get properties() { | ||
return { | ||
/** | ||
* Used for mixin detection because `instanceof` does not work with mixins. | ||
* @type {boolean} | ||
*/ | ||
_hasVaadinListMixin: { | ||
value: true | ||
}, | ||
/** | ||
* The index of the item selected in the items array. | ||
* Note: Not updated when used in `multiple` selection mode. | ||
*/ | ||
selected: { | ||
type: Number, | ||
reflectToAttribute: true, | ||
notify: true | ||
}, | ||
/** | ||
* The index of the item selected in the items array. | ||
* Note: Not updated when used in `multiple` selection mode. | ||
*/ | ||
selected: { | ||
type: Number, | ||
reflectToAttribute: true, | ||
notify: true | ||
}, | ||
/** | ||
* Define how items are disposed in the dom. | ||
* Possible values are: `horizontal|vertical`. | ||
* It also changes navigation keys from left/right to up/down. | ||
* @type {!ListOrientation} | ||
*/ | ||
orientation: { | ||
type: String, | ||
reflectToAttribute: true, | ||
value: '' | ||
}, | ||
/** | ||
* Define how items are disposed in the dom. | ||
* Possible values are: `horizontal|vertical`. | ||
* It also changes navigation keys from left/right to up/down. | ||
* @type {!ListOrientation} | ||
*/ | ||
orientation: { | ||
type: String, | ||
reflectToAttribute: true, | ||
value: '' | ||
}, | ||
/** | ||
* The list of items from which a selection can be made. | ||
* It is populated from the elements passed to the light DOM, | ||
* and updated dynamically when adding or removing items. | ||
* | ||
* The item elements must implement `Vaadin.ItemMixin`. | ||
* | ||
* Note: unlike `<vaadin-combo-box>`, this property is read-only, | ||
* so if you want to provide items by iterating array of data, | ||
* you have to use `dom-repeat` and place it to the light DOM. | ||
* @type {!Array<!Element> | undefined} | ||
*/ | ||
items: { | ||
type: Array, | ||
readOnly: true, | ||
notify: true | ||
}, | ||
/** | ||
* The list of items from which a selection can be made. | ||
* It is populated from the elements passed to the light DOM, | ||
* and updated dynamically when adding or removing items. | ||
* | ||
* The item elements must implement `Vaadin.ItemMixin`. | ||
* | ||
* Note: unlike `<vaadin-combo-box>`, this property is read-only, | ||
* so if you want to provide items by iterating array of data, | ||
* you have to use `dom-repeat` and place it to the light DOM. | ||
* @type {!Array<!Element> | undefined} | ||
*/ | ||
items: { | ||
type: Array, | ||
readOnly: true, | ||
notify: true | ||
}, | ||
/** | ||
* The search buffer for the keyboard selection feature. | ||
* @private | ||
*/ | ||
_searchBuf: { | ||
type: String, | ||
value: '' | ||
} | ||
}; | ||
} | ||
/** | ||
* The search buffer for the keyboard selection feature. | ||
* @private | ||
*/ | ||
_searchBuf: { | ||
type: String, | ||
value: '' | ||
} | ||
}; | ||
} | ||
static get observers() { | ||
return ['_enhanceItems(items, orientation, selected, disabled)']; | ||
} | ||
static get observers() { | ||
return ['_enhanceItems(items, orientation, selected, disabled)']; | ||
} | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
this.addEventListener('keydown', e => this._onKeydown(e)); | ||
this.addEventListener('click', e => this._onClick(e)); | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
this.addEventListener('keydown', (e) => this._onKeydown(e)); | ||
this.addEventListener('click', (e) => this._onClick(e)); | ||
this._observer = new FlattenedNodesObserver(this, info => { | ||
this._setItems(this._filterItems(Array.from(this.children))); | ||
}); | ||
} | ||
this._observer = new FlattenedNodesObserver(this, () => { | ||
this._setItems(this._filterItems(Array.from(this.children))); | ||
}); | ||
} | ||
/** @private */ | ||
_enhanceItems(items, orientation, selected, disabled) { | ||
if (!disabled) { | ||
if (items) { | ||
this.setAttribute('aria-orientation', orientation || 'vertical'); | ||
this.items.forEach(item => { | ||
orientation ? item.setAttribute('orientation', orientation) : item.removeAttribute('orientation'); | ||
item.updateStyles(); | ||
}); | ||
/** @private */ | ||
_enhanceItems(items, orientation, selected, disabled) { | ||
if (!disabled) { | ||
if (items) { | ||
this.setAttribute('aria-orientation', orientation || 'vertical'); | ||
this.items.forEach((item) => { | ||
orientation ? item.setAttribute('orientation', orientation) : item.removeAttribute('orientation'); | ||
}); | ||
this._setFocusable(selected); | ||
this._setFocusable(selected); | ||
const itemToSelect = items[selected]; | ||
items.forEach(item => item.selected = item === itemToSelect); | ||
if (itemToSelect && !itemToSelect.disabled) { | ||
this._scrollToItem(selected); | ||
const itemToSelect = items[selected]; | ||
items.forEach((item) => (item.selected = item === itemToSelect)); | ||
if (itemToSelect && !itemToSelect.disabled) { | ||
this._scrollToItem(selected); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
/** | ||
* @return {Element} | ||
*/ | ||
get focused() { | ||
return this.getRootNode().activeElement; | ||
} | ||
/** | ||
* @param {!Array<!Element>} array | ||
* @return {!Array<!Element>} | ||
* @protected | ||
*/ | ||
_filterItems(array) { | ||
return array.filter(e => e._hasVaadinItemMixin); | ||
} | ||
/** | ||
* @param {!MouseEvent} event | ||
* @protected | ||
*/ | ||
_onClick(event) { | ||
if (event.metaKey || event.shiftKey || event.ctrlKey || event.defaultPrevented) { | ||
return; | ||
/** | ||
* @return {Element} | ||
*/ | ||
get focused() { | ||
return this.getRootNode().activeElement; | ||
} | ||
const item = this._filterItems(event.composedPath())[0]; | ||
let idx; | ||
if (item && !item.disabled && ((idx = this.items.indexOf(item)) >= 0)) { | ||
this.selected = idx; | ||
/** | ||
* @param {!Array<!Element>} array | ||
* @return {!Array<!Element>} | ||
* @protected | ||
*/ | ||
_filterItems(array) { | ||
return array.filter((e) => e._hasVaadinItemMixin); | ||
} | ||
} | ||
/** | ||
* @param {number} currentIdx | ||
* @param {string} key | ||
* @return {number} | ||
* @protected | ||
*/ | ||
_searchKey(currentIdx, key) { | ||
this._searchReset = Debouncer.debounce( | ||
this._searchReset, | ||
timeOut.after(500), | ||
() => this._searchBuf = '' | ||
); | ||
this._searchBuf += key.toLowerCase(); | ||
const increment = 1; | ||
const condition = item => !(item.disabled || this._isItemHidden(item)) && | ||
item.textContent.replace(/[^a-zA-Z0-9]/g, '').toLowerCase().indexOf(this._searchBuf) === 0; | ||
/** | ||
* @param {!MouseEvent} event | ||
* @protected | ||
*/ | ||
_onClick(event) { | ||
if (event.metaKey || event.shiftKey || event.ctrlKey || event.defaultPrevented) { | ||
return; | ||
} | ||
if (!this.items.some(item => item.textContent.replace(/[^a-zA-Z0-9]/g, '').toLowerCase().indexOf(this._searchBuf) === 0)) { | ||
this._searchBuf = key.toLowerCase(); | ||
const item = this._filterItems(event.composedPath())[0]; | ||
let idx; | ||
if (item && !item.disabled && (idx = this.items.indexOf(item)) >= 0) { | ||
this.selected = idx; | ||
} | ||
} | ||
const idx = this._searchBuf.length === 1 ? currentIdx + 1 : currentIdx; | ||
return this._getAvailableIndex(idx, increment, condition); | ||
} | ||
/** | ||
* @param {number} currentIdx | ||
* @param {string} key | ||
* @return {number} | ||
* @protected | ||
*/ | ||
_searchKey(currentIdx, key) { | ||
this._searchReset = Debouncer.debounce(this._searchReset, timeOut.after(500), () => (this._searchBuf = '')); | ||
this._searchBuf += key.toLowerCase(); | ||
const increment = 1; | ||
const condition = (item) => | ||
!(item.disabled || this._isItemHidden(item)) && | ||
item.textContent | ||
.replace(/[^a-zA-Z0-9]/g, '') | ||
.toLowerCase() | ||
.indexOf(this._searchBuf) === 0; | ||
/** | ||
* @return {boolean} | ||
* @protected | ||
*/ | ||
get _isRTL() { | ||
return !this._vertical && this.getAttribute('dir') === 'rtl'; | ||
} | ||
if ( | ||
!this.items.some( | ||
(item) => | ||
item.textContent | ||
.replace(/[^a-zA-Z0-9]/g, '') | ||
.toLowerCase() | ||
.indexOf(this._searchBuf) === 0 | ||
) | ||
) { | ||
this._searchBuf = key.toLowerCase(); | ||
} | ||
/** | ||
* @param {!KeyboardEvent} event | ||
* @protected | ||
*/ | ||
_onKeydown(event) { | ||
if (event.metaKey || event.ctrlKey) { | ||
return; | ||
const idx = this._searchBuf.length === 1 ? currentIdx + 1 : currentIdx; | ||
return this._getAvailableIndex(idx, increment, condition); | ||
} | ||
// IE names for arrows do not include the Arrow prefix | ||
const key = event.key.replace(/^Arrow/, ''); | ||
/** | ||
* @return {boolean} | ||
* @protected | ||
*/ | ||
get _isRTL() { | ||
return !this._vertical && this.getAttribute('dir') === 'rtl'; | ||
} | ||
const currentIdx = this.items.indexOf(this.focused); | ||
/** | ||
* @param {!KeyboardEvent} event | ||
* @protected | ||
*/ | ||
_onKeydown(event) { | ||
if (event.metaKey || event.ctrlKey) { | ||
return; | ||
} | ||
if (/[a-zA-Z0-9]/.test(key) && key.length === 1) { | ||
const idx = this._searchKey(currentIdx, key); | ||
const key = event.key; | ||
const currentIdx = this.items.indexOf(this.focused); | ||
if (/[a-zA-Z0-9]/.test(key) && key.length === 1) { | ||
const idx = this._searchKey(currentIdx, key); | ||
if (idx >= 0) { | ||
this._focus(idx); | ||
} | ||
return; | ||
} | ||
const condition = (item) => !(item.disabled || this._isItemHidden(item)); | ||
let idx, increment; | ||
const dirIncrement = this._isRTL ? -1 : 1; | ||
if ((this._vertical && key === 'ArrowUp') || (!this._vertical && key === 'ArrowLeft')) { | ||
increment = -dirIncrement; | ||
idx = currentIdx - dirIncrement; | ||
} else if ((this._vertical && key === 'ArrowDown') || (!this._vertical && key === 'ArrowRight')) { | ||
increment = dirIncrement; | ||
idx = currentIdx + dirIncrement; | ||
} else if (key === 'Home') { | ||
increment = 1; | ||
idx = 0; | ||
} else if (key === 'End') { | ||
increment = -1; | ||
idx = this.items.length - 1; | ||
} | ||
idx = this._getAvailableIndex(idx, increment, condition); | ||
if (idx >= 0) { | ||
this._focus(idx); | ||
event.preventDefault(); | ||
} | ||
return; | ||
} | ||
const condition = item => !(item.disabled || this._isItemHidden(item)); | ||
let idx, increment; | ||
/** | ||
* @param {number} idx | ||
* @param {number} increment | ||
* @param {function(!Element):boolean} condition | ||
* @return {number} | ||
* @protected | ||
*/ | ||
_getAvailableIndex(idx, increment, condition) { | ||
const totalItems = this.items.length; | ||
for (let i = 0; typeof idx == 'number' && i < totalItems; i++, idx += increment || 1) { | ||
if (idx < 0) { | ||
idx = totalItems - 1; | ||
} else if (idx >= totalItems) { | ||
idx = 0; | ||
} | ||
const dirIncrement = this._isRTL ? -1 : 1; | ||
const item = this.items[idx]; | ||
if (this._vertical && key === 'Up' || !this._vertical && key === 'Left') { | ||
increment = -dirIncrement; | ||
idx = currentIdx - dirIncrement; | ||
} else if (this._vertical && key === 'Down' || !this._vertical && key === 'Right') { | ||
increment = dirIncrement; | ||
idx = currentIdx + dirIncrement; | ||
} else if (key === 'Home') { | ||
increment = 1; | ||
idx = 0; | ||
} else if (key === 'End') { | ||
increment = -1; | ||
idx = this.items.length - 1; | ||
if (condition(item)) { | ||
return idx; | ||
} | ||
} | ||
return -1; | ||
} | ||
idx = this._getAvailableIndex(idx, increment, condition); | ||
if (idx >= 0) { | ||
this._focus(idx); | ||
event.preventDefault(); | ||
/** | ||
* @param {!Element} item | ||
* @return {boolean} | ||
* @protected | ||
*/ | ||
_isItemHidden(item) { | ||
return getComputedStyle(item).display === 'none'; | ||
} | ||
} | ||
/** | ||
* @param {number} idx | ||
* @param {number} increment | ||
* @param {function(!Element):boolean} condition | ||
* @return {number} | ||
* @protected | ||
*/ | ||
_getAvailableIndex(idx, increment, condition) { | ||
const totalItems = this.items.length; | ||
for (let i = 0; typeof idx == 'number' && i < totalItems; i++, idx += (increment || 1)) { | ||
if (idx < 0) { | ||
idx = totalItems - 1; | ||
} else if (idx >= totalItems) { | ||
idx = 0; | ||
} | ||
/** | ||
* @param {number} idx | ||
* @protected | ||
*/ | ||
_setFocusable(idx) { | ||
idx = this._getAvailableIndex(idx, 1, (item) => !item.disabled); | ||
const item = this.items[idx] || this.items[0]; | ||
this.items.forEach((e) => (e.tabIndex = e === item ? 0 : -1)); | ||
} | ||
/** | ||
* @param {number} idx | ||
* @protected | ||
*/ | ||
_focus(idx) { | ||
const item = this.items[idx]; | ||
this.items.forEach((e) => (e.focused = e === item)); | ||
this._setFocusable(idx); | ||
this._scrollToItem(idx); | ||
item.focus(); | ||
} | ||
if (condition(item)) { | ||
return idx; | ||
} | ||
focus() { | ||
// In initialisation (e.g vaadin-select) observer might not been run yet. | ||
this._observer && this._observer.flush(); | ||
const firstItem = this.querySelector('[tabindex="0"]') || (this.items ? this.items[0] : null); | ||
firstItem && firstItem.focus(); | ||
} | ||
return -1; | ||
} | ||
/** | ||
* @param {!Element} item | ||
* @return {boolean} | ||
* @protected | ||
*/ | ||
_isItemHidden(item) { | ||
return getComputedStyle(item).display === 'none'; | ||
} | ||
/** | ||
* @return {!HTMLElement} | ||
* @protected | ||
*/ | ||
get _scrollerElement() { | ||
// Returning scroller element of the component | ||
console.warn(`Please implement the '_scrollerElement' property in <${this.localName}>`); | ||
return this; | ||
} | ||
/** | ||
* @param {number} idx | ||
* @protected | ||
*/ | ||
_setFocusable(idx) { | ||
idx = this._getAvailableIndex(idx, 1, item => !item.disabled); | ||
const item = this.items[idx] || this.items[0]; | ||
this.items.forEach(e => e.tabIndex = e === item ? 0 : -1); | ||
} | ||
/** | ||
* Scroll the container to have the next item by the edge of the viewport. | ||
* @param {number} idx | ||
* @protected | ||
*/ | ||
_scrollToItem(idx) { | ||
const item = this.items[idx]; | ||
if (!item) { | ||
return; | ||
} | ||
/** | ||
* @param {number} idx | ||
* @protected | ||
*/ | ||
_focus(idx) { | ||
const item = this.items[idx]; | ||
this.items.forEach(e => e.focused = e === item); | ||
this._setFocusable(idx); | ||
this._scrollToItem(idx); | ||
item.focus(); | ||
} | ||
const props = this._vertical ? ['top', 'bottom'] : this._isRTL ? ['right', 'left'] : ['left', 'right']; | ||
focus() { | ||
// In initialisation (e.g vaadin-select) observer might not been run yet. | ||
this._observer && this._observer.flush(); | ||
const firstItem = this.querySelector('[tabindex="0"]') || (this.items ? this.items[0] : null); | ||
firstItem && firstItem.focus(); | ||
} | ||
const scrollerRect = this._scrollerElement.getBoundingClientRect(); | ||
const nextItemRect = (this.items[idx + 1] || item).getBoundingClientRect(); | ||
const prevItemRect = (this.items[idx - 1] || item).getBoundingClientRect(); | ||
/** | ||
* @return {!HTMLElement} | ||
* @protected | ||
*/ | ||
get _scrollerElement() { | ||
// Returning scroller element of the component | ||
} | ||
let scrollDistance = 0; | ||
if ( | ||
(!this._isRTL && nextItemRect[props[1]] >= scrollerRect[props[1]]) || | ||
(this._isRTL && nextItemRect[props[1]] <= scrollerRect[props[1]]) | ||
) { | ||
scrollDistance = nextItemRect[props[1]] - scrollerRect[props[1]]; | ||
} else if ( | ||
(!this._isRTL && prevItemRect[props[0]] <= scrollerRect[props[0]]) || | ||
(this._isRTL && prevItemRect[props[0]] >= scrollerRect[props[0]]) | ||
) { | ||
scrollDistance = prevItemRect[props[0]] - scrollerRect[props[0]]; | ||
} | ||
/** | ||
* Scroll the container to have the next item by the edge of the viewport. | ||
* @param {number} idx | ||
* @protected | ||
*/ | ||
_scrollToItem(idx) { | ||
const item = this.items[idx]; | ||
if (!item) { | ||
return; | ||
this._scroll(scrollDistance); | ||
} | ||
const props = this._vertical ? ['top', 'bottom'] : | ||
this._isRTL ? ['right', 'left'] : ['left', 'right']; | ||
const scrollerRect = this._scrollerElement.getBoundingClientRect(); | ||
const nextItemRect = (this.items[idx + 1] || item).getBoundingClientRect(); | ||
const prevItemRect = (this.items[idx - 1] || item).getBoundingClientRect(); | ||
let scrollDistance = 0; | ||
if (!this._isRTL && nextItemRect[props[1]] >= scrollerRect[props[1]] || | ||
this._isRTL && nextItemRect[props[1]] <= scrollerRect[props[1]]) { | ||
scrollDistance = nextItemRect[props[1]] - scrollerRect[props[1]]; | ||
} else if (!this._isRTL && prevItemRect[props[0]] <= scrollerRect[props[0]] || | ||
this._isRTL && prevItemRect[props[0]] >= scrollerRect[props[0]]) { | ||
scrollDistance = prevItemRect[props[0]] - scrollerRect[props[0]]; | ||
/** | ||
* @return {boolean} | ||
* @protected | ||
*/ | ||
get _vertical() { | ||
return this.orientation !== 'horizontal'; | ||
} | ||
this._scroll(scrollDistance); | ||
} | ||
/** | ||
* @return {boolean} | ||
* @protected | ||
*/ | ||
get _vertical() { | ||
return this.orientation !== 'horizontal'; | ||
} | ||
/** | ||
* @param {number} pixels | ||
* @protected | ||
*/ | ||
_scroll(pixels) { | ||
if (this._vertical) { | ||
this._scrollerElement['scrollTop'] += pixels; | ||
} else { | ||
const scrollType = DirHelper.detectScrollType(); | ||
const scrollLeft = DirHelper.getNormalizedScrollLeft(scrollType, | ||
this.getAttribute('dir') || 'ltr', this._scrollerElement) + pixels; | ||
DirHelper.setNormalizedScrollLeft(scrollType, | ||
this.getAttribute('dir') || 'ltr', this._scrollerElement, scrollLeft); | ||
/** | ||
* @param {number} pixels | ||
* @protected | ||
*/ | ||
_scroll(pixels) { | ||
if (this._vertical) { | ||
this._scrollerElement['scrollTop'] += pixels; | ||
} else { | ||
const dir = this.getAttribute('dir') || 'ltr'; | ||
const scrollType = DirHelper.detectScrollType(); | ||
const scrollLeft = DirHelper.getNormalizedScrollLeft(scrollType, dir, this._scrollerElement) + pixels; | ||
DirHelper.setNormalizedScrollLeft(scrollType, dir, this._scrollerElement, scrollLeft); | ||
} | ||
} | ||
} | ||
/** | ||
* Fired when the selection is changed. | ||
* Not fired when used in `multiple` selection mode. | ||
* | ||
* @event selected-changed | ||
* @param {Object} detail | ||
* @param {Object} detail.value the index of the item selected in the items array. | ||
*/ | ||
}; | ||
/** | ||
* Fired when the selection is changed. | ||
* Not fired when used in `multiple` selection mode. | ||
* | ||
* @event selected-changed | ||
* @param {Object} detail | ||
* @param {Object} detail.value the index of the item selected in the items array. | ||
*/ | ||
}; |
@@ -1,39 +0,19 @@ | ||
/** | ||
* DO NOT EDIT | ||
* | ||
* This file was automatically generated by | ||
* https://github.com/Polymer/tools/tree/master/packages/gen-typescript-declarations | ||
* | ||
* To modify these typings, edit the source file(s): | ||
* vaadin-multi-select-list-mixin.js | ||
*/ | ||
import { ListMixin, ListMixinConstructor } from './vaadin-list-mixin.js'; | ||
// tslint:disable:variable-name Describing an API that's defined elsewhere. | ||
// tslint:disable:no-any describes the API as best we are able today | ||
import {ListMixin} from './vaadin-list-mixin.js'; | ||
export {MultiSelectListMixin}; | ||
/** | ||
* A mixin for `nav` elements, facilitating multiple selection of childNodes. | ||
*/ | ||
declare function MultiSelectListMixin<T extends new (...args: any[]) => {}>(base: T): T & MultiSelectListMixinConstructor & ListMixinConstructor; | ||
declare function MultiSelectListMixin<T extends new (...args: any[]) => {}>( | ||
base: T | ||
): T & MultiSelectListMixinConstructor & ListMixinConstructor; | ||
import {ListMixinConstructor} from './vaadin-list-mixin.js'; | ||
interface MultiSelectListMixinConstructor { | ||
new(...args: any[]): MultiSelectListMixin; | ||
new (...args: any[]): MultiSelectListMixin; | ||
} | ||
export {MultiSelectListMixinConstructor}; | ||
interface MultiSelectListMixin extends ListMixin { | ||
/** | ||
* Specifies that multiple options can be selected at once. | ||
*/ | ||
multiple: boolean|null|undefined; | ||
multiple: boolean | null | undefined; | ||
@@ -44,5 +24,7 @@ /** | ||
*/ | ||
selectedValues: string[]|null|undefined; | ||
ready(): void; | ||
selectedValues: string[] | null | undefined; | ||
_onMultipleClick(event: MouseEvent): void; | ||
} | ||
export { MultiSelectListMixin, MultiSelectListMixinConstructor }; |
/** | ||
@license | ||
Copyright (c) 2019 Vaadin Ltd. | ||
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
* @license | ||
* Copyright (c) 2021 Vaadin Ltd. | ||
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ | ||
*/ | ||
import { ListMixin } from './vaadin-list-mixin.js'; | ||
@@ -14,108 +14,107 @@ | ||
*/ | ||
export const MultiSelectListMixin = superClass => class VaadinMultiSelectListMixin extends ListMixin(superClass) { | ||
static get properties() { | ||
return { | ||
/** | ||
* Specifies that multiple options can be selected at once. | ||
*/ | ||
multiple: { | ||
type: Boolean, | ||
value: false, | ||
reflectToAttribute: true, | ||
observer: '_multipleChanged' | ||
}, | ||
export const MultiSelectListMixin = (superClass) => | ||
class VaadinMultiSelectListMixin extends ListMixin(superClass) { | ||
static get properties() { | ||
return { | ||
/** | ||
* Specifies that multiple options can be selected at once. | ||
*/ | ||
multiple: { | ||
type: Boolean, | ||
value: false, | ||
reflectToAttribute: true, | ||
observer: '_multipleChanged' | ||
}, | ||
/** | ||
* Array of indexes of the items selected in the items array | ||
* Note: Not updated when used in single selection mode. | ||
* @type {string[] | null | undefined} | ||
*/ | ||
selectedValues: { | ||
type: Array, | ||
notify: true, | ||
value: function() { | ||
return []; | ||
/** | ||
* Array of indexes of the items selected in the items array | ||
* Note: Not updated when used in single selection mode. | ||
* @type {string[] | null | undefined} | ||
*/ | ||
selectedValues: { | ||
type: Array, | ||
notify: true, | ||
value: function () { | ||
return []; | ||
} | ||
} | ||
} | ||
}; | ||
} | ||
}; | ||
} | ||
static get observers() { | ||
return [ | ||
`_enhanceMultipleItems(items, multiple, selected, selectedValues, selectedValues.*)` | ||
]; | ||
} | ||
static get observers() { | ||
return [`_enhanceMultipleItems(items, multiple, selected, selectedValues, selectedValues.*)`]; | ||
} | ||
/** @protected */ | ||
ready() { | ||
// Should be attached before click listener in list-mixin | ||
this.addEventListener('click', e => this._onMultipleClick(e)); | ||
/** @protected */ | ||
ready() { | ||
// Should be attached before click listener in list-mixin | ||
this.addEventListener('click', (e) => this._onMultipleClick(e)); | ||
super.ready(); | ||
} | ||
/** @private */ | ||
_enhanceMultipleItems(items, multiple, selected, selectedValues) { | ||
if (!items || !multiple) { | ||
return; | ||
super.ready(); | ||
} | ||
if (selectedValues) { | ||
const selectedItems = selectedValues.map(selectedId => items[selectedId]); | ||
items.forEach(item => item.selected = selectedItems.indexOf(item) !== -1); | ||
} | ||
/** @private */ | ||
_enhanceMultipleItems(items, multiple, selected, selectedValues) { | ||
if (!items || !multiple) { | ||
return; | ||
} | ||
this._scrollToLastSelectedItem(); | ||
} | ||
if (selectedValues) { | ||
const selectedItems = selectedValues.map((selectedId) => items[selectedId]); | ||
items.forEach((item) => (item.selected = selectedItems.indexOf(item) !== -1)); | ||
} | ||
/** @private */ | ||
_scrollToLastSelectedItem() { | ||
const lastSelectedItem = this.selectedValues.slice(-1)[0]; | ||
if (lastSelectedItem && !lastSelectedItem.disabled) { | ||
this._scrollToItem(lastSelectedItem); | ||
this._scrollToLastSelectedItem(); | ||
} | ||
} | ||
/** | ||
* @param {!MouseEvent} event | ||
* @protected | ||
*/ | ||
_onMultipleClick(event) { | ||
const item = this._filterItems(event.composedPath())[0]; | ||
const idx = item && !item.disabled ? this.items.indexOf(item) : -1; | ||
if (idx < 0 || !this.multiple) { | ||
return; | ||
/** @private */ | ||
_scrollToLastSelectedItem() { | ||
const lastSelectedItem = this.selectedValues.slice(-1)[0]; | ||
if (lastSelectedItem && !lastSelectedItem.disabled) { | ||
this._scrollToItem(lastSelectedItem); | ||
} | ||
} | ||
event.preventDefault(); | ||
if (this.selectedValues.indexOf(idx) !== -1) { | ||
this.selectedValues = this.selectedValues.filter(v => v !== idx); | ||
} else { | ||
this.selectedValues = this.selectedValues.concat(idx); | ||
} | ||
} | ||
/** | ||
* @param {!MouseEvent} event | ||
* @protected | ||
*/ | ||
_onMultipleClick(event) { | ||
const item = this._filterItems(event.composedPath())[0]; | ||
const idx = item && !item.disabled ? this.items.indexOf(item) : -1; | ||
if (idx < 0 || !this.multiple) { | ||
return; | ||
} | ||
/** @private */ | ||
_multipleChanged(value, oldValue) { | ||
// Changing from multiple to single selection, clear selection. | ||
if (!value && oldValue) { | ||
this.selectedValues = []; | ||
this.items.forEach(item => item.selected = false); | ||
event.preventDefault(); | ||
if (this.selectedValues.indexOf(idx) !== -1) { | ||
this.selectedValues = this.selectedValues.filter((v) => v !== idx); | ||
} else { | ||
this.selectedValues = this.selectedValues.concat(idx); | ||
} | ||
} | ||
// Changing from single to multiple selection, add selected to selectedValues. | ||
if (value && !oldValue && this.selected !== undefined) { | ||
this.push('selectedValues', this.selected); | ||
this.selected = undefined; | ||
/** @private */ | ||
_multipleChanged(value, oldValue) { | ||
// Changing from multiple to single selection, clear selection. | ||
if (!value && oldValue) { | ||
this.selectedValues = []; | ||
this.items.forEach((item) => (item.selected = false)); | ||
} | ||
// Changing from single to multiple selection, add selected to selectedValues. | ||
if (value && !oldValue && this.selected !== undefined) { | ||
this.push('selectedValues', this.selected); | ||
this.selected = undefined; | ||
} | ||
} | ||
} | ||
/** | ||
* Fired when the selection is changed. | ||
* Not fired in single selection mode. | ||
* | ||
* @event selected-values-changed | ||
* @param {Object} detail | ||
* @param {Object} detail.value the array of indexes of the items selected in the items array. | ||
*/ | ||
}; | ||
/** | ||
* Fired when the selection is changed. | ||
* Not fired in single selection mode. | ||
* | ||
* @event selected-values-changed | ||
* @param {Object} detail | ||
* @param {Object} detail.value the array of indexes of the items selected in the items array. | ||
*/ | ||
}; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
4
30077
514
1
37
+ Added@vaadin/vaadin-element-mixin@20.0.5(transitive)
- Removed@vaadin/vaadin-element-mixin@2.4.2(transitive)