@vaadin/field-base
Advanced tools
Comparing version 22.0.0-alpha1 to 22.0.0-alpha10
@@ -1,14 +0,15 @@ | ||
export { ClearButtonMixin } from './src/clear-button-mixin.js'; | ||
export { AriaLabelController } from './src/aria-label-controller.js'; | ||
export { CheckedMixin } from './src/checked-mixin.js'; | ||
export { DelegateFocusMixin } from './src/delegate-focus-mixin.js'; | ||
export { DisabledMixin } from './src/disabled-mixin.js'; | ||
export { FieldAriaMixin } from './src/field-aria-mixin.js'; | ||
export { FocusMixin } from './src/focus-mixin.js'; | ||
export { HelperTextMixin } from './src/helper-text-mixin.js'; | ||
export { InputAriaMixin } from './src/input-aria-mixin.js'; | ||
export { DelegateStateMixin } from './src/delegate-state-mixin.js'; | ||
export { FieldMixin } from './src/field-mixin.js'; | ||
export { InputController } from './src/input-controller.js'; | ||
export { InputControlMixin } from './src/input-control-mixin.js'; | ||
export { InputFieldMixin } from './src/input-field-mixin.js'; | ||
export { InputMixin } from './src/input-mixin.js'; | ||
export { InputPropsMixin } from './src/input-props-mixin.js'; | ||
export { LabelMixin } from './src/label-mixin.js'; | ||
export { SlotMixin } from './src/slot-mixin.js'; | ||
export { TextFieldMixin } from './src/text-field-mixin.js'; | ||
export { PatternMixin } from './src/pattern-mixin.js'; | ||
export { ShadowFocusMixin } from './src/shadow-focus-mixin.js'; | ||
export { SlotStylesMixin } from './src/slot-styles-mixin.js'; | ||
export { TextAreaController } from './src/text-area-controller.js'; | ||
export { ValidateMixin } from './src/validate-mixin.js'; |
19
index.js
@@ -1,14 +0,15 @@ | ||
export { ClearButtonMixin } from './src/clear-button-mixin.js'; | ||
export { AriaLabelController } from './src/aria-label-controller.js'; | ||
export { CheckedMixin } from './src/checked-mixin.js'; | ||
export { DelegateFocusMixin } from './src/delegate-focus-mixin.js'; | ||
export { DisabledMixin } from './src/disabled-mixin.js'; | ||
export { FieldAriaMixin } from './src/field-aria-mixin.js'; | ||
export { FocusMixin } from './src/focus-mixin.js'; | ||
export { HelperTextMixin } from './src/helper-text-mixin.js'; | ||
export { InputAriaMixin } from './src/input-aria-mixin.js'; | ||
export { DelegateStateMixin } from './src/delegate-state-mixin.js'; | ||
export { FieldMixin } from './src/field-mixin.js'; | ||
export { InputController } from './src/input-controller.js'; | ||
export { InputControlMixin } from './src/input-control-mixin.js'; | ||
export { InputFieldMixin } from './src/input-field-mixin.js'; | ||
export { InputMixin } from './src/input-mixin.js'; | ||
export { InputPropsMixin } from './src/input-props-mixin.js'; | ||
export { LabelMixin } from './src/label-mixin.js'; | ||
export { SlotMixin } from './src/slot-mixin.js'; | ||
export { TextFieldMixin } from './src/text-field-mixin.js'; | ||
export { PatternMixin } from './src/pattern-mixin.js'; | ||
export { ShadowFocusMixin } from './src/shadow-focus-mixin.js'; | ||
export { SlotStylesMixin } from './src/slot-styles-mixin.js'; | ||
export { TextAreaController } from './src/text-area-controller.js'; | ||
export { ValidateMixin } from './src/validate-mixin.js'; |
{ | ||
"name": "@vaadin/field-base", | ||
"version": "22.0.0-alpha1", | ||
"version": "22.0.0-alpha10", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"description": "Vaadin field base mixins", | ||
"main": "index.js", | ||
"module": "index.js", | ||
"repository": "vaadin/web-components", | ||
"keywords": [ | ||
"Vaadin", | ||
"web-components", | ||
"web-component" | ||
], | ||
"license": "Apache-2.0", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/vaadin/web-components.git", | ||
"directory": "packages/field-base" | ||
}, | ||
"author": "Vaadin Ltd", | ||
"license": "Apache-2.0", | ||
"homepage": "https://vaadin.com/components", | ||
"bugs": { | ||
"url": "https://github.com/vaadin/web-components" | ||
}, | ||
"homepage": "https://vaadin.com/components", | ||
"main": "index.js", | ||
"module": "index.js", | ||
"files": [ | ||
"index.d.ts", | ||
"index.js", | ||
"index.d.ts", | ||
"src" | ||
], | ||
"keywords": [ | ||
"Vaadin", | ||
"web-components", | ||
"web-component" | ||
], | ||
"dependencies": { | ||
"@polymer/polymer": "^3.0.0" | ||
"@polymer/polymer": "^3.0.0", | ||
"@vaadin/component-base": "22.0.0-alpha10", | ||
"lit": "^2.0.0" | ||
}, | ||
"devDependencies": { | ||
"@esm-bundle/chai": "^4.1.5", | ||
"@vaadin/testing-helpers": "^0.2.1", | ||
"@esm-bundle/chai": "^4.3.4", | ||
"@vaadin/testing-helpers": "^0.3.0", | ||
"sinon": "^9.2.1" | ||
}, | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"gitHead": "c9694d6549bff1f7fffb9ece26178e57fc228a51" | ||
"gitHead": "6d3055383b9c3c8306ea34c6f2e2087e63c49348" | ||
} |
@@ -6,4 +6,4 @@ /** | ||
*/ | ||
import { FocusMixin } from './focus-mixin.js'; | ||
import { DisabledMixin } from './disabled-mixin.js'; | ||
import { DisabledMixin } from '@vaadin/component-base/src/disabled-mixin.js'; | ||
import { FocusMixin } from '@vaadin/component-base/src/focus-mixin.js'; | ||
@@ -26,4 +26,7 @@ /** | ||
/** | ||
* Any element extending this mixin is required to implement this getter. | ||
* It returns the actual focusable element in the component. | ||
* A reference to the focusable element controlled by the mixin. | ||
* It can be an input, textarea, button or any element with tabindex > -1. | ||
* | ||
* Any component implementing this mixin is expected to provide it | ||
* by using `this._setFocusElement(input)` Polymer API. | ||
*/ | ||
@@ -30,0 +33,0 @@ readonly focusElement: Element | null | undefined; |
@@ -7,92 +7,174 @@ /** | ||
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js'; | ||
import { FocusMixin } from './focus-mixin.js'; | ||
import { DisabledMixin } from './disabled-mixin.js'; | ||
import { DisabledMixin } from '@vaadin/component-base/src/disabled-mixin.js'; | ||
import { FocusMixin } from '@vaadin/component-base/src/focus-mixin.js'; | ||
const DelegateFocusMixinImplementation = (superclass) => | ||
class DelegateFocusMixinClass extends FocusMixin(DisabledMixin(superclass)) { | ||
static get properties() { | ||
return { | ||
/** | ||
* Specify that this control should have input focus when the page loads. | ||
*/ | ||
autofocus: { | ||
type: Boolean | ||
/** | ||
* A mixin to forward focus to an element in the light DOM. | ||
* | ||
* @polymerMixin | ||
* @mixes DisabledMixin | ||
* @mixes FocusMixin | ||
*/ | ||
export const DelegateFocusMixin = dedupingMixin( | ||
(superclass) => | ||
class DelegateFocusMixinClass extends FocusMixin(DisabledMixin(superclass)) { | ||
static get properties() { | ||
return { | ||
/** | ||
* Specify that this control should have input focus when the page loads. | ||
*/ | ||
autofocus: { | ||
type: Boolean | ||
}, | ||
/** | ||
* A reference to the focusable element controlled by the mixin. | ||
* It can be an input, textarea, button or any element with tabindex > -1. | ||
* | ||
* Any component implementing this mixin is expected to provide it | ||
* by using `this._setFocusElement(input)` Polymer API. | ||
* | ||
* @protected | ||
* @type {!HTMLElement} | ||
*/ | ||
focusElement: { | ||
type: Object, | ||
readOnly: true, | ||
observer: '_focusElementChanged' | ||
} | ||
}; | ||
} | ||
constructor() { | ||
super(); | ||
this._boundOnBlur = this._onBlur.bind(this); | ||
this._boundOnFocus = this._onFocus.bind(this); | ||
} | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
if (this.autofocus && !this.disabled) { | ||
requestAnimationFrame(() => { | ||
this.focus(); | ||
this.setAttribute('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() { | ||
console.warn(`Please implement the 'focusElement' property in <${this.localName}>`); | ||
return null; | ||
} | ||
/** | ||
* @protected | ||
* @override | ||
*/ | ||
focus() { | ||
if (!this.focusElement || this.disabled) { | ||
return; | ||
} | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
this.focusElement.focus(); | ||
this._setFocused(true); | ||
} | ||
if (this.autofocus && !this.disabled) { | ||
requestAnimationFrame(() => { | ||
this.focus(); | ||
this.setAttribute('focus-ring', ''); | ||
}); | ||
/** | ||
* @protected | ||
* @override | ||
*/ | ||
blur() { | ||
if (!this.focusElement) { | ||
return; | ||
} | ||
this.focusElement.blur(); | ||
this._setFocused(false); | ||
} | ||
} | ||
focus() { | ||
if (!this.focusElement || this.disabled) { | ||
return; | ||
/** | ||
* @protected | ||
* @override | ||
*/ | ||
click() { | ||
if (this.focusElement && !this.disabled) { | ||
this.focusElement.click(); | ||
} | ||
} | ||
this.focusElement.focus(); | ||
this._setFocused(true); | ||
} | ||
/** @protected */ | ||
_focusElementChanged(element, oldElement) { | ||
if (element) { | ||
element.disabled = this.disabled; | ||
this._addFocusListeners(element); | ||
} else if (oldElement) { | ||
this._removeFocusListeners(oldElement); | ||
} | ||
} | ||
blur() { | ||
if (!this.focusElement) { | ||
return; | ||
/** | ||
* @param {HTMLElement} element | ||
* @protected | ||
*/ | ||
_addFocusListeners(element) { | ||
element.addEventListener('blur', this._boundOnBlur); | ||
element.addEventListener('focus', this._boundOnFocus); | ||
} | ||
this.focusElement.blur(); | ||
this._setFocused(false); | ||
} | ||
click() { | ||
if (this.focusElement && !this.disabled) { | ||
this.focusElement.click(); | ||
/** | ||
* @param {HTMLElement} element | ||
* @protected | ||
*/ | ||
_removeFocusListeners(element) { | ||
element.removeEventListener('blur', this._boundOnBlur); | ||
element.removeEventListener('focus', this._boundOnFocus); | ||
} | ||
} | ||
/** | ||
* @param {Event} event | ||
* @return {boolean} | ||
* @protected | ||
*/ | ||
_shouldSetFocus(event) { | ||
return event.target === this.focusElement; | ||
} | ||
/** | ||
* Focus event does not bubble, so we dispatch it manually | ||
* on the host element to support adding focus listeners | ||
* when the focusable element is placed in light DOM. | ||
* @param {FocusEvent} event | ||
* @protected | ||
*/ | ||
_onFocus(event) { | ||
event.stopPropagation(); | ||
this.dispatchEvent(new Event('focus')); | ||
} | ||
/** | ||
* @param {boolean} disabled | ||
* @protected | ||
*/ | ||
_disabledChanged(disabled) { | ||
super._disabledChanged(disabled); | ||
/** | ||
* Blur event does not bubble, so we dispatch it manually | ||
* on the host element to support adding blur listeners | ||
* when the focusable element is placed in light DOM. | ||
* @param {FocusEvent} event | ||
* @protected | ||
*/ | ||
_onBlur(event) { | ||
event.stopPropagation(); | ||
this.dispatchEvent(new Event('blur')); | ||
} | ||
if (this.focusElement) { | ||
this.focusElement.disabled = disabled; | ||
/** | ||
* @param {Event} event | ||
* @return {boolean} | ||
* @protected | ||
* @override | ||
*/ | ||
_shouldSetFocus(event) { | ||
return event.target === this.focusElement; | ||
} | ||
if (disabled) { | ||
this.blur(); | ||
/** | ||
* @param {boolean} disabled | ||
* @protected | ||
*/ | ||
_disabledChanged(disabled) { | ||
super._disabledChanged(disabled); | ||
if (this.focusElement) { | ||
this.focusElement.disabled = disabled; | ||
} | ||
if (disabled) { | ||
this.blur(); | ||
} | ||
} | ||
} | ||
}; | ||
/** | ||
* A mixin to forward focus to an element in the light DOM. | ||
*/ | ||
export const DelegateFocusMixin = dedupingMixin(DelegateFocusMixinImplementation); | ||
); |
@@ -6,6 +6,3 @@ /** | ||
*/ | ||
import { ClearButtonMixin } from './clear-button-mixin.js'; | ||
import { DelegateFocusMixin } from './delegate-focus-mixin.js'; | ||
import { FieldAriaMixin } from './field-aria-mixin.js'; | ||
import { InputPropsMixin } from './input-props-mixin.js'; | ||
import { InputControlMixin } from './input-control-mixin.js'; | ||
@@ -21,5 +18,3 @@ /** | ||
interface InputFieldMixin extends ClearButtonMixin, DelegateFocusMixin, FieldAriaMixin, InputPropsMixin { | ||
readonly inputElement: HTMLElement | undefined; | ||
interface InputFieldMixin extends InputControlMixin { | ||
/** | ||
@@ -51,19 +46,4 @@ * Whether the value of the control can be automatically completed by the browser. | ||
autocapitalize: 'on' | 'off' | 'none' | 'characters' | 'words' | 'sentences' | undefined; | ||
/** | ||
* Specify that the value should be automatically selected when the field gains focus. | ||
*/ | ||
autoselect: boolean; | ||
/** | ||
* The value of the field. | ||
*/ | ||
value: string; | ||
/** | ||
* Returns true if the current input value satisfies all constraints (if any). | ||
*/ | ||
checkValidity(): boolean; | ||
} | ||
export { InputFieldMixin, InputFieldMixinConstructor }; |
@@ -6,12 +6,12 @@ /** | ||
*/ | ||
import { Debouncer } from '@polymer/polymer/lib/utils/debounce.js'; | ||
import { animationFrame } from '@polymer/polymer/lib/utils/async.js'; | ||
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js'; | ||
import { ClearButtonMixin } from './clear-button-mixin.js'; | ||
import { DelegateFocusMixin } from './delegate-focus-mixin.js'; | ||
import { FieldAriaMixin } from './field-aria-mixin.js'; | ||
import { InputPropsMixin } from './input-props-mixin.js'; | ||
import { InputControlMixin } from './input-control-mixin.js'; | ||
const InputFieldMixinImplementation = (superclass) => | ||
class InputFieldMixinClass extends ClearButtonMixin(FieldAriaMixin(InputPropsMixin(DelegateFocusMixin(superclass)))) { | ||
/** | ||
* A mixin to provide logic for vaadin-text-field and related components. | ||
* | ||
* @polymerMixin | ||
* @mixes InputControlMixin | ||
*/ | ||
export const InputFieldMixin = (superclass) => | ||
class InputFieldMixinClass extends InputControlMixin(superclass) { | ||
static get properties() { | ||
@@ -53,17 +53,15 @@ return { | ||
/** | ||
* Specify that the value should be automatically selected when the field gains focus. | ||
* A pattern matched against individual characters the user inputs. | ||
* When set, the field will prevent: | ||
* - `keyDown` events if the entered key doesn't match `/^_enabledCharPattern$/` | ||
* - `paste` events if the pasted text doesn't match `/^_enabledCharPattern*$/` | ||
* - `drop` events if the dropped text doesn't match `/^_enabledCharPattern*$/` | ||
* | ||
* For example, to enable entering only numbers and minus signs, | ||
* `_enabledCharPattern = "[\\d-]"` | ||
* @protected | ||
*/ | ||
autoselect: { | ||
type: Boolean, | ||
value: false | ||
}, | ||
/** | ||
* The value of the field. | ||
*/ | ||
value: { | ||
_enabledCharPattern: { | ||
type: String, | ||
value: '', | ||
observer: '_valueChanged', | ||
notify: true | ||
observer: '_enabledCharPatternChanged' | ||
} | ||
@@ -73,50 +71,31 @@ }; | ||
static get hostProps() { | ||
return [...super.hostProps, 'autocapitalize', 'autocomplete', 'autocorrect']; | ||
static get delegateAttrs() { | ||
return [...super.delegateAttrs, 'autocapitalize', 'autocomplete', 'autocorrect']; | ||
} | ||
static get observers() { | ||
return ['__observeOffsetHeight(errorMessage, invalid, label, helperText)']; | ||
constructor() { | ||
super(); | ||
this._boundOnPaste = this._onPaste.bind(this); | ||
this._boundOnDrop = this._onDrop.bind(this); | ||
this._boundOnBeforeInput = this._onBeforeInput.bind(this); | ||
} | ||
/** | ||
* Element used by `FieldAriaMixin` to set ARIA attributes. | ||
* @param {HTMLElement} input | ||
* @protected | ||
* @override | ||
*/ | ||
get _ariaTarget() { | ||
return this._inputNode; | ||
} | ||
_inputElementChanged(input) { | ||
super._inputElementChanged(input); | ||
/** | ||
* Element used by `DelegatesFocusMixin` to handle focus. | ||
* @return {!HTMLInputElement} | ||
*/ | ||
get focusElement() { | ||
return this._inputNode; | ||
} | ||
constructor() { | ||
super(); | ||
this._boundOnInput = this._onInput.bind(this); | ||
this._boundOnBlur = this._onBlur.bind(this); | ||
this._boundOnFocus = this._onFocus.bind(this); | ||
} | ||
/** @protected */ | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
if (this._inputNode) { | ||
this._addInputListeners(this._inputNode); | ||
if (input) { | ||
// Discard value set on the custom slotted input. | ||
if (this._inputNode.value !== this.value) { | ||
if (input.value && input.value !== this.value) { | ||
console.warn(`Please define value on the <${this.localName}> component!`); | ||
this._inputNode.value = ''; | ||
input.value = ''; | ||
} | ||
if (this.value) { | ||
this._inputNode.value = this.value; | ||
this.validate(); | ||
input.value = this.value; | ||
} | ||
@@ -126,37 +105,2 @@ } | ||
/** @protected */ | ||
disconnectedCallback() { | ||
super.disconnectedCallback(); | ||
if (this._inputNode) { | ||
this._removeInputListeners(this._inputNode); | ||
} | ||
} | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
// Lumo theme defines a max-height transition for the "error-message" | ||
// part on invalid state change. | ||
const errorPart = this.shadowRoot.querySelector('[part="error-message"]'); | ||
if (errorPart) { | ||
errorPart.addEventListener('transitionend', () => { | ||
this.__observeOffsetHeight(); | ||
}); | ||
} | ||
} | ||
/** | ||
* Returns true if the current input value satisfies all constraints (if any). | ||
* @return {boolean} | ||
*/ | ||
checkValidity() { | ||
if (this.required) { | ||
return this._inputNode ? this._inputNode.checkValidity() : undefined; | ||
} else { | ||
return !this.invalid; | ||
} | ||
} | ||
// Workaround for https://github.com/Polymer/polymer/issues/5259 | ||
@@ -172,115 +116,120 @@ get __data() { | ||
/** | ||
* @param {HTMLElement} node | ||
* Override an event listener from `DelegateFocusMixin`. | ||
* @param {FocusEvent} event | ||
* @protected | ||
* @override | ||
*/ | ||
_addInputListeners(node) { | ||
node.addEventListener('input', this._boundOnInput); | ||
node.addEventListener('blur', this._boundOnBlur); | ||
node.addEventListener('focus', this._boundOnFocus); | ||
_onBlur(event) { | ||
super._onBlur(event); | ||
this.validate(); | ||
} | ||
/** | ||
* @param {HTMLElement} node | ||
* Override a method from `InputMixin` to validate the field | ||
* when a new value is set programmatically. | ||
* @param {string} value | ||
* @protected | ||
* @override | ||
*/ | ||
_removeInputListeners(node) { | ||
node.removeEventListener('input', this._boundOnInput); | ||
node.removeEventListener('blur', this._boundOnBlur); | ||
node.removeEventListener('focus', this._boundOnFocus); | ||
} | ||
_forwardInputValue(value) { | ||
super._forwardInputValue(value); | ||
/** @private */ | ||
_onFocus() { | ||
if (this.autoselect && this._inputNode) { | ||
this._inputNode.select(); | ||
if (this.invalid) { | ||
this.validate(); | ||
} | ||
} | ||
/** @private */ | ||
_onBlur() { | ||
this.validate(); | ||
/** | ||
* Override a method from `InputMixin`. | ||
* @param {!HTMLElement} input | ||
* @protected | ||
* @override | ||
*/ | ||
_addInputListeners(input) { | ||
super._addInputListeners(input); | ||
input.addEventListener('paste', this._boundOnPaste); | ||
input.addEventListener('drop', this._boundOnDrop); | ||
input.addEventListener('beforeinput', this._boundOnBeforeInput); | ||
} | ||
/** | ||
* @param {Event} event | ||
* Override a method from `InputMixin`. | ||
* @param {!HTMLElement} input | ||
* @protected | ||
* @override | ||
*/ | ||
_onInput(event) { | ||
// Ignore manual clear button events | ||
this.__userInput = event.isTrusted; | ||
this.value = event.target.value; | ||
this.__userInput = false; | ||
_removeInputListeners(input) { | ||
super._removeInputListeners(input); | ||
input.removeEventListener('paste', this._boundOnPaste); | ||
input.removeEventListener('drop', this._boundOnDrop); | ||
input.removeEventListener('beforeinput', this._boundOnBeforeInput); | ||
} | ||
/** | ||
* Dispatch an event if a specific size measurement property has changed. | ||
* Supporting multiple properties here is needed for `vaadin-text-area`. | ||
* Override an event listener from `ClearButtonMixin` | ||
* to avoid adding a separate listener. | ||
* @param {!KeyboardEvent} event | ||
* @protected | ||
* @override | ||
*/ | ||
_dispatchIronResizeEventIfNeeded(prop, value) { | ||
const oldSize = '__old' + prop; | ||
if (this[oldSize] !== undefined && this[oldSize] !== value) { | ||
this.dispatchEvent(new CustomEvent('iron-resize', { bubbles: true, composed: true })); | ||
_onKeyDown(event) { | ||
if (this._enabledCharPattern && !this.__shouldAcceptKey(event)) { | ||
event.preventDefault(); | ||
} | ||
this[oldSize] = value; | ||
super._onKeyDown(event); | ||
} | ||
/** @private */ | ||
__observeOffsetHeight() { | ||
this.__observeOffsetHeightDebouncer = Debouncer.debounce( | ||
this.__observeOffsetHeightDebouncer, | ||
animationFrame, | ||
() => { | ||
this._dispatchIronResizeEventIfNeeded('Height', this.offsetHeight); | ||
} | ||
__shouldAcceptKey(event) { | ||
return ( | ||
event.metaKey || | ||
event.ctrlKey || | ||
!event.key || // allow typing anything if event.key is not supported | ||
event.key.length !== 1 || // allow "Backspace", "ArrowLeft" etc. | ||
this.__enabledCharRegExp.test(event.key) | ||
); | ||
} | ||
/** | ||
* @param {unknown} newVal | ||
* @param {unknown} oldVal | ||
* @protected | ||
*/ | ||
_valueChanged(newVal, oldVal) { | ||
// Setting initial value to empty string, skip validation | ||
if (newVal === '' && oldVal === undefined) { | ||
return; | ||
/** @private */ | ||
_onPaste(e) { | ||
if (this._enabledCharPattern) { | ||
const pastedText = (e.clipboardData || window.clipboardData).getData('text'); | ||
if (!this.__enabledTextRegExp.test(pastedText)) { | ||
e.preventDefault(); | ||
} | ||
} | ||
} | ||
if (newVal !== '' && newVal != null) { | ||
this.setAttribute('has-value', ''); | ||
} else { | ||
this.removeAttribute('has-value'); | ||
/** @private */ | ||
_onDrop(e) { | ||
if (this._enabledCharPattern) { | ||
const draggedText = e.dataTransfer.getData('text'); | ||
if (!this.__enabledTextRegExp.test(draggedText)) { | ||
e.preventDefault(); | ||
} | ||
} | ||
} | ||
// Value is set before an element is connected to the DOM: | ||
// this case is handled separately in `connectedCallback`. | ||
if (!this._inputNode) { | ||
return; | ||
/** @private */ | ||
_onBeforeInput(e) { | ||
// The `beforeinput` event covers all the cases for `_enabledCharPattern`: keyboard, pasting and dropping, | ||
// but it is still experimental technology so we can't rely on it. It's used here just as an additional check, | ||
// because it seems to be the only way to detect and prevent specific keys on mobile devices. | ||
// See https://github.com/vaadin/vaadin-text-field/issues/429 | ||
if (this._enabledCharPattern && e.data && !this.__enabledTextRegExp.test(e.data)) { | ||
e.preventDefault(); | ||
} | ||
} | ||
// Value is set by the user, no need to sync it back to input. | ||
// Also no need to validate, as we call `validate` on blur. | ||
if (this.__userInput) { | ||
return; | ||
/** @private */ | ||
_enabledCharPatternChanged(charPattern) { | ||
if (charPattern) { | ||
this.__enabledCharRegExp = new RegExp('^' + charPattern + '$'); | ||
this.__enabledTextRegExp = new RegExp('^' + charPattern + '*$'); | ||
} | ||
// Setting a value programmatically, sync it to input element. | ||
if (newVal != undefined) { | ||
this._inputNode.value = newVal; | ||
} else { | ||
this.clear(); | ||
} | ||
// Validate the field after a new value is set programmatically. | ||
if (this.invalid) { | ||
this.validate(); | ||
} | ||
} | ||
}; | ||
/** | ||
* A mixin to provide logic for vaadin-text-field and related components. | ||
*/ | ||
export const InputFieldMixin = dedupingMixin(InputFieldMixinImplementation); |
@@ -6,6 +6,6 @@ /** | ||
*/ | ||
import { SlotMixin } from './slot-mixin.js'; | ||
/** | ||
* A mixin to add `<input>` element to the corresponding named slot. | ||
* A mixin to store the reference to an input element | ||
* and add input and change event listeners to it. | ||
*/ | ||
@@ -18,9 +18,25 @@ declare function InputMixin<T extends new (...args: any[]) => {}>(base: T): T & InputMixinConstructor; | ||
interface InputMixin extends SlotMixin { | ||
interface InputMixin { | ||
/** | ||
* String used to define input type. | ||
* A reference to the input element controlled by the mixin. | ||
* Any component implementing this mixin is expected to provide it | ||
* by using `this._setInputElement(input)` Polymer API. | ||
* | ||
* A typical case is using `InputController` that does this automatically. | ||
* However, the input element does not have to always be native <input>: | ||
* as an example, <vaadin-combo-box-light> accepts other components. | ||
*/ | ||
readonly type: string; | ||
readonly inputElement: HTMLInputElement; | ||
/** | ||
* The value of the field. | ||
*/ | ||
value: string; | ||
/** | ||
* Clear the value of the field. | ||
*/ | ||
clear(): void; | ||
} | ||
export { InputMixinConstructor, InputMixin }; | ||
export { InputMixin, InputMixinConstructor }; |
@@ -7,65 +7,175 @@ /** | ||
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js'; | ||
import { SlotMixin } from './slot-mixin.js'; | ||
const InputMixinImplementation = (superclass) => | ||
class InputMixinClass extends SlotMixin(superclass) { | ||
static get properties() { | ||
/** | ||
* A mixin to store the reference to an input element | ||
* and add input and change event listeners to it. | ||
* | ||
* @polymerMixin | ||
*/ | ||
export const InputMixin = dedupingMixin( | ||
(superclass) => | ||
class InputMixinClass extends superclass { | ||
static get properties() { | ||
return { | ||
/** | ||
* A reference to the input element controlled by the mixin. | ||
* Any component implementing this mixin is expected to provide it | ||
* by using `this._setInputElement(input)` Polymer API. | ||
* | ||
* A typical case is using `InputController` that does this automatically. | ||
* However, the input element does not have to always be native <input>: | ||
* as an example, <vaadin-combo-box-light> accepts other components. | ||
* | ||
* @protected | ||
* @type {!HTMLElement} | ||
*/ | ||
inputElement: { | ||
type: Object, | ||
readOnly: true, | ||
observer: '_inputElementChanged' | ||
}, | ||
/** | ||
* String used to define input type. | ||
* @protected | ||
*/ | ||
type: { | ||
type: String, | ||
readOnly: true | ||
}, | ||
/** | ||
* The value of the field. | ||
*/ | ||
value: { | ||
type: String, | ||
value: '', | ||
observer: '_valueChanged', | ||
notify: true | ||
} | ||
}; | ||
} | ||
constructor() { | ||
super(); | ||
this._boundOnInput = this._onInput.bind(this); | ||
this._boundOnChange = this._onChange.bind(this); | ||
} | ||
/** | ||
* String used to define input type. | ||
* Clear the value of the field. | ||
*/ | ||
return { | ||
type: { | ||
type: String, | ||
readOnly: true | ||
clear() { | ||
this.value = ''; | ||
} | ||
/** | ||
* Add event listeners to the input element instance. | ||
* Override this method to add custom listeners. | ||
* @param {!HTMLElement} input | ||
*/ | ||
_addInputListeners(input) { | ||
input.addEventListener('input', this._boundOnInput); | ||
input.addEventListener('change', this._boundOnChange); | ||
} | ||
/** | ||
* Remove event listeners from the input element instance. | ||
* @param {!HTMLElement} input | ||
*/ | ||
_removeInputListeners(input) { | ||
input.removeEventListener('input', this._boundOnInput); | ||
input.removeEventListener('change', this._boundOnChange); | ||
} | ||
/** | ||
* A method to forward the value property set on the field | ||
* programmatically back to the input element value. | ||
* Override this method to perform additional checks, | ||
* for example to skip this in certain conditions. | ||
* @param {string} value | ||
* @protected | ||
* @override | ||
*/ | ||
_forwardInputValue(value) { | ||
// Value might be set before an input element is initialized. | ||
// This case should be handled separately by a component that | ||
// implements this mixin, for example in `connectedCallback`. | ||
if (!this.inputElement) { | ||
return; | ||
} | ||
}; | ||
} | ||
get slots() { | ||
return { | ||
...super.slots, | ||
input: () => { | ||
const native = document.createElement('input'); | ||
const value = this.getAttribute('value'); | ||
if (value) { | ||
native.setAttribute('value', value); | ||
} | ||
const name = this.getAttribute('name'); | ||
if (name) { | ||
native.setAttribute('name', name); | ||
} | ||
if (this.type) { | ||
native.setAttribute('type', this.type); | ||
} | ||
return native; | ||
if (value != undefined) { | ||
this.inputElement.value = value; | ||
} else { | ||
this.inputElement.value = ''; | ||
} | ||
}; | ||
} | ||
} | ||
/** @protected */ | ||
get _inputNode() { | ||
return this._getDirectSlotChild('input'); | ||
} | ||
/** @protected */ | ||
_inputElementChanged(input, oldInput) { | ||
if (input) { | ||
this._addInputListeners(input); | ||
} else if (oldInput) { | ||
this._removeInputListeners(oldInput); | ||
} | ||
} | ||
constructor() { | ||
super(); | ||
/** | ||
* An input event listener used to update the field value. | ||
* Override this method with an actual implementation. | ||
* @param {Event} _event | ||
* @protected | ||
* @override | ||
*/ | ||
_onInput(event) { | ||
// Ignore fake input events e.g. used by clear button. | ||
this.__userInput = event.isTrusted; | ||
this.value = event.target.value; | ||
this.__userInput = false; | ||
} | ||
// Ensure every instance has unique ID | ||
const uniqueId = (InputMixinClass._uniqueId = 1 + InputMixinClass._uniqueId || 0); | ||
this._inputId = `${this.localName}-${uniqueId}`; | ||
} | ||
/** | ||
* A change event listener. | ||
* Override this method with an actual implementation. | ||
* @param {Event} _event | ||
* @protected | ||
* @override | ||
*/ | ||
_onChange(_event) {} | ||
/** @protected */ | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
/** | ||
* Toggle the has-value attribute based on the value property. | ||
* @param {boolean} hasValue | ||
* @protected | ||
*/ | ||
_toggleHasValue(hasValue) { | ||
this.toggleAttribute('has-value', hasValue); | ||
} | ||
if (this._inputNode) { | ||
this._inputNode.id = this._inputId; | ||
/** | ||
* Observer called when a value property changes. | ||
* @param {string | undefined} newVal | ||
* @param {string | undefined} oldVal | ||
* @protected | ||
* @override | ||
*/ | ||
_valueChanged(newVal, oldVal) { | ||
this._toggleHasValue(newVal !== '' && newVal != null); | ||
// Setting initial value to empty string, do nothing. | ||
if (newVal === '' && oldVal === undefined) { | ||
return; | ||
} | ||
// Value is set by the user, no need to sync it back to input. | ||
if (this.__userInput) { | ||
return; | ||
} | ||
// Setting a value programmatically, sync it to input element. | ||
this._forwardInputValue(newVal); | ||
} | ||
} | ||
}; | ||
/** | ||
* A mixin to add `<input>` element to the corresponding named slot. | ||
*/ | ||
export const InputMixin = dedupingMixin(InputMixinImplementation); | ||
); |
@@ -6,3 +6,3 @@ /** | ||
*/ | ||
import { SlotMixin } from './slot-mixin.js'; | ||
import { SlotMixin } from '@vaadin/component-base/src/slot-mixin.js'; | ||
@@ -22,5 +22,5 @@ /** | ||
*/ | ||
label: string; | ||
label: string | null | undefined; | ||
} | ||
export { LabelMixinConstructor, LabelMixin }; |
@@ -7,75 +7,89 @@ /** | ||
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js'; | ||
import { SlotMixin } from './slot-mixin.js'; | ||
import { SlotMixin } from '@vaadin/component-base/src/slot-mixin.js'; | ||
const LabelMixinImplementation = (superclass) => | ||
class LabelMixinClass extends SlotMixin(superclass) { | ||
static get properties() { | ||
return { | ||
/** | ||
* The label text for the input node. | ||
* When no light dom defined via [slot=label], this value will be used. | ||
*/ | ||
label: { | ||
type: String, | ||
observer: '_labelChanged' | ||
} | ||
}; | ||
} | ||
/** | ||
* A mixin to provide label via corresponding property or named slot. | ||
* | ||
* @polymerMixin | ||
* @mixes SlotMixin | ||
*/ | ||
export const LabelMixin = dedupingMixin( | ||
(superclass) => | ||
class LabelMixinClass extends SlotMixin(superclass) { | ||
static get properties() { | ||
return { | ||
/** | ||
* The label text for the input node. | ||
* When no light dom defined via [slot=label], this value will be used. | ||
*/ | ||
label: { | ||
type: String, | ||
observer: '_labelChanged' | ||
} | ||
}; | ||
} | ||
get slots() { | ||
return { | ||
...super.slots, | ||
label: () => { | ||
const label = document.createElement('label'); | ||
label.textContent = this.label; | ||
return label; | ||
} | ||
}; | ||
} | ||
/** @protected */ | ||
get slots() { | ||
return { | ||
...super.slots, | ||
label: () => { | ||
const label = document.createElement('label'); | ||
label.textContent = this.label; | ||
return label; | ||
} | ||
}; | ||
} | ||
/** @protected */ | ||
get _labelNode() { | ||
return this._getDirectSlotChild('label'); | ||
} | ||
/** @protected */ | ||
get _labelNode() { | ||
return this._getDirectSlotChild('label'); | ||
} | ||
constructor() { | ||
super(); | ||
constructor() { | ||
super(); | ||
// Ensure every instance has unique ID | ||
const uniqueId = (LabelMixinClass._uniqueId = 1 + LabelMixinClass._uniqueId || 0); | ||
this._labelId = `label-${this.localName}-${uniqueId}`; | ||
} | ||
// Ensure every instance has unique ID | ||
const uniqueId = (LabelMixinClass._uniqueLabelId = 1 + LabelMixinClass._uniqueLabelId || 0); | ||
this._labelId = `label-${this.localName}-${uniqueId}`; | ||
/** @protected */ | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
/** | ||
* @type {MutationObserver} | ||
* @private | ||
*/ | ||
this.__labelNodeObserver = new MutationObserver(() => { | ||
this._toggleHasLabelAttribute(); | ||
}); | ||
} | ||
if (this._labelNode) { | ||
this._labelNode.id = this._labelId; | ||
/** @protected */ | ||
ready() { | ||
super.ready(); | ||
this._applyCustomLabel(); | ||
if (this._labelNode) { | ||
this._labelNode.id = this._labelId; | ||
this._toggleHasLabelAttribute(); | ||
this.__labelNodeObserver.observe(this._labelNode, { childList: true }); | ||
} | ||
} | ||
} | ||
/** @protected */ | ||
_applyCustomLabel() { | ||
const label = this._labelNode.textContent; | ||
if (label !== this.label) { | ||
this.label = label; | ||
/** @protected */ | ||
_labelChanged(label) { | ||
if (this._labelNode) { | ||
this._labelNode.textContent = label; | ||
this._toggleHasLabelAttribute(); | ||
} | ||
} | ||
} | ||
/** @protected */ | ||
_labelChanged(label) { | ||
if (this._labelNode) { | ||
this._labelNode.textContent = label; | ||
/** @protected */ | ||
_toggleHasLabelAttribute() { | ||
if (this._labelNode) { | ||
const hasLabel = this._labelNode.children.length > 0 || this._labelNode.textContent.trim() !== ''; | ||
this.toggleAttribute('has-label', hasLabel); | ||
this.dispatchEvent(new CustomEvent('has-label-changed', { detail: { value: hasLabel } })); | ||
} | ||
} | ||
this.toggleAttribute('has-label', Boolean(label)); | ||
} | ||
}; | ||
/** | ||
* A mixin to provide label via corresponding property or named slot. | ||
*/ | ||
export const LabelMixin = dedupingMixin(LabelMixinImplementation); | ||
); |
@@ -6,3 +6,2 @@ /** | ||
*/ | ||
import { SlotMixin } from './slot-mixin.js'; | ||
@@ -18,3 +17,3 @@ /** | ||
interface ValidateMixin extends SlotMixin { | ||
interface ValidateMixin { | ||
/** | ||
@@ -31,9 +30,2 @@ * Set to true when the field is invalid. | ||
/** | ||
* Error to show when the field is invalid. | ||
* | ||
* @attr {string} error-message | ||
*/ | ||
errorMessage: string; | ||
/** | ||
* Returns true if field is valid, and sets `invalid` based on the field validity. | ||
@@ -40,0 +32,0 @@ */ |
@@ -7,126 +7,51 @@ /** | ||
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js'; | ||
import { SlotMixin } from './slot-mixin.js'; | ||
const ValidateMixinImplementation = (superclass) => | ||
class ValidateMixinClass extends SlotMixin(superclass) { | ||
static get properties() { | ||
return { | ||
/** | ||
* Set to true when the field is invalid. | ||
*/ | ||
invalid: { | ||
type: Boolean, | ||
reflectToAttribute: true, | ||
notify: true, | ||
value: false | ||
}, | ||
/** | ||
* A mixin to provide required state and validation logic. | ||
* | ||
* @polymerMixin | ||
*/ | ||
export const ValidateMixin = dedupingMixin( | ||
(superclass) => | ||
class ValidateMixinClass extends superclass { | ||
static get properties() { | ||
return { | ||
/** | ||
* Set to true when the field is invalid. | ||
*/ | ||
invalid: { | ||
type: Boolean, | ||
reflectToAttribute: true, | ||
notify: true, | ||
value: false | ||
}, | ||
/** | ||
* Specifies that the user must fill in a value. | ||
*/ | ||
required: { | ||
type: Boolean, | ||
reflectToAttribute: true | ||
}, | ||
/** | ||
* Error to show when the field is invalid. | ||
* | ||
* @attr {string} error-message | ||
*/ | ||
errorMessage: { | ||
type: String | ||
} | ||
}; | ||
} | ||
get slots() { | ||
return { | ||
...super.slots, | ||
'error-message': () => { | ||
const error = document.createElement('div'); | ||
error.textContent = this.errorMessage; | ||
error.setAttribute('aria-live', 'assertive'); | ||
return error; | ||
} | ||
}; | ||
} | ||
static get observers() { | ||
return ['_updateErrorMessage(invalid, errorMessage)']; | ||
} | ||
/** @protected */ | ||
get _errorNode() { | ||
return this._getDirectSlotChild('error-message'); | ||
} | ||
constructor() { | ||
super(); | ||
// Ensure every instance has unique ID | ||
const uniqueId = (ValidateMixinClass._uniqueId = 1 + ValidateMixinClass._uniqueId || 0); | ||
this._errorId = `error-${this.localName}-${uniqueId}`; | ||
} | ||
/** @protected */ | ||
connectedCallback() { | ||
super.connectedCallback(); | ||
if (this._errorNode) { | ||
this._errorNode.id = this._errorId; | ||
this._applyCustomError(); | ||
/** | ||
* Specifies that the user must fill in a value. | ||
*/ | ||
required: { | ||
type: Boolean, | ||
reflectToAttribute: true | ||
} | ||
}; | ||
} | ||
} | ||
/** | ||
* Returns true if field is valid, and sets `invalid` based on the field validity. | ||
* | ||
* @return {boolean} True if the value is valid. | ||
*/ | ||
validate() { | ||
return !(this.invalid = !this.checkValidity()); | ||
} | ||
/** | ||
* Returns true if the field value satisfies all constraints (if any). | ||
* | ||
* @return {boolean} | ||
*/ | ||
checkValidity() { | ||
return !this.required || !!this.value; | ||
} | ||
/** @protected */ | ||
_applyCustomError() { | ||
const error = this.__errorMessage; | ||
if (error && error !== this.errorMessage) { | ||
this.errorMessage = error; | ||
delete this.__errorMessage; | ||
/** | ||
* Returns true if field is valid, and sets `invalid` based on the field validity. | ||
* | ||
* @return {boolean} True if the value is valid. | ||
*/ | ||
validate() { | ||
return !(this.invalid = !this.checkValidity()); | ||
} | ||
} | ||
/** | ||
* @param {boolean} invalid | ||
* @protected | ||
*/ | ||
_updateErrorMessage(invalid, errorMessage) { | ||
if (!this._errorNode) { | ||
return; | ||
/** | ||
* Returns true if the field value satisfies all constraints (if any). | ||
* | ||
* @return {boolean} | ||
*/ | ||
checkValidity() { | ||
return !this.required || !!this.value; | ||
} | ||
// save the custom error message content | ||
if (this._errorNode.textContent && !errorMessage) { | ||
this.__errorMessage = this._errorNode.textContent.trim(); | ||
} | ||
const hasError = Boolean(invalid && errorMessage); | ||
this._errorNode.textContent = hasError ? errorMessage : ''; | ||
this.toggleAttribute('has-error-message', hasError); | ||
} | ||
}; | ||
/** | ||
* A mixin to provide required state and validation logic. | ||
*/ | ||
export const ValidateMixin = dedupingMixin(ValidateMixinImplementation); | ||
); |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
90350
51
2403
0
3
1
+ Addedlit@^2.0.0
+ Added@lit-labs/ssr-dom-shim@1.2.1(transitive)
+ Added@lit/reactive-element@1.6.3(transitive)
+ Added@types/trusted-types@2.0.7(transitive)
+ Added@vaadin/component-base@22.0.0-alpha10(transitive)
+ Added@vaadin/vaadin-development-mode-detector@2.0.7(transitive)
+ Added@vaadin/vaadin-usage-statistics@2.1.3(transitive)
+ Addedlit@2.8.0(transitive)
+ Addedlit-element@3.3.3(transitive)
+ Addedlit-html@2.8.0(transitive)