UNPKG

standards-ui

Version:

A foundational design system built with native Web Components. Includes comprehensive TypeScript types, JSDoc documentation, and component examples.

394 lines (333 loc) 12.5 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>JSDoc: Source: ds-select.js</title> <script src="scripts/prettify/prettify.js"> </script> <script src="scripts/prettify/lang-css.js"> </script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css"> </head> <body> <div id="main"> <h1 class="page-title">Source: ds-select.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>/** * @file ds-select.js * @summary A custom Web Component that wraps a native `&lt;select>` element. * @description * The `ds-select` component provides a styled and functional select dropdown. * It supports both single and multiple selection, and can work with both native * `&lt;option>` elements and custom `&lt;ds-option>` components. * * @element ds-select * @extends BaseComponent * * @attr {string} value - The currently selected option's value. * @attr {boolean} disabled - If present, the select cannot be interacted with. * @attr {boolean} required - If present, a selection must be made before form submission. * @attr {string} name - The name of the select, used when submitting form data. * @attr {boolean} multiple - If present, allows multiple options to be selected. * @attr {string} size - The number of visible options in the dropdown (for multiple selection). * * @property {string} value - Gets or sets the currently selected option's value. * @property {boolean} disabled - Gets or sets the disabled state of the select. * @property {boolean} required - Gets or sets the required state of the select. * @property {string} name - Gets or sets the name of the select. * @property {boolean} multiple - Gets or sets the multiple selection state. * @property {number} size - Gets or sets the number of visible options. * * @fires input - re-emitted from host (bubbles, composed) * @fires change - re-emitted from host (bubbles, composed) * @fires focus - re-emitted from host (bubbles, composed) * @fires blur - re-emitted from host (bubbles, composed) * @fires ds-change - custom event with { detail: { value } } * * @slot - Renders `&lt;option>` or `&lt;ds-option>` elements as select options. * * @example * &lt;!-- Basic select with native options --> * &lt;ds-select name="country"> * &lt;option value="us">United States&lt;/option> * &lt;option value="ca">Canada&lt;/option> * &lt;option value="uk">United Kingdom&lt;/option> * &lt;/ds-select> * * @example * &lt;!-- Select with custom ds-option components --> * &lt;ds-select name="category" required> * &lt;ds-option value="electronics">Electronics&lt;/ds-option> * &lt;ds-option value="clothing">Clothing&lt;/ds-option> * &lt;ds-option value="books">Books&lt;/ds-option> * &lt;/ds-select> * * @example * &lt;!-- Multiple selection select --> * &lt;ds-select name="interests" multiple size="4"> * &lt;ds-option value="sports">Sports&lt;/ds-option> * &lt;ds-option value="music">Music&lt;/ds-option> * &lt;ds-option value="reading">Reading&lt;/ds-option> * &lt;ds-option value="travel">Travel&lt;/ds-option> * &lt;/ds-select> */ import BaseComponent from './base-component.js'; import { emit } from '../utils/emit.js'; class DsSelect extends BaseComponent { constructor() { super(); // Define the template with internal markup and styles const template = document.createElement('template'); template.innerHTML = ` &lt;style> @import url('/src/styles/styles.css'); :host { display: block; } .wrapper { width: 100%; } &lt;/style> &lt;div class="wrapper"> &lt;select id="select" part="select"> &lt;slot>&lt;/slot> &lt;/select> &lt;/div> `; // Set up the component with template and observed attributes this.setupComponent(template, ['value', 'disabled', 'required', 'name', 'multiple', 'size']); // Store reference to the internal select for attribute changes this.select = this.shadowRoot.querySelector('select'); // Set up slot change listener to handle option projection this.setupSlotListener(); this._onInput = this._onInput.bind(this); this._onChange = this._onChange.bind(this); this._onFocus = this._onFocus.bind(this); this._onBlur = this._onBlur.bind(this); } /** * 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 'value': this.select.value = newValue || ''; break; case 'disabled': if (this.hasAttribute('disabled')) { this.select.disabled = true; } else { this.select.disabled = false; } break; case 'required': if (this.hasAttribute('required')) { this.select.required = true; } else { this.select.required = false; } break; case 'name': this.select.name = newValue || ''; break; case 'multiple': if (this.hasAttribute('multiple')) { this.select.multiple = true; } else { this.select.multiple = false; } break; case 'size': this.select.size = newValue || ''; break; } } /** * Sets up event listeners to re-dispatch events from the host element. */ connectedCallback() { if (super.connectedCallback) super.connectedCallback(); this.select.addEventListener('input', this._onInput); this.select.addEventListener('change', this._onChange); this.select.addEventListener('focus', this._onFocus); this.select.addEventListener('blur', this._onBlur); } disconnectedCallback() { if (super.disconnectedCallback) super.disconnectedCallback(); this.select.removeEventListener('input', this._onInput); this.select.removeEventListener('change', this._onChange); this.select.removeEventListener('focus', this._onFocus); this.select.removeEventListener('blur', this._onBlur); } _onInput() { this.dispatchEvent(new Event('input', { bubbles: true, composed: true })); emit(this, 'ds-change', { value: this.select.value }); } _onChange() { this.dispatchEvent(new Event('change', { bubbles: true, composed: true })); } _onFocus() { this.dispatchEvent(new Event('focus', { bubbles: true, composed: true })); } _onBlur() { this.dispatchEvent(new Event('blur', { bubbles: true, composed: true })); } /** * Sets up slot listener to handle option projection. */ setupSlotListener() { const slot = this.shadowRoot.querySelector('slot'); slot.addEventListener('slotchange', () => { this.handleSlotChange(); }); } /** * Handles slot changes to project ds-option components into the select. */ handleSlotChange() { const slot = this.shadowRoot.querySelector('slot'); const assignedNodes = slot.assignedNodes(); // Clear existing options this.select.innerHTML = ''; // Process each assigned node assignedNodes.forEach(node => { if (node.nodeType === Node.ELEMENT_NODE) { if (node.tagName === 'DS-OPTION') { // Create a native option element const option = document.createElement('option'); // Copy attributes from ds-option if (node.hasAttribute('value')) { option.value = node.getAttribute('value'); } if (node.hasAttribute('disabled')) { option.disabled = true; } if (node.hasAttribute('selected')) { option.selected = true; } // Copy text content option.textContent = node.textContent || node.innerText || ''; this.select.appendChild(option); } else if (node.tagName === 'OPTION') { // Direct option element, clone it this.select.appendChild(node.cloneNode(true)); } } }); } /** * Gets the currently selected option's value. * @returns {string} The selected option's value. */ get value() { return this.select.value; } /** * Sets the currently selected option's value. * @param {string} val - The value to select. */ set value(val) { const v = val ?? ''; if (this.select.value !== v) { this.select.value = v; } this.setAttribute('value', v); } /** * Gets the disabled state of the select. * @returns {boolean} Whether the select is disabled. */ get disabled() { return this.select.disabled; } /** * Sets the disabled state of the select. * @param {boolean} val - Whether to disable the select. */ set disabled(val) { this.select.disabled = val; } /** * Gets the required state of the select. * @returns {boolean} Whether the select is required. */ get required() { return this.select.required; } /** * Sets the required state of the select. * @param {boolean} val - Whether to make the select required. */ set required(val) { this.select.required = val; } /** * Gets the name of the select. * @returns {string} The select's name. */ get name() { return this.select.name; } /** * Sets the name of the select. * @param {string} val - The new name to set. */ set name(val) { this.select.name = val; } /** * Gets the multiple selection state. * @returns {boolean} Whether multiple selection is enabled. */ get multiple() { return this.select.multiple; } /** * Sets the multiple selection state. * @param {boolean} val - Whether to enable multiple selection. */ set multiple(val) { this.select.multiple = val; } /** * Gets the number of visible options. * @returns {number} The number of visible options. */ get size() { return this.select.size; } /** * Sets the number of visible options. * @param {number} val - The number of visible options to set. */ set size(val) { this.select.size = val; } } // Register the custom element if (!customElements.get('ds-select')) { customElements.define('ds-select', DsSelect); } // Export for use in other modules export default DsSelect;</code></pre> </article> </section> </div> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="BaseComponent.html">BaseComponent</a></li></ul> </nav> <br class="clear"> <footer> Documentation generated by <a href="https://github.com/jsdoc/jsdoc">JSDoc 4.0.4</a> on Wed Aug 20 2025 19:54:53 GMT-0700 (Pacific Daylight Time) </footer> <script> prettyPrint(); </script> <script src="scripts/linenumber.js"> </script> </body> </html>