Source: ds-label.js

/**
 * @file ds-label.js
 * @summary A custom Web Component that wraps a native `<label>` element.
 * @description
 * The `ds-label` component provides a styled and functional label element
 * for associating text with form controls. It supports the `for` attribute
 * to create explicit associations with form elements.
 *
 * @element ds-label
 * @extends HTMLElement
 *
 * @attr {string} for - The ID of the form control this label is associated with.
 *
 * @property {string} htmlFor - Gets or sets the ID of the associated form control.
 *
 * @fires click - Fired when the label is clicked.
 *
 * @slot - Renders the label text content.
 *
 * @example
 * <!-- Basic label -->
 * <ds-label>Username</ds-label>
 *
 * @example
 * <!-- Label with explicit association -->
 * <ds-label for="username-input">Username</ds-label>
 * <ds-text-input id="username-input"></ds-text-input>
 *
 * @example
 * <!-- Label with form control -->
 * <ds-label for="email-field">Email Address</ds-label>
 * <ds-text-input type="email" id="email-field" required></ds-text-input>
 *
 * @example
 * <!-- Label with checkbox -->
 * <ds-label for="agree-terms">I agree to the terms and conditions</ds-label>
 * <ds-checkbox id="agree-terms" name="agree" value="yes"></ds-checkbox>
 */
class DsLabel extends HTMLElement {
    constructor() {
        super();
        
        // Attach shadow root with open mode for experimentation
        const shadowRoot = this.attachShadow({ mode: 'open' });
        
        // Define the template with internal markup and styles
        const template = document.createElement('template');
        template.innerHTML = `
            <style>
                @import url('/src/design_system/styles.css');
                
                :host {
                    display: block;
                }
                
                .wrapper {
                    width: 100%;
                }
            </style>
            <div class="wrapper">
                <label part="label">
                    <slot></slot>
                </label>
            </div>
        `;
        
        // Append the template's content to the shadow root
        shadowRoot.appendChild(template.content.cloneNode(true));
        
        // Store reference to the internal label for attribute changes
        this.label = shadowRoot.querySelector('label');
        
        // Set up event listeners
        this.setupEventListeners();
    }
    
    /**
     * Defines which attributes the component observes for changes.
     * @returns {Array<string>} An array of attribute names to observe.
     */
    static get observedAttributes() {
        return ['for'];
    }
    
    /**
     * Called when one of the component's observed attributes is added, removed, or changed.
     * @param {string} name - The name of the attribute that changed.
     * @param {string|null} oldValue - The attribute's old value.
     * @param {string|null} newValue - The attribute's new value.
     */
    attributeChangedCallback(name, oldValue, newValue) {
        if (oldValue === newValue) return; // No change
        
        switch (name) {
            case 'for':
                this.label.setAttribute('for', newValue || '');
                break;
        }
    }
    
    /**
     * Sets up event listeners for the label.
     */
    setupEventListeners() {
        // Labels don't typically have interactive events, but we can listen for clicks
        this.label.addEventListener('click', (event) => {
            const newEvent = new Event('click', {
                bubbles: true,
                composed: true,
                cancelable: true
            });
            this.dispatchEvent(newEvent);
        });
    }
    
    /**
     * Gets the ID of the associated form control.
     * @returns {string} The ID of the associated form control.
     */
    get htmlFor() {
        return this.label.htmlFor;
    }
    
    /**
     * Sets the ID of the associated form control.
     * @param {string} val - The ID of the form control to associate with.
     */
    set htmlFor(val) {
        this.label.htmlFor = val;
    }
    
    /**
     * Called when the element is connected to the DOM.
     * Applies initial attributes.
     */
    connectedCallback() {
        // Apply initial attributes
        this.attributeChangedCallback('for', null, this.getAttribute('for'));
    }
}

// Register the custom element
customElements.define('ds-label', DsLabel);