@vaadin/vaadin-control-state-mixin
Advanced tools
Comparing version 2.2.4 to 20.0.0-alpha1
{ | ||
"name": "@vaadin/vaadin-control-state-mixin", | ||
"version": "20.0.0-alpha1", | ||
"description": "vaadin-control-state-mixin", | ||
"main": "vaadin-control-state-mixin.js", | ||
"module": "vaadin-control-state-mixin.js", | ||
"repository": "vaadin/vaadin-control-state-mixin", | ||
"keywords": [ | ||
@@ -10,7 +15,2 @@ "Vaadin", | ||
], | ||
"repository": "vaadin/vaadin-control-state-mixin", | ||
"homepage": "https://vaadin.com/elements", | ||
"name": "@vaadin/vaadin-control-state-mixin", | ||
"version": "2.2.4", | ||
"main": "vaadin-control-state-mixin.js", | ||
"author": "Vaadin Ltd", | ||
@@ -21,2 +21,3 @@ "license": "Apache-2.0", | ||
}, | ||
"homepage": "https://vaadin.com/elements", | ||
"files": [ | ||
@@ -26,21 +27,15 @@ "vaadin-*.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" | ||
}, | ||
"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" | ||
} | ||
"sinon": "^9.2.4" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"gitHead": "93c8e0ec03a178c6d74261261f985bd07f7cc79c" | ||
} |
@@ -0,9 +1,9 @@ | ||
# vaadin-control-state-mixin | ||
A mixin which adds `focused` and `focus-ring` states to an element. | ||
[![npm version](https://badgen.net/npm/v/@vaadin/vaadin-control-state-mixin)](https://www.npmjs.com/package/@vaadin/vaadin-control-state-mixin) | ||
[![Bower version](https://badgen.net/github/release/vaadin/vaadin-control-state-mixin)](https://github.com/vaadin/vaadin-control-state-mixin/releases) | ||
[![Build Status](https://travis-ci.org/vaadin/vaadin-control-state-mixin.svg?branch=master)](https://travis-ci.org/vaadin/vaadin-control-state-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) | ||
[![Discord](https://img.shields.io/discord/732335336448852018?label=discord)](https://discord.gg/PHmkCKC) | ||
# vaadin-control-state-mixin | ||
A mixin which adds `focused` and `focus-ring` states to an element. | ||
## Running tests in browser | ||
@@ -13,23 +13,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-control-state-mixin` directory, run `npm install` and then `bower install` to install dependencies. | ||
1. When in the `vaadin-control-state-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-control-state-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-control-state-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 +30,0 @@ |
/** | ||
* 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-control-state-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 | ||
export {ControlStateMixin}; | ||
/** | ||
* Polymer.IronControlState is not a proper 2.0 class, also, its tabindex | ||
@@ -25,9 +8,6 @@ * implementation fails in the shadow dom, so we have this for vaadin elements. | ||
interface ControlStateMixinConstructor { | ||
new(...args: any[]): ControlStateMixin; | ||
new (...args: any[]): ControlStateMixin; | ||
} | ||
export {ControlStateMixinConstructor}; | ||
interface ControlStateMixin { | ||
/** | ||
@@ -37,3 +17,3 @@ * Any element extending this mixin is required to implement this getter. | ||
*/ | ||
readonly focusElement: Element|null|undefined; | ||
readonly focusElement: Element | null | undefined; | ||
@@ -43,3 +23,3 @@ /** | ||
*/ | ||
autofocus: boolean|null|undefined; | ||
autofocus: boolean | null | undefined; | ||
@@ -49,8 +29,11 @@ /** | ||
*/ | ||
disabled: boolean|null|undefined; | ||
ready(): void; | ||
disconnectedCallback(): void; | ||
disabled: boolean | null | undefined; | ||
_setFocused(focused: boolean): void; | ||
_focus(): void; | ||
click(): void; | ||
} | ||
export { ControlStateMixinConstructor, ControlStateMixin }; |
/** | ||
@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/ | ||
*/ | ||
// We consider the keyboard to be active if the window has received a keydown | ||
@@ -17,3 +18,3 @@ // event since the last mousedown event. | ||
}, | ||
{capture: true} | ||
{ capture: true } | ||
); | ||
@@ -26,3 +27,3 @@ | ||
}, | ||
{capture: true} | ||
{ capture: true } | ||
); | ||
@@ -36,28 +37,22 @@ | ||
*/ | ||
const TabIndexMixin = superClass => class VaadinTabIndexMixin extends superClass { | ||
static get properties() { | ||
var properties = { | ||
/** | ||
* Internal property needed to listen to `tabindex` attribute changes. | ||
* | ||
* For changing the tabindex of this component use the native `tabIndex` property. | ||
* @private | ||
*/ | ||
tabindex: { | ||
type: Number, | ||
value: 0, | ||
reflectToAttribute: true, | ||
observer: '_tabindexChanged' | ||
} | ||
}; | ||
if (window.ShadyDOM) { | ||
// ShadyDOM browsers need the `tabIndex` in order to notify when the user changes it programmatically. | ||
properties['tabIndex'] = properties.tabindex; | ||
const TabIndexMixin = (superClass) => | ||
class VaadinTabIndexMixin extends superClass { | ||
static get properties() { | ||
return { | ||
/** | ||
* Internal property needed to listen to `tabindex` attribute changes. | ||
* | ||
* For changing the tabindex of this component use the native `tabIndex` property. | ||
* @private | ||
*/ | ||
tabindex: { | ||
type: Number, | ||
value: 0, | ||
reflectToAttribute: true, | ||
observer: '_tabindexChanged' | ||
} | ||
}; | ||
} | ||
}; | ||
return properties; | ||
} | ||
}; | ||
/** | ||
@@ -68,77 +63,63 @@ * Polymer.IronControlState is not a proper 2.0 class, also, its tabindex | ||
*/ | ||
export const ControlStateMixin = superClass => class VaadinControlStateMixin extends TabIndexMixin(superClass) { | ||
static get properties() { | ||
return { | ||
/** | ||
* Specify that this control should have input focus when the page loads. | ||
*/ | ||
autofocus: { | ||
type: Boolean | ||
}, | ||
export const ControlStateMixin = (superClass) => | ||
class VaadinControlStateMixin extends TabIndexMixin(superClass) { | ||
static get properties() { | ||
return { | ||
/** | ||
* Specify that this control should have input focus when the page loads. | ||
*/ | ||
autofocus: { | ||
type: Boolean | ||
}, | ||
/** | ||
* Stores the previous value of tabindex attribute of the disabled element | ||
* @private | ||
*/ | ||
_previousTabIndex: { | ||
type: Number | ||
}, | ||
/** | ||
* Stores the previous value of tabindex attribute of the disabled element | ||
* @private | ||
*/ | ||
_previousTabIndex: { | ||
type: Number | ||
}, | ||
/** | ||
* If true, the user cannot interact with this element. | ||
*/ | ||
disabled: { | ||
type: Boolean, | ||
observer: '_disabledChanged', | ||
reflectToAttribute: true | ||
}, | ||
/** | ||
* If true, the user cannot interact with this element. | ||
*/ | ||
disabled: { | ||
type: Boolean, | ||
observer: '_disabledChanged', | ||
reflectToAttribute: true | ||
}, | ||
/** | ||
* @private | ||
*/ | ||
_isShiftTabbing: { | ||
type: Boolean | ||
} | ||
}; | ||
} | ||
/** | ||
* @private | ||
*/ | ||
_isShiftTabbing: { | ||
type: Boolean | ||
} | ||
}; | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
ready() { | ||
this.addEventListener('focusin', e => { | ||
if (e.composedPath()[0] === this) { | ||
// Only focus if the focus is received from somewhere outside | ||
if (!this.contains(e.relatedTarget)) { | ||
this._focus(); | ||
/** | ||
* @protected | ||
*/ | ||
ready() { | ||
this.addEventListener('focusin', (e) => { | ||
if (e.composedPath()[0] === this) { | ||
// Only focus if the focus is received from somewhere outside | ||
if (!this.contains(e.relatedTarget)) { | ||
this._focus(); | ||
} | ||
} else if (e.composedPath().indexOf(this.focusElement) !== -1 && !this.disabled) { | ||
this._setFocused(true); | ||
} | ||
} else if (e.composedPath().indexOf(this.focusElement) !== -1 && !this.disabled) { | ||
this._setFocused(true); | ||
} | ||
}); | ||
this.addEventListener('focusout', e => this._setFocused(false)); | ||
}); | ||
this.addEventListener('focusout', () => this._setFocused(false)); | ||
// In super.ready() other 'focusin' and 'focusout' listeners might be | ||
// added, so we call it after our own ones to ensure they execute first. | ||
// Issue to watch out: when incorrect, <vaadin-combo-box> refocuses the | ||
// input field on iOS after “Done” is pressed. | ||
super.ready(); | ||
// In super.ready() other 'focusin' and 'focusout' listeners might be | ||
// added, so we call it after our own ones to ensure they execute first. | ||
// Issue to watch out: when incorrect, <vaadin-combo-box> refocuses the | ||
// input field on iOS after “Done” is pressed. | ||
super.ready(); | ||
// This fixes the bug in Firefox 61 (https://bugzilla.mozilla.org/show_bug.cgi?id=1472887) | ||
// where focusout event does not go out of shady DOM because composed property in the event is not true | ||
const ensureEventComposed = e => { | ||
if (!e.composed) { | ||
e.target.dispatchEvent(new CustomEvent(e.type, { | ||
bubbles: true, | ||
composed: true, | ||
cancelable: false | ||
})); | ||
} | ||
}; | ||
this.shadowRoot.addEventListener('focusin', ensureEventComposed); | ||
this.shadowRoot.addEventListener('focusout', ensureEventComposed); | ||
this.addEventListener('keydown', e => { | ||
if (!e.defaultPrevented && e.keyCode === 9) { | ||
if (e.shiftKey) { | ||
this.addEventListener('keydown', (e) => { | ||
if (!e.defaultPrevented && e.keyCode === 9 && e.shiftKey) { | ||
// Flag is checked in _focus event handler. | ||
@@ -149,167 +130,141 @@ this._isShiftTabbing = true; | ||
// Event handling in IE is asynchronous and the flag is removed asynchronously as well | ||
setTimeout(() => this._isShiftTabbing = false, 0); | ||
} else { | ||
// Workaround for FF63-65 bug that causes the focus to get lost when | ||
// blurring a slotted component with focusable shadow root content | ||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1528686 | ||
// TODO: Remove when safe | ||
const firefox = window.navigator.userAgent.match(/Firefox\/(\d\d\.\d)/); | ||
if (firefox | ||
&& parseFloat(firefox[1]) >= 63 | ||
&& parseFloat(firefox[1]) < 66 | ||
&& this.parentNode | ||
&& this.nextSibling) { | ||
const fakeTarget = document.createElement('input'); | ||
fakeTarget.style.position = 'absolute'; | ||
fakeTarget.style.opacity = '0'; | ||
fakeTarget.tabIndex = this.tabIndex; | ||
this.parentNode.insertBefore(fakeTarget, this.nextSibling); | ||
fakeTarget.focus(); | ||
fakeTarget.addEventListener('focusout', () => this.parentNode.removeChild(fakeTarget)); | ||
} | ||
setTimeout(() => (this._isShiftTabbing = false), 0); | ||
} | ||
}); | ||
if (this.autofocus && !this.disabled) { | ||
window.requestAnimationFrame(() => { | ||
this._focus(); | ||
this._setFocused(true); | ||
this.setAttribute('focus-ring', ''); | ||
}); | ||
} | ||
}); | ||
if (this.autofocus && !this.disabled) { | ||
window.requestAnimationFrame(() => { | ||
this._focus(); | ||
this._setFocused(true); | ||
this.setAttribute('focus-ring', ''); | ||
}); | ||
} | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
disconnectedCallback() { | ||
super.disconnectedCallback(); | ||
/** | ||
* @protected | ||
*/ | ||
disconnectedCallback() { | ||
super.disconnectedCallback(); | ||
// in non-Chrome browsers, blur does not fire on the element when it is disconnected. | ||
// reproducible in `<vaadin-date-picker>` when closing on `Cancel` or `Today` click. | ||
if (this.hasAttribute('focused')) { | ||
this._setFocused(false); | ||
// in non-Chrome browsers, blur does not fire on the element when it is disconnected. | ||
// reproducible in `<vaadin-date-picker>` when closing on `Cancel` or `Today` click. | ||
if (this.hasAttribute('focused')) { | ||
this._setFocused(false); | ||
} | ||
} | ||
} | ||
/** | ||
* @param {boolean} focused | ||
* @protected | ||
*/ | ||
_setFocused(focused) { | ||
if (focused) { | ||
this.setAttribute('focused', ''); | ||
} else { | ||
this.removeAttribute('focused'); | ||
} | ||
/** | ||
* @param {boolean} focused | ||
* @protected | ||
*/ | ||
_setFocused(focused) { | ||
if (focused) { | ||
this.setAttribute('focused', ''); | ||
} else { | ||
this.removeAttribute('focused'); | ||
} | ||
// focus-ring is true when the element was focused from the keyboard. | ||
// Focus Ring [A11ycasts]: https://youtu.be/ilj2P5-5CjI | ||
if (focused && keyboardActive) { | ||
this.setAttribute('focus-ring', ''); | ||
} else { | ||
this.removeAttribute('focus-ring'); | ||
// focus-ring is true when the element was focused from the keyboard. | ||
// Focus Ring [A11ycasts]: https://youtu.be/ilj2P5-5CjI | ||
if (focused && keyboardActive) { | ||
this.setAttribute('focus-ring', ''); | ||
} else { | ||
this.removeAttribute('focus-ring'); | ||
} | ||
} | ||
} | ||
/** | ||
* Any element extending this mixin is required to implement this getter. | ||
* It returns the actual focusable element in the component. | ||
* @return {Element | null | undefined} | ||
*/ | ||
get focusElement() { | ||
window.console.warn(`Please implement the 'focusElement' property in <${this.localName}>`); | ||
return this; | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
_focus() { | ||
if (!this.focusElement || this._isShiftTabbing) { | ||
return; | ||
/** | ||
* Any element extending this mixin is required to implement this getter. | ||
* It returns the actual focusable element in the component. | ||
* @return {Element | null | undefined} | ||
*/ | ||
get focusElement() { | ||
window.console.warn(`Please implement the 'focusElement' property in <${this.localName}>`); | ||
return this; | ||
} | ||
this.focusElement.focus(); | ||
this._setFocused(true); | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
_focus() { | ||
if (!this.focusElement || this._isShiftTabbing) { | ||
return; | ||
} | ||
/** | ||
* Moving the focus from the host element causes firing of the blur event what leads to problems in IE. | ||
* @private | ||
*/ | ||
focus() { | ||
if (!this.focusElement || this.disabled) { | ||
return; | ||
this.focusElement.focus(); | ||
this._setFocused(true); | ||
} | ||
this.focusElement.focus(); | ||
this._setFocused(true); | ||
} | ||
/** | ||
* Moving the focus from the host element causes firing of the blur event what leads to problems in IE. | ||
* @private | ||
*/ | ||
focus() { | ||
if (!this.focusElement || this.disabled) { | ||
return; | ||
} | ||
/** | ||
* Native bluring in the host element does nothing because it does not have the focus. | ||
* In chrome it works, but not in FF. | ||
* @private | ||
*/ | ||
blur() { | ||
if (!this.focusElement) { | ||
return; | ||
this.focusElement.focus(); | ||
this._setFocused(true); | ||
} | ||
this.focusElement.blur(); | ||
this._setFocused(false); | ||
} | ||
/** | ||
* @param {boolean} disabled | ||
* @private | ||
*/ | ||
_disabledChanged(disabled) { | ||
this.focusElement.disabled = disabled; | ||
if (disabled) { | ||
this.blur(); | ||
this._previousTabIndex = this.tabindex; | ||
this.tabindex = -1; | ||
this.setAttribute('aria-disabled', 'true'); | ||
} else { | ||
if (typeof this._previousTabIndex !== 'undefined') { | ||
this.tabindex = this._previousTabIndex; | ||
/** | ||
* Native bluring in the host element does nothing because it does not have the focus. | ||
* In chrome it works, but not in FF. | ||
* @private | ||
*/ | ||
blur() { | ||
if (!this.focusElement) { | ||
return; | ||
} | ||
this.removeAttribute('aria-disabled'); | ||
this.focusElement.blur(); | ||
this._setFocused(false); | ||
} | ||
} | ||
/** | ||
* @param {number | null | undefined} tabindex | ||
* @private | ||
*/ | ||
_tabindexChanged(tabindex) { | ||
if (tabindex !== undefined) { | ||
this.focusElement.tabIndex = tabindex; | ||
} | ||
if (this.disabled && this.tabindex) { | ||
// If tabindex attribute was changed while checkbox was disabled | ||
if (this.tabindex !== -1) { | ||
/** | ||
* @param {boolean} disabled | ||
* @private | ||
*/ | ||
_disabledChanged(disabled) { | ||
this.focusElement.disabled = disabled; | ||
if (disabled) { | ||
this.blur(); | ||
this._previousTabIndex = this.tabindex; | ||
this.tabindex = -1; | ||
this.setAttribute('aria-disabled', 'true'); | ||
} else { | ||
if (typeof this._previousTabIndex !== 'undefined') { | ||
this.tabindex = this._previousTabIndex; | ||
} | ||
this.removeAttribute('aria-disabled'); | ||
} | ||
this.tabindex = tabindex = undefined; | ||
} | ||
if (window.ShadyDOM) { | ||
this.setProperties({tabIndex: tabindex, tabindex: tabindex}); | ||
/** | ||
* @param {number | null | undefined} tabindex | ||
* @private | ||
*/ | ||
_tabindexChanged(tabindex) { | ||
if (tabindex !== undefined) { | ||
this.focusElement.tabIndex = tabindex; | ||
} | ||
if (this.disabled && this.tabindex) { | ||
// If tabindex attribute was changed while checkbox was disabled | ||
if (this.tabindex !== -1) { | ||
this._previousTabIndex = this.tabindex; | ||
} | ||
this.tabindex = tabindex = undefined; | ||
} | ||
} | ||
} | ||
/** | ||
* @protected | ||
*/ | ||
click() { | ||
if (!this.disabled) { | ||
super.click(); | ||
/** | ||
* @protected | ||
*/ | ||
click() { | ||
if (!this.disabled) { | ||
super.click(); | ||
} | ||
} | ||
} | ||
}; | ||
}; |
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
21128
4
266
1
38