All Downloads are FREE. Search and download functionalities are using the official Maven repository.

form-layoutpackage.src.vaadin-form-item.js Maven / Gradle / Ivy

The newest version!
/**
 * @license
 * Copyright (c) 2017 - 2024 Vaadin Ltd.
 * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
 */
import { html, PolymerElement } from '@polymer/polymer/polymer-element.js';
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
import { addValueToAttribute, removeValueFromAttribute } from '@vaadin/component-base/src/dom-utils.js';
import { generateUniqueId } from '@vaadin/component-base/src/unique-id-utils.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';

/**
 * `` is a Web Component providing labelled form item wrapper
 * for using inside ``.
 *
 * `` accepts a single child as the input content,
 * and also has a separate named `label` slot:
 *
 * ```html
 * 
 *   
 *   
 * 
 * ```
 *
 * The label is optional and can be omitted:
 *
 * ```html
 * 
 *    Subscribe to our Newsletter
 * 
 * ```
 *
 * By default, the `label` slot content is displayed aside of the input content.
 * When `label-position="top"` is set, the `label` slot content is displayed on top:
 *
 * ```html
 * 
 *   
 *   
 * 
 * ```
 *
 * **Note:** Normally, `` is used as a child of
 * a `` element. Setting `label-position` is unnecessary,
 * because the `label-position` attribute is triggered automatically by the parent
 * ``, depending on its width and responsive behavior.
 *
 * ### Input Width
 *
 * By default, `` does not manipulate the width of the slotted
 * input element. Optionally you can stretch the child input element to fill
 * the available width for the input content by adding the `full-width` class:
 *
 * ```html
 * 
 *   
 *   
 * 
 * ```
 *
 * ### Styling
 *
 * The `label-position` host attribute can be used to target the label on top state:
 *
 * ```
 * :host([label-position="top"]) {
 *   padding-top: 0.5rem;
 * }
 * ```
 *
 * The following shadow DOM parts are available for styling:
 *
 * Part name | Description
 * ---|---
 * label | The label slot container
 *
 * ### Custom CSS Properties Reference
 *
 * The following custom CSS properties are available on the ``
 * element:
 *
 * Custom CSS property | Description | Default
 * ---|---|---
 * `--vaadin-form-item-label-width` | Width of the label column when the labels are aside | `8em`
 * `--vaadin-form-item-label-spacing` | Spacing between the label column and the input column when the labels are aside | `1em`
 * `--vaadin-form-item-row-spacing` | Height of the spacing between the form item elements | `1em`
 *
 * See [Styling Components](https://vaadin.com/docs/latest/styling/styling-components) documentation.
 *
 * @customElement
 * @extends HTMLElement
 * @mixes ThemableMixin
 */
class FormItem extends ThemableMixin(PolymerElement) {
  static get template() {
    return html`
      
      
`; } static get is() { return 'vaadin-form-item'; } constructor() { super(); this.__updateInvalidState = this.__updateInvalidState.bind(this); /** * 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; } /** * 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 fieldNode = this.__fieldNode; if (fieldNode) { fieldNode.focus(); fieldNode.click(); } } /** @private */ __getValidateFunction(field) { return field.validate || field.checkValidity; } /** * 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 = null; if (this.__fieldNode) { this.__unlinkLabelFromField(this.__fieldNode); } } const newLabelNode = this.$.labelSlot.assignedElements()[0]; if (newLabelNode) { this.__labelNode = newLabelNode; if (this.__labelNode.id) { // The new label node already has an id. Let's use it. this.__labelId = this.__labelNode.id; } else { // The new label node doesn't have an id yet. Generate a unique one. this.__labelId = `label-${this.localName}-${generateUniqueId()}`; 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.__fieldNode) { // Discard the old field this.__unlinkLabelFromField(this.__fieldNode); this.__updateRequiredState(false); this.__fieldNodeObserver.disconnect(); this.__fieldNode = null; } const fieldNodes = this.$.contentSlot.assignedElements(); if (fieldNodes.length > 1) { console.warn( `WARNING: Since Vaadin 23, placing multiple fields directly to a is deprecated. Please wrap fields with a instead.`, ); } const newFieldNode = fieldNodes.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); } } } /** @private */ __updateRequiredState(required) { if (required) { this.setAttribute('required', ''); this.__fieldNode.addEventListener('blur', this.__updateInvalidState); this.__fieldNode.addEventListener('change', this.__updateInvalidState); } else { this.removeAttribute('invalid'); this.removeAttribute('required'); this.__fieldNode.removeEventListener('blur', this.__updateInvalidState); this.__fieldNode.removeEventListener('change', this.__updateInvalidState); } } /** @private */ __updateInvalidState() { const isValid = this.__getValidateFunction(this.__fieldNode).call(this.__fieldNode); this.toggleAttribute('invalid', isValid === false); } } defineCustomElement(FormItem); export { FormItem };




© 2015 - 2024 Weber Informatics LLC | Privacy Policy