@vaadin/form-layout
Advanced tools
Comparing version 23.0.0-alpha1 to 23.0.0-alpha2
{ | ||
"name": "@vaadin/form-layout", | ||
"version": "23.0.0-alpha1", | ||
"version": "23.0.0-alpha2", | ||
"publishConfig": { | ||
@@ -37,14 +37,15 @@ "access": "public" | ||
"@polymer/polymer": "^3.0.0", | ||
"@vaadin/component-base": "23.0.0-alpha1", | ||
"@vaadin/vaadin-lumo-styles": "23.0.0-alpha1", | ||
"@vaadin/vaadin-material-styles": "23.0.0-alpha1", | ||
"@vaadin/vaadin-themable-mixin": "23.0.0-alpha1" | ||
"@vaadin/component-base": "23.0.0-alpha2", | ||
"@vaadin/vaadin-lumo-styles": "23.0.0-alpha2", | ||
"@vaadin/vaadin-material-styles": "23.0.0-alpha2", | ||
"@vaadin/vaadin-themable-mixin": "23.0.0-alpha2" | ||
}, | ||
"devDependencies": { | ||
"@esm-bundle/chai": "^4.3.4", | ||
"@vaadin/custom-field": "23.0.0-alpha2", | ||
"@vaadin/testing-helpers": "^0.3.2", | ||
"@vaadin/text-field": "23.0.0-alpha1", | ||
"@vaadin/text-field": "23.0.0-alpha2", | ||
"sinon": "^9.2.1" | ||
}, | ||
"gitHead": "fbcb07328fdf88260e3b461088d207426b21c710" | ||
"gitHead": "070f586dead02ca41b66717820c647f48bf1665f" | ||
} |
@@ -100,3 +100,12 @@ /** | ||
*/ | ||
declare class FormItem extends ThemableMixin(HTMLElement) {} | ||
declare class FormItem extends ThemableMixin(HTMLElement) { | ||
/** | ||
* Returns a target element to add ARIA attributes to for a field. | ||
* | ||
* - For Vaadin field components, the method returns an element | ||
* obtained through the `ariaTarget` property defined in `FieldMixin`. | ||
* - In other cases, the method returns the field element itself. | ||
*/ | ||
protected _getFieldAriaTarget(field: HTMLElement): HTMLElement; | ||
} | ||
@@ -103,0 +112,0 @@ declare global { |
@@ -7,2 +7,3 @@ /** | ||
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js'; | ||
import { addValueToAttribute, removeValueFromAttribute } from '@vaadin/field-base/src/utils.js'; | ||
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; | ||
@@ -149,4 +150,4 @@ | ||
</style> | ||
<div id="label" part="label" on-click="_onLabelClick"> | ||
<slot name="label" id="labelSlot"></slot> | ||
<div id="label" part="label" on-click="__onLabelClick"> | ||
<slot name="label" id="labelSlot" on-slotchange="__onLabelSlotChange"></slot> | ||
<span part="required-indicator" aria-hidden="true"></span> | ||
@@ -168,11 +169,76 @@ </div> | ||
this.__updateInvalidState = this.__updateInvalidState.bind(this); | ||
this.__contentFieldObserver = new MutationObserver(() => this.__updateRequiredState(this.__contentField.required)); | ||
/** | ||
* An observer for a field node to reflect its `required` and `invalid` attributes to the component. | ||
* | ||
* @type {MutationObserver} | ||
* @private | ||
*/ | ||
this.__fieldNodeObserver = new MutationObserver(() => this.__updateRequiredState(this.__fieldNode.required)); | ||
/** | ||
* The first label node in the label slot. | ||
* | ||
* @type {HTMLElement | null} | ||
* @private | ||
*/ | ||
this.__labelNode = null; | ||
/** | ||
* The first field node in the content slot. | ||
* | ||
* An element is considered a field when it has the `checkValidity` or `validate` method. | ||
* | ||
* @type {HTMLElement | null} | ||
* @private | ||
*/ | ||
this.__fieldNode = null; | ||
// Ensure every instance has unique ID | ||
const uniqueId = (FormItem._uniqueLabelId = 1 + FormItem._uniqueLabelId || 0); | ||
this.__labelId = `label-${this.localName}-${uniqueId}`; | ||
} | ||
/** | ||
* Returns a target element to add ARIA attributes to for a field. | ||
* | ||
* - For Vaadin field components, the method returns an element | ||
* obtained through the `ariaTarget` property defined in `FieldMixin`. | ||
* - In other cases, the method returns the field element itself. | ||
* | ||
* @param {HTMLElement} field | ||
* @protected | ||
*/ | ||
_getFieldAriaTarget(field) { | ||
return field.ariaTarget || field; | ||
} | ||
/** | ||
* Links the label to a field by adding the label id to | ||
* the `aria-labelledby` attribute of the field's ARIA target element. | ||
* | ||
* @param {HTMLElement} field | ||
* @private | ||
*/ | ||
__linkLabelToField(field) { | ||
addValueToAttribute(this._getFieldAriaTarget(field), 'aria-labelledby', this.__labelId); | ||
} | ||
/** | ||
* Unlinks the label from a field by removing the label id from | ||
* the `aria-labelledby` attribute of the field's ARIA target element. | ||
* | ||
* @param {HTMLElement} field | ||
* @private | ||
*/ | ||
__unlinkLabelFromField(field) { | ||
removeValueFromAttribute(this._getFieldAriaTarget(field), 'aria-labelledby', this.__labelId); | ||
} | ||
/** @private */ | ||
_onLabelClick() { | ||
const firstContentElementChild = this.$.contentSlot.assignedElements()[0]; | ||
if (firstContentElementChild) { | ||
firstContentElementChild.focus(); | ||
firstContentElementChild.click(); | ||
__onLabelClick() { | ||
const fieldNode = this.__fieldNode; | ||
if (fieldNode) { | ||
fieldNode.focus(); | ||
fieldNode.click(); | ||
} | ||
@@ -186,17 +252,62 @@ } | ||
/** @private */ | ||
/** | ||
* A `slotchange` event handler for the label slot. | ||
* | ||
* - Ensures the label id is only assigned to the first label node. | ||
* - Ensures the label node is linked to the first field node via the `aria-labelledby` attribute | ||
* if both nodes are provided, and unlinked otherwise. | ||
* | ||
* @private | ||
*/ | ||
__onLabelSlotChange() { | ||
if (this.__labelNode) { | ||
this.__labelNode.id = ''; | ||
this.__labelNode = null; | ||
if (this.__fieldNode) { | ||
this.__unlinkLabelFromField(this.__fieldNode); | ||
} | ||
} | ||
const newLabelNode = this.$.labelSlot.assignedElements()[0]; | ||
if (newLabelNode) { | ||
this.__labelNode = newLabelNode; | ||
this.__labelNode.id = this.__labelId; | ||
if (this.__fieldNode) { | ||
this.__linkLabelToField(this.__fieldNode); | ||
} | ||
} | ||
} | ||
/** | ||
* A `slotchange` event handler for the content slot. | ||
* | ||
* - Ensures the label node is only linked to the first field node via the `aria-labelledby` attribute. | ||
* - Sets up an observer for the `required` attribute changes on the first field | ||
* to reflect the attribute on the component. Ensures the observer is disconnected from the field | ||
* as soon as it is removed or replaced by another one. | ||
* | ||
* @private | ||
*/ | ||
__onContentSlotChange() { | ||
if (this.__contentField) { | ||
if (this.__fieldNode) { | ||
// Discard the old field | ||
this.__unlinkLabelFromField(this.__fieldNode); | ||
this.__updateRequiredState(false); | ||
this.__contentFieldObserver.disconnect(); | ||
delete this.__contentField; | ||
this.__fieldNodeObserver.disconnect(); | ||
this.__fieldNode = null; | ||
} | ||
const contentFields = this.$.contentSlot.assignedElements().filter((node) => !!this.__getValidateFunction(node)); | ||
if (contentFields.length === 1) { | ||
// There's only one child field | ||
this.__contentField = contentFields[0]; | ||
this.__updateRequiredState(this.__contentField.required); | ||
this.__contentFieldObserver.observe(this.__contentField, { attributes: true, attributeFilter: ['required'] }); | ||
const newFieldNode = this.$.contentSlot.assignedElements().find((field) => { | ||
return !!this.__getValidateFunction(field); | ||
}); | ||
if (newFieldNode) { | ||
this.__fieldNode = newFieldNode; | ||
this.__updateRequiredState(this.__fieldNode.required); | ||
this.__fieldNodeObserver.observe(this.__fieldNode, { attributes: true, attributeFilter: ['required'] }); | ||
if (this.__labelNode) { | ||
this.__linkLabelToField(this.__fieldNode); | ||
} | ||
} | ||
@@ -209,9 +320,9 @@ } | ||
this.setAttribute('required', ''); | ||
this.__contentField.addEventListener('blur', this.__updateInvalidState); | ||
this.__contentField.addEventListener('change', this.__updateInvalidState); | ||
this.__fieldNode.addEventListener('blur', this.__updateInvalidState); | ||
this.__fieldNode.addEventListener('change', this.__updateInvalidState); | ||
} else { | ||
this.removeAttribute('invalid'); | ||
this.removeAttribute('required'); | ||
this.__contentField.removeEventListener('blur', this.__updateInvalidState); | ||
this.__contentField.removeEventListener('change', this.__updateInvalidState); | ||
this.__fieldNode.removeEventListener('blur', this.__updateInvalidState); | ||
this.__fieldNode.removeEventListener('change', this.__updateInvalidState); | ||
} | ||
@@ -222,6 +333,4 @@ } | ||
__updateInvalidState() { | ||
this.toggleAttribute( | ||
'invalid', | ||
this.__getValidateFunction(this.__contentField).call(this.__contentField) === false | ||
); | ||
const isValid = this.__getValidateFunction(this.__fieldNode).call(this.__fieldNode); | ||
this.toggleAttribute('invalid', isValid === false); | ||
} | ||
@@ -228,0 +337,0 @@ } |
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
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
52879
1128
5
+ Added@vaadin/component-base@23.0.0-alpha2(transitive)
+ Added@vaadin/icon@23.0.0-alpha2(transitive)
+ Added@vaadin/vaadin-lumo-styles@23.0.0-alpha2(transitive)
+ Added@vaadin/vaadin-material-styles@23.0.0-alpha2(transitive)
+ Added@vaadin/vaadin-themable-mixin@23.0.0-alpha2(transitive)
- Removed@vaadin/component-base@23.0.0-alpha1(transitive)
- Removed@vaadin/icon@23.0.0-alpha1(transitive)
- Removed@vaadin/vaadin-lumo-styles@23.0.0-alpha1(transitive)
- Removed@vaadin/vaadin-material-styles@23.0.0-alpha1(transitive)
- Removed@vaadin/vaadin-themable-mixin@23.0.0-alpha1(transitive)