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();
}
}