From f52b9d8b728ebc67ada4165bed1c40bb639bd161 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Sat, 4 Apr 2026 23:37:28 +0000 Subject: [PATCH] feat(dees-input-dropdown): extract dropdown popup into a floating overlay component with search, keyboard navigation, and viewport repositioning --- changelog.md | 7 + ts_web/00_commitinfo_data.ts | 2 +- .../dees-input-dropdown-popup.ts | 349 +++++++++++++++++ .../dees-input-dropdown.ts | 353 +++++------------- .../dees-input-dropdown/index.ts | 1 + 5 files changed, 447 insertions(+), 265 deletions(-) create mode 100644 ts_web/elements/00group-input/dees-input-dropdown/dees-input-dropdown-popup.ts diff --git a/changelog.md b/changelog.md index f05aedc..d262b53 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 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 + +- adds a new dees-input-dropdown-popup export for rendering the menu as a fixed overlay attached to document.body +- keeps the dropdown aligned to its trigger on scroll and resize, and closes it when the trigger moves off-screen +- moves option filtering and keyboard selection handling into the popup component while preserving selection events + ## 2026-04-04 - 3.55.6 - fix(dees-heading) adjust heading hr text color to use muted theme values diff --git a/ts_web/00_commitinfo_data.ts b/ts_web/00_commitinfo_data.ts index ffccd7e..6703402 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.55.6', + version: '3.56.0', 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 new file mode 100644 index 0000000..f9210ff --- /dev/null +++ b/ts_web/elements/00group-input/dees-input-dropdown/dees-input-dropdown-popup.ts @@ -0,0 +1,349 @@ +import { + customElement, + type TemplateResult, + property, + state, + html, + css, + cssManager, + DeesElement, +} from '@design.estate/dees-element'; +import { zIndexRegistry } from '../../00zindex.js'; +import { cssGeistFontFamily } from '../../00fonts.js'; +import { themeDefaultStyles } from '../../00theme.js'; + +declare global { + interface HTMLElementTagNameMap { + 'dees-input-dropdown-popup': DeesInputDropdownPopup; + } +} + +@customElement('dees-input-dropdown-popup') +export class DeesInputDropdownPopup extends DeesElement { + @property({ type: Array }) + accessor options: { option: string; key: string; payload?: any }[] = []; + + @property({ type: Boolean }) + accessor enableSearch: boolean = true; + + @property({ type: Boolean }) + accessor opensToTop: boolean = false; + + @property({ attribute: false }) + accessor triggerRect: DOMRect | null = null; + + @property({ attribute: false }) + accessor ownerComponent: HTMLElement | null = null; + + @state() + accessor filteredOptions: { option: string; key: string; payload?: any }[] = []; + + @state() + accessor highlightedIndex: number = 0; + + @state() + accessor searchValue: string = ''; + + @state() + accessor menuZIndex: number = 1000; + + public static styles = [ + themeDefaultStyles, + cssManager.defaultStyles, + css` + :host { + position: fixed; + top: 0; + left: 0; + width: 0; + height: 0; + pointer-events: none; + font-family: ${cssGeistFontFamily}; + color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')}; + } + + * { + box-sizing: border-box; + } + + .selectionBox { + position: fixed; + pointer-events: auto; + will-change: transform, opacity; + transition: all 0.15s ease; + opacity: 0; + transform: translateY(-8px) scale(0.98); + background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')}; + border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; + box-shadow: 0 4px 6px -1px hsl(0 0% 0% / 0.1), 0 2px 4px -2px hsl(0 0% 0% / 0.1); + min-height: 40px; + max-height: 300px; + overflow: hidden; + border-radius: 6px; + user-select: none; + } + + .selectionBox.top { + transform: translateY(8px) scale(0.98); + } + + .selectionBox.show { + pointer-events: auto; + transform: translateY(0) scale(1); + opacity: 1; + } + + .options-container { + max-height: 250px; + overflow-y: auto; + padding: 4px; + } + + .option { + transition: all 0.15s ease; + line-height: 32px; + padding: 0 8px; + border-radius: 4px; + margin: 2px 0; + cursor: pointer; + font-size: 14px; + color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')}; + } + + .option.highlighted { + background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')}; + } + + .option:hover { + background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')}; + color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; + } + + .no-options { + padding: 8px; + text-align: center; + font-size: 14px; + color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')}; + font-style: italic; + } + + .search { + padding: 4px; + border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; + margin-bottom: 4px; + } + + .search input { + display: block; + width: 100%; + height: 32px; + padding: 0 8px; + background: transparent; + border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; + border-radius: 4px; + color: inherit; + font-size: 14px; + font-family: inherit; + outline: none; + transition: border-color 0.15s ease; + } + + .search input::placeholder { + color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')}; + } + + .search input:focus { + border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')}; + } + + .options-container::-webkit-scrollbar { + width: 8px; + } + + .options-container::-webkit-scrollbar-track { + background: transparent; + } + + .options-container::-webkit-scrollbar-thumb { + background: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; + border-radius: 4px; + } + + .options-container::-webkit-scrollbar-thumb:hover { + background: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')}; + } + `, + ]; + + public render(): TemplateResult { + if (!this.triggerRect) return html``; + + const posStyle = this.computePositionStyle(); + + return html` +
+ ${this.enableSearch + ? html` + + ` + : null} +
+ ${this.filteredOptions.length === 0 + ? html`
No options found
` + : this.filteredOptions.map((option, index) => { + const isHighlighted = this.highlightedIndex === index; + return html` +
+ ${option.option} +
+ `; + })} +
+
+ `; + } + + private computePositionStyle(): string { + const rect = this.triggerRect!; + const left = rect.left; + const width = rect.width; + + if (this.opensToTop) { + const bottom = window.innerHeight - rect.top + 4; + return `left: ${left}px; width: ${width}px; bottom: ${bottom}px; top: auto`; + } else { + const top = rect.bottom + 4; + return `left: ${left}px; width: ${width}px; top: ${top}px`; + } + } + + public show(): void { + this.filteredOptions = this.options; + this.highlightedIndex = 0; + this.searchValue = ''; + + this.menuZIndex = zIndexRegistry.getNextZIndex(); + zIndexRegistry.register(this, this.menuZIndex); + this.style.zIndex = this.menuZIndex.toString(); + + document.body.appendChild(this); + + // Add listeners + 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 + window.removeEventListener('scroll', this.handleScrollOrResize, { capture: true } as EventListenerOptions); + window.removeEventListener('resize', this.handleScrollOrResize); + document.removeEventListener('mousedown', this.handleOutsideClick); + + zIndexRegistry.unregister(this); + + this.searchValue = ''; + this.filteredOptions = this.options; + this.highlightedIndex = 0; + + if (this.parentElement) { + this.parentElement.removeChild(this); + } + } + + public async focusSearchInput(): Promise { + await this.updateComplete; + const input = this.shadowRoot!.querySelector('.search input') as HTMLInputElement; + if (input) input.focus(); + } + + public updateOptions(options: { option: string; key: string; payload?: any }[]): void { + this.options = options; + // Re-filter with current search value + if (this.searchValue) { + const searchLower = this.searchValue.toLowerCase(); + this.filteredOptions = this.options.filter((opt) => + opt.option.toLowerCase().includes(searchLower) + ); + } else { + this.filteredOptions = this.options; + } + this.highlightedIndex = 0; + } + + private selectOption(option: { option: string; key: string; payload?: any }): void { + this.dispatchEvent( + new CustomEvent('option-selected', { + detail: option, + }) + ); + } + + private handleSearch = (event: Event): void => { + const searchTerm = (event.target as HTMLInputElement).value; + this.searchValue = searchTerm; + const searchLower = searchTerm.toLowerCase(); + this.filteredOptions = this.options.filter((option) => + option.option.toLowerCase().includes(searchLower) + ); + this.highlightedIndex = 0; + }; + + private handleSearchKeydown = (event: KeyboardEvent): void => { + const key = event.key; + const maxIndex = this.filteredOptions.length - 1; + + if (key === 'ArrowDown') { + event.preventDefault(); + this.highlightedIndex = this.highlightedIndex + 1 > maxIndex ? 0 : this.highlightedIndex + 1; + } else if (key === 'ArrowUp') { + event.preventDefault(); + this.highlightedIndex = this.highlightedIndex - 1 < 0 ? maxIndex : this.highlightedIndex - 1; + } else if (key === 'Enter') { + event.preventDefault(); + if (this.filteredOptions[this.highlightedIndex]) { + this.selectOption(this.filteredOptions[this.highlightedIndex]); + } + } else if (key === 'Escape') { + event.preventDefault(); + this.dispatchEvent(new CustomEvent('close-request')); + } + }; + + 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')); + }; + + async disconnectedCallback() { + 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 4bd0301..7af5ddb 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 @@ -7,11 +7,11 @@ import { css, cssManager, } from '@design.estate/dees-element'; -import * as domtools from '@design.estate/dees-domtools'; import { demoFunc } from './dees-input-dropdown.demo.js'; import { DeesInputBase } from '../dees-input-base/dees-input-base.js'; import { cssGeistFontFamily } from '../../00fonts.js'; import { themeDefaultStyles } from '../../00theme.js'; +import { DeesInputDropdownPopup } from './dees-input-dropdown-popup.js'; declare global { interface HTMLElementTagNameMap { @@ -46,27 +46,16 @@ export class DeesInputDropdown extends DeesInputBase { }) accessor enableSearch: boolean = true; - @state() - accessor opensToTop: boolean = false; - - @state() - accessor filteredOptions: { option: string; key: string; payload?: any }[] = []; - - @state() - accessor highlightedIndex: number = 0; - @state() accessor isOpened = false; - @state() - accessor searchValue: string = ''; + private popupInstance: DeesInputDropdownPopup | null = null; public static styles = [ themeDefaultStyles, ...DeesInputBase.baseStyles, cssManager.defaultStyles, css` - /* TODO: Migrate hardcoded values to --dees-* CSS variables */ * { box-sizing: border-box; } @@ -137,137 +126,6 @@ export class DeesInputDropdown extends DeesInputBase { .selectedBox.open::after { transform: translateY(-50%) rotate(180deg); } - - .selectionBox { - will-change: transform, opacity; - pointer-events: none; - transition: all 0.15s ease; - opacity: 0; - transform: translateY(-8px) scale(0.98); - background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')}; - border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; - box-shadow: 0 4px 6px -1px hsl(0 0% 0% / 0.1), 0 2px 4px -2px hsl(0 0% 0% / 0.1); - min-height: 40px; - max-height: 300px; - overflow: hidden; - border-radius: 6px; - position: absolute; - user-select: none; - margin-top: 4px; - z-index: 50; - left: 0; - right: 0; - } - - .selectionBox.top { - bottom: calc(100% + 4px); - top: auto; - margin-top: 0; - margin-bottom: 4px; - transform: translateY(8px) scale(0.98); - } - - .selectionBox.bottom { - top: 100%; - } - - .selectionBox.show { - pointer-events: all; - transform: translateY(0) scale(1); - opacity: 1; - } - - /* Options container */ - .options-container { - max-height: 250px; - overflow-y: auto; - padding: 4px; - } - - /* Options */ - .option { - transition: all 0.15s ease; - line-height: 32px; - padding: 0 8px; - border-radius: 4px; - margin: 2px 0; - cursor: pointer; - font-size: 14px; - color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')}; - } - - .option.highlighted { - background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')}; - } - - .option:hover { - background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')}; - color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')}; - } - - /* No options message */ - .no-options { - padding: 8px; - text-align: center; - font-size: 14px; - color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')}; - font-style: italic; - } - - /* Search */ - .search { - padding: 4px; - border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; - margin-bottom: 4px; - } - - .search.bottom { - border-bottom: none; - border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; - margin-bottom: 0; - margin-top: 4px; - } - - .search input { - display: block; - width: 100%; - height: 32px; - padding: 0 8px; - background: transparent; - border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; - border-radius: 4px; - color: inherit; - font-size: 14px; - font-family: inherit; - outline: none; - transition: border-color 0.15s ease; - } - - .search input::placeholder { - color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')}; - } - - .search input:focus { - border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')}; - } - - /* Scrollbar styling */ - .options-container::-webkit-scrollbar { - width: 8px; - } - - .options-container::-webkit-scrollbar-track { - background: transparent; - } - - .options-container::-webkit-scrollbar-thumb { - background: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')}; - border-radius: 4px; - } - - .options-container::-webkit-scrollbar-thumb:hover { - background: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')}; - } `, ]; @@ -284,68 +142,26 @@ export class DeesInputDropdown extends DeesInputBase { > ${this.selectedOption?.option || 'Select an option'} -
- ${this.enableSearch - ? html` - - ` - : null} -
- ${this.filteredOptions.length === 0 - ? html`
No options found
` - : this.filteredOptions.map((option, index) => { - const isHighlighted = this.highlightedIndex === index; - return html` -
- ${option.option} -
- `; - }) - } -
-
`; } - async connectedCallback() { - super.connectedCallback(); - this.handleClickOutside = this.handleClickOutside.bind(this); - } - firstUpdated() { this.selectedOption = this.selectedOption || null; - this.filteredOptions = this.options; } updated(changedProperties: Map) { super.updated(changedProperties); - - if (changedProperties.has('options')) { - this.filteredOptions = this.options; + + if (changedProperties.has('options') && this.popupInstance && this.isOpened) { + this.popupInstance.updateOptions(this.options); } } public async updateSelection(selectedOption: { option: string; key: string; payload?: any }) { this.selectedOption = selectedOption; - this.isOpened = false; - this.searchValue = ''; - this.filteredOptions = this.options; - this.highlightedIndex = 0; + this.closePopup(); this.dispatchEvent( new CustomEvent('selectedOption', { @@ -353,92 +169,95 @@ export class DeesInputDropdown extends DeesInputBase { bubbles: true, }) ); - + this.changeSubject.next(this); } - private handleClickOutside = (event: MouseEvent) => { - const path = event.composedPath(); - if (!path.includes(this)) { - this.isOpened = false; - this.searchValue = ''; - this.filteredOptions = this.options; - document.removeEventListener('click', this.handleClickOutside); - } - }; - public async toggleSelectionBox() { - this.isOpened = !this.isOpened; - if (this.isOpened) { - // Check available space and set position - const selectedBox = this.shadowRoot!.querySelector('.selectedBox') as HTMLElement; - const rect = selectedBox.getBoundingClientRect(); - const spaceBelow = window.innerHeight - rect.bottom; - const spaceAbove = rect.top; - - // Determine if we should open upwards - this.opensToTop = spaceBelow < 300 && spaceAbove > spaceBelow; - - // Focus search input if present - await this.updateComplete; - const searchInput = this.shadowRoot!.querySelector('.search input') as HTMLInputElement; - if (searchInput) { - searchInput.focus(); - } - - // Add click outside listener - setTimeout(() => { - document.addEventListener('click', this.handleClickOutside); - }, 0); - } else { - // Cleanup - this.searchValue = ''; - this.filteredOptions = this.options; - document.removeEventListener('click', this.handleClickOutside); + this.closePopup(); + return; + } + + this.isOpened = true; + + // Get trigger position + const selectedBox = this.shadowRoot!.querySelector('.selectedBox') as HTMLElement; + const rect = selectedBox.getBoundingClientRect(); + const spaceBelow = window.innerHeight - rect.bottom; + const spaceAbove = rect.top; + const opensToTop = spaceBelow < 300 && spaceAbove > spaceBelow; + + // Create popup if needed + if (!this.popupInstance) { + this.popupInstance = new DeesInputDropdownPopup(); + } + + // Configure popup + this.popupInstance.options = this.options; + this.popupInstance.enableSearch = this.enableSearch; + this.popupInstance.opensToTop = opensToTop; + this.popupInstance.triggerRect = rect; + this.popupInstance.ownerComponent = this; + + // Listen for popup events + this.popupInstance.addEventListener('option-selected', this.handleOptionSelected); + this.popupInstance.addEventListener('close-request', this.handleCloseRequest); + this.popupInstance.addEventListener('reposition-request', this.handleRepositionRequest); + + // Show popup (appends to document.body) + this.popupInstance.show(); + + // Focus search input + if (this.enableSearch) { + this.popupInstance.focusSearchInput(); } } - private handleSearch(event: Event): void { - const searchTerm = (event.target as HTMLInputElement).value; - this.searchValue = searchTerm; - const searchLower = searchTerm.toLowerCase(); - this.filteredOptions = this.options.filter((option) => - option.option.toLowerCase().includes(searchLower) - ); - this.highlightedIndex = 0; - } + private closePopup(): void { + this.isOpened = false; - private handleKeyDown(event: KeyboardEvent): void { - const key = event.key; - const maxIndex = this.filteredOptions.length - 1; - - if (key === 'ArrowDown') { - event.preventDefault(); - this.highlightedIndex = this.highlightedIndex + 1 > maxIndex ? 0 : this.highlightedIndex + 1; - } else if (key === 'ArrowUp') { - event.preventDefault(); - this.highlightedIndex = this.highlightedIndex - 1 < 0 ? maxIndex : this.highlightedIndex - 1; - } else if (key === 'Enter') { - event.preventDefault(); - if (this.filteredOptions[this.highlightedIndex]) { - this.updateSelection(this.filteredOptions[this.highlightedIndex]); - } - } else if (key === 'Escape') { - event.preventDefault(); - this.isOpened = false; + if (this.popupInstance) { + this.popupInstance.removeEventListener('option-selected', this.handleOptionSelected); + this.popupInstance.removeEventListener('close-request', this.handleCloseRequest); + this.popupInstance.removeEventListener('reposition-request', this.handleRepositionRequest); + this.popupInstance.hide(); } } - private handleSearchKeydown(event: KeyboardEvent): void { - if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') { - this.handleKeyDown(event); + private handleOptionSelected = (event: Event): void => { + const detail = (event as CustomEvent).detail; + this.updateSelection(detail); + }; + + private handleCloseRequest = (): void => { + this.closePopup(); + }; + + private handleRepositionRequest = (): void => { + if (!this.popupInstance || !this.isOpened) return; + + const selectedBox = this.shadowRoot!.querySelector('.selectedBox') as HTMLElement; + if (!selectedBox) return; + + const rect = selectedBox.getBoundingClientRect(); + + // Close if trigger scrolled off-screen + if (rect.bottom < 0 || rect.top > window.innerHeight) { + this.closePopup(); + return; } - } + + // Update position + const spaceBelow = window.innerHeight - rect.bottom; + const spaceAbove = rect.top; + this.popupInstance.opensToTop = spaceBelow < 300 && spaceAbove > spaceBelow; + this.popupInstance.triggerRect = rect; + }; private handleSelectedBoxKeydown(event: KeyboardEvent) { if (this.disabled) return; - + if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); this.toggleSelectionBox(); @@ -450,7 +269,7 @@ export class DeesInputDropdown extends DeesInputBase { } else if (event.key === 'Escape') { event.preventDefault(); if (this.isOpened) { - this.isOpened = false; + this.closePopup(); } } } @@ -462,9 +281,15 @@ export class DeesInputDropdown extends DeesInputBase { public setValue(value: { option: string; key: string; payload?: any }): void { this.selectedOption = value; } - + async disconnectedCallback() { await super.disconnectedCallback(); - document.removeEventListener('click', this.handleClickOutside); + if (this.popupInstance) { + this.popupInstance.removeEventListener('option-selected', this.handleOptionSelected); + this.popupInstance.removeEventListener('close-request', this.handleCloseRequest); + this.popupInstance.removeEventListener('reposition-request', this.handleRepositionRequest); + this.popupInstance.hide(); + this.popupInstance = null; + } } -} \ No newline at end of file +} diff --git a/ts_web/elements/00group-input/dees-input-dropdown/index.ts b/ts_web/elements/00group-input/dees-input-dropdown/index.ts index 6bb5639..e0fa59e 100644 --- a/ts_web/elements/00group-input/dees-input-dropdown/index.ts +++ b/ts_web/elements/00group-input/dees-input-dropdown/index.ts @@ -1 +1,2 @@ export * from './dees-input-dropdown.js'; +export * from './dees-input-dropdown-popup.js';