import { ComposableElement } from '../ComposableElement.mjs'; /** * Spinner Web Component * * A simple spinner component that indicates loading or processing state. * This component uses the `aria-busy` attribute in combination with CSS tricks to create a spinner that is accessible, modern, and easy to manage. */ export class Spinner extends ComposableElement { constructor() { super(); } // ------------------------------- // Web Component Lifecycle Methods // ------------------------------- /** * Does initial setup and adds event listeners for interactivity * * `connectedCallback` is a lifecycle method in web components that runs when the custom element is inserted into the document's Document Object Model (DOM). * It can be invoked multiple times if the element is removed and then re-inserted into the DOM. * * Timing: It is called after the element's constructor() but before the element's children are necessarily connected or fully rendered. * Purpose: It is the ideal place to set up tasks that should only occur when the element is actually present in the live document. Common uses include: */ connectedCallback() { const internals = this.attachInternals(); this.shadow = this.shadowRoot; if (!this.shadow) { this.shadow = this.attachShadow({ mode: 'open' }); // Defer execution until the browser finishes parsing the children setTimeout(() => { // Recreate the template using the shadow DOM that is only available through JavaScript this.createTemplateInJS(this.shadow); }, 0); } } /** * Cleans up event listeners when the component is removed from the DOM * * `disconnectedCallback` is a lifecycle method in web components that runs when the custom element is removed from the document's DOM. * It can be invoked multiple times if the element is removed and then re-inserted into the DOM. * * Timing: It is called after the element is removed from the DOM but before it is garbage collected. * Purpose: It is the ideal place to clean up any resources or event listeners that were set up in `connectedCallback`. * * Common uses include: * - Removing event listeners to prevent memory leaks * - Clearing timers or intervals * - Disconnecting from external data sources or APIs */ disconnectedCallback() {} /** * Recreate the template in the shadow DOM through JavaScript instead of relying on the `shadowrootmode` attribute * * @param {ShadowRoot} shadow The shadow DOM to attach the template to */ createTemplateInJS(shadow) { const config = this.initializeComponent('spinner', shadow); if(config == null) { throw new Error('Spinner configuration not found. Please ensure you have a script tag with the appropriate data-spinner-config attribute containing the necessary JSON configuration for the spinner component.'); } // Create the container div for the spinner component const spinnerSpan = document.createElement('span'); spinnerSpan.id = config.spinnerId || 'spinner'; spinnerSpan.setAttribute('aria-busy', 'true'); if(config.inline) { spinnerSpan.classList.add('inline'); } if(typeof config.speed !== 'undefined' && (config.speed === 'slow' || config.speed === 'fast')) { spinnerSpan.classList.add(config.speed); } const spinnerSlot = document.createElement('slot'); spinnerSlot.name = 'spinner-content'; spinnerSpan.appendChild(spinnerSlot); // Finally, append the entire spinner span to the shadow DOM of the component shadow.appendChild(spinnerSpan); } } document.addEventListener('DOMContentLoaded', () => { if (!customElements.get('ba-spinner')) { customElements.define('ba-spinner', Spinner); } });