diff --git a/changelog.md b/changelog.md index d262b53..cd70293 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2026-04-04 - 3.56.1 - fix(dees-input-dropdown) +improve dropdown popup lifecycle with window layer overlay and animated visibility transitions + +- use a window layer to handle outside-click closing instead of document-level mousedown listeners +- await popup show and search focus to keep popup initialization and overlay setup in sync +- add guarded async hide logic with animated teardown and cleanup of scroll/resize listeners + ## 2026-04-04 - 3.56.0 - feat(dees-input-dropdown) extract dropdown popup into a floating overlay component with search, keyboard navigation, and viewport repositioning diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index 6703402..fae9816 100644 --- a/ts_web/00_commitinfo_data.ts +++ b/ts_web/00_commitinfo_data.ts @@ -3,6 +3,6 @@ */ export const commitinfo = { name: '@design.estate/dees-catalog', - version: '3.56.0', + version: '3.56.1', description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.' } diff --git a/ts_web/elements/00group-input/dees-input-dropdown/dees-input-dropdown-popup.ts b/ts_web/elements/00group-input/dees-input-dropdown/dees-input-dropdown-popup.ts index f9210ff..3c71ad2 100644 --- a/ts_web/elements/00group-input/dees-input-dropdown/dees-input-dropdown-popup.ts +++ b/ts_web/elements/00group-input/dees-input-dropdown/dees-input-dropdown-popup.ts @@ -8,9 +8,11 @@ import { cssManager, DeesElement, } from '@design.estate/dees-element'; +import * as domtools from '@design.estate/dees-domtools'; import { zIndexRegistry } from '../../00zindex.js'; import { cssGeistFontFamily } from '../../00fonts.js'; import { themeDefaultStyles } from '../../00theme.js'; +import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js'; declare global { interface HTMLElementTagNameMap { @@ -47,6 +49,12 @@ export class DeesInputDropdownPopup extends DeesElement { @state() accessor menuZIndex: number = 1000; + @state() + accessor visible: boolean = false; + + private windowLayer: DeesWindowLayer | null = null; + private isDestroying: boolean = false; + public static styles = [ themeDefaultStyles, cssManager.defaultStyles, @@ -182,7 +190,7 @@ export class DeesInputDropdownPopup extends DeesElement { return html`
${this.enableSearch @@ -233,30 +241,43 @@ export class DeesInputDropdownPopup extends DeesElement { } } - public show(): void { + public async show(): Promise { this.filteredOptions = this.options; this.highlightedIndex = 0; this.searchValue = ''; + // Create window layer (transparent, no blur) + this.windowLayer = await DeesWindowLayer.createAndShow(); + this.windowLayer.addEventListener('click', () => { + this.dispatchEvent(new CustomEvent('close-request')); + }); + + // Set z-index above the window layer this.menuZIndex = zIndexRegistry.getNextZIndex(); zIndexRegistry.register(this, this.menuZIndex); this.style.zIndex = this.menuZIndex.toString(); document.body.appendChild(this); - // Add listeners + // Animate in on next frame + await domtools.plugins.smartdelay.delayFor(0); + this.visible = true; + + // Add scroll/resize listeners for repositioning window.addEventListener('scroll', this.handleScrollOrResize, { capture: true, passive: true }); window.addEventListener('resize', this.handleScrollOrResize, { passive: true }); - setTimeout(() => { - document.addEventListener('mousedown', this.handleOutsideClick); - }, 0); } - public hide(): void { - // Remove listeners + public async hide(): Promise { + // Guard against double-destruction + if (this.isDestroying) { + return; + } + this.isDestroying = true; + + // Remove scroll/resize listeners window.removeEventListener('scroll', this.handleScrollOrResize, { capture: true } as EventListenerOptions); window.removeEventListener('resize', this.handleScrollOrResize); - document.removeEventListener('mousedown', this.handleOutsideClick); zIndexRegistry.unregister(this); @@ -264,9 +285,21 @@ export class DeesInputDropdownPopup extends DeesElement { this.filteredOptions = this.options; this.highlightedIndex = 0; + // Don't await - let window layer cleanup happen in background for instant visual feedback + if (this.windowLayer) { + this.windowLayer.destroy(); + this.windowLayer = null; + } + + // Animate out via CSS transition + this.visible = false; + await domtools.plugins.smartdelay.delayFor(150); + if (this.parentElement) { this.parentElement.removeChild(this); } + + this.isDestroying = false; } public async focusSearchInput(): Promise { @@ -328,13 +361,6 @@ export class DeesInputDropdownPopup extends DeesElement { } }; - private handleOutsideClick = (event: MouseEvent): void => { - const path = event.composedPath(); - if (!path.includes(this) && (!this.ownerComponent || !path.includes(this.ownerComponent))) { - this.dispatchEvent(new CustomEvent('close-request')); - } - }; - private handleScrollOrResize = (): void => { this.dispatchEvent(new CustomEvent('reposition-request')); }; @@ -343,7 +369,6 @@ export class DeesInputDropdownPopup extends DeesElement { await super.disconnectedCallback(); window.removeEventListener('scroll', this.handleScrollOrResize, { capture: true } as EventListenerOptions); window.removeEventListener('resize', this.handleScrollOrResize); - document.removeEventListener('mousedown', this.handleOutsideClick); zIndexRegistry.unregister(this); } } diff --git a/ts_web/elements/00group-input/dees-input-dropdown/dees-input-dropdown.ts b/ts_web/elements/00group-input/dees-input-dropdown/dees-input-dropdown.ts index 7af5ddb..23bef22 100644 --- a/ts_web/elements/00group-input/dees-input-dropdown/dees-input-dropdown.ts +++ b/ts_web/elements/00group-input/dees-input-dropdown/dees-input-dropdown.ts @@ -205,12 +205,12 @@ export class DeesInputDropdown extends DeesInputBase { this.popupInstance.addEventListener('close-request', this.handleCloseRequest); this.popupInstance.addEventListener('reposition-request', this.handleRepositionRequest); - // Show popup (appends to document.body) - this.popupInstance.show(); + // Show popup (creates window layer, appends to document.body) + await this.popupInstance.show(); // Focus search input if (this.enableSearch) { - this.popupInstance.focusSearchInput(); + await this.popupInstance.focusSearchInput(); } }