import { customElement, type TemplateResult, html, property, css, cssManager, } from '@design.estate/dees-element'; import { DeesInputBase } from './dees-input-base.js'; import * as colors from './00colors.js' const { demoFunc } = await import('./dees-input-multitoggle.demo.js'); declare global { interface HTMLElementTagNameMap { 'dees-input-multitoggle': DeesInputMultitoggle; } } @customElement('dees-input-multitoggle') export class DeesInputMultitoggle extends DeesInputBase { public static demo = demoFunc; @property() type: 'boolean' | 'multi' | 'single' = 'multi'; @property() booleanTrueName: string = 'true'; @property() booleanFalseName: string = 'false'; @property({ type: Array, }) options: string[] = []; @property() selectedOption: string = ''; @property({ type: Boolean }) boolValue: boolean = false; // Add value property for form compatibility public get value(): string | boolean { if (this.type === 'boolean') { return this.selectedOption === this.booleanTrueName; } return this.selectedOption; } public set value(val: string | boolean) { if (this.type === 'boolean' && typeof val === 'boolean') { this.selectedOption = val ? this.booleanTrueName : this.booleanFalseName; } else { this.selectedOption = val as string; } // Defer indicator update to next frame if component not yet updated if (this.hasUpdated) { this.setIndicator(); } } public static styles = [ ...DeesInputBase.baseStyles, cssManager.defaultStyles, css` :host { color: ${cssManager.bdTheme('#333', '#ccc')}; user-select: none; } .selections { position: relative; display: flex; flex-direction: row; flex-wrap: nowrap; background: ${cssManager.bdTheme('#fff', '#222')}; width: min-content; border-radius: 20px; height: 32px; border-top: 1px solid ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(255,255,255,0.1)')}; } .option { color: ${cssManager.bdTheme('#666', '#999')}; position: relative; padding: 0px 16px; line-height: 32px; cursor: default; width: min-content; /* Make the width as per the content */ white-space: nowrap; /* Prevent text wrapping */ transition: all 0.1s; font-size: 14px; transform: translateY(-1px); } .option:hover { color: ${cssManager.bdTheme('#333', '#fff')}; } .option.selected { color: ${cssManager.bdTheme('#fff', '#fff')}; } .indicator { opacity: 0; position: absolute; height: 24px; left: 4px; top: 3px; border-radius: 16px; background: ${cssManager.bdTheme(colors.bright.blueActive, colors.dark.blueActive)}; min-width: 24px; transition: all 0.1s ease-in-out; } .indicator.no-transition { transition: none; } `, ]; public render(): TemplateResult { return html`
${this.options.map( (option) => html`
this.handleSelection(option)}> ${option}
` )}
`; } public async connectedCallback() { await super.connectedCallback(); // Initialize boolean options early if (this.type === 'boolean' && this.options.length === 0) { this.options = [this.booleanTrueName || 'true', this.booleanFalseName || 'false']; } } public async firstUpdated(_changedProperties: Map) { super.firstUpdated(_changedProperties); // Update boolean options if they changed if (this.type === 'boolean') { this.options = [this.booleanTrueName || 'true', this.booleanFalseName || 'false']; } // Wait for the next frame to ensure DOM is fully rendered await this.updateComplete; requestAnimationFrame(() => { this.setIndicator(); }); } public async handleSelection(optionArg: string) { this.selectedOption = optionArg; this.setIndicator(); } private indicatorInitialized = false; public async setIndicator() { const indicator: HTMLDivElement = this.shadowRoot.querySelector('.indicator'); const selectedIndex = this.options.indexOf(this.selectedOption); // If no valid selection, hide indicator if (selectedIndex === -1 || !indicator) { if (indicator) { indicator.style.opacity = '0'; } return; } const option: HTMLDivElement = this.shadowRoot.querySelector( `.option:nth-child(${selectedIndex + 2})` ); if (indicator && option) { // Only disable transition for the very first positioning if (!this.indicatorInitialized) { indicator.classList.add('no-transition'); this.indicatorInitialized = true; // Remove the no-transition class after a brief delay setTimeout(() => { indicator.classList.remove('no-transition'); }, 50); } indicator.style.width = `${option.clientWidth - 8}px`; indicator.style.left = `${option.offsetLeft + 4}px`; indicator.style.opacity = '1'; } } public getValue(): string | boolean { if (this.type === 'boolean') { return this.selectedOption === this.booleanTrueName; } return this.selectedOption; } public setValue(value: string | boolean): void { if (this.type === 'boolean' && typeof value === 'boolean') { this.selectedOption = value ? (this.booleanTrueName || 'true') : (this.booleanFalseName || 'false'); } else { this.selectedOption = value as string; } if (this.hasUpdated) { this.setIndicator(); } } }