feat(dees-contextmenu,dees-appui-tabs,test): Prevent double-destruction of context menus, await window layer teardown, update destroyAll behavior, remove tabs content slot, and adjust tests

This commit is contained in:
2025-12-29 11:30:49 +00:00
parent 45a2743312
commit e45810dd06
6 changed files with 36 additions and 27 deletions

View File

@@ -1,5 +1,15 @@
# Changelog # Changelog
## 2025-12-29 - 3.7.0 - feat(dees-contextmenu,dees-appui-tabs,test)
Prevent double-destruction of context menus, await window layer teardown, update destroyAll behavior, remove tabs content slot, and adjust tests
- Add isDestroying guard in DeesContextmenu.destroy to avoid double-destruction.
- Await windowLayer.destroy() to ensure the window layer is fully torn down before continuing.
- Ensure submenu timeouts are cleared and submenu.destroy() is awaited during teardown.
- Change destroyAll to locate the root/top-level menu and destroy from the root to cascade teardown reliably.
- Remove the .content wrapper and the <slot> output from dees-appui-tabs (demo updated to render content outside the component) — this is a breaking change to the tabs API (slotted content no longer rendered).
- Increase test timeout in test.contextmenu-nested-close.browser.ts to 600ms to account for ~300ms windowLayer destruction + ~100ms context menu delay.
## 2025-12-29 - 3.6.1 - fix(readme) ## 2025-12-29 - 3.6.1 - fix(readme)
document new App UI APIs to control main/secondary menu and content tabs visibility document new App UI APIs to control main/secondary menu and content tabs visibility

View File

@@ -76,8 +76,8 @@ tap.test('should close all parent menus when clicking action in nested submenu',
expect(childItem).toBeTruthy(); expect(childItem).toBeTruthy();
childItem!.click(); childItem!.click();
// Wait for menus to close // Wait for menus to close (windowLayer destruction takes 300ms + context menu 100ms)
await new Promise(resolve => setTimeout(resolve, 200)); await new Promise(resolve => setTimeout(resolve, 600));
// Verify action was called // Verify action was called
expect(actionCalled).toEqual(true); expect(actionCalled).toEqual(true);

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@design.estate/dees-catalog', name: '@design.estate/dees-catalog',
version: '3.6.1', version: '3.7.0',
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.' description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
} }

View File

@@ -67,9 +67,8 @@ export const demoFunc = () => {
<div class="demo-container"> <div class="demo-container">
<div class="section"> <div class="section">
<div class="section-title">Horizontal Tabs with Animated Indicator</div> <div class="section-title">Horizontal Tabs with Animated Indicator</div>
<dees-appui-tabs .tabs=${horizontalTabs}> <dees-appui-tabs .tabs=${horizontalTabs}></dees-appui-tabs>
${demoContent('Select a tab to see the smooth sliding animation of the indicator. The indicator automatically adjusts its width to match the tab content with minimal padding.')} ${demoContent('Select a tab to see the smooth sliding animation of the indicator. The indicator automatically adjusts its width to match the tab content with minimal padding.')}
</dees-appui-tabs>
</div> </div>
<div class="section"> <div class="section">
@@ -82,9 +81,8 @@ export const demoFunc = () => {
<div class="section"> <div class="section">
<div class="section-title">Without Indicator</div> <div class="section-title">Without Indicator</div>
<dees-appui-tabs .showTabIndicator=${false} .tabs=${noIndicatorTabs}> <dees-appui-tabs .showTabIndicator=${false} .tabs=${noIndicatorTabs}></dees-appui-tabs>
${demoContent('Tabs can also be used without the animated indicator by setting showTabIndicator to false.')} ${demoContent('Tabs can also be used without the animated indicator by setting showTabIndicator to false.')}
</dees-appui-tabs>
</div> </div>
</div> </div>
`; `;

View File

@@ -198,19 +198,12 @@ export class DeesAppuiTabs extends DeesElement {
z-index: 1; z-index: 1;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
} }
.content {
padding: 32px 24px;
}
`, `,
]; ];
public render(): TemplateResult { public render(): TemplateResult {
return html` return html`
${this.renderTabsWrapper()} ${this.renderTabsWrapper()}
<div class="content">
<slot></slot>
</div>
`; `;
} }

View File

@@ -134,6 +134,7 @@ export class DeesContextmenu extends DeesElement {
private submenu: DeesContextmenu | null = null; private submenu: DeesContextmenu | null = null;
private submenuTimeout: any = null; private submenuTimeout: any = null;
private parentMenu: DeesContextmenu | null = null; private parentMenu: DeesContextmenu | null = null;
private isDestroying: boolean = false;
constructor() { constructor() {
super(); super();
@@ -416,27 +417,33 @@ export class DeesContextmenu extends DeesElement {
} }
public async destroy() { public async destroy() {
// Guard against double-destruction
if (this.isDestroying) {
return;
}
this.isDestroying = true;
// Clear timeout // Clear timeout
if (this.submenuTimeout) { if (this.submenuTimeout) {
clearTimeout(this.submenuTimeout); clearTimeout(this.submenuTimeout);
this.submenuTimeout = null; this.submenuTimeout = null;
} }
// Destroy submenu first // Destroy submenu first
if (this.submenu) { if (this.submenu) {
await this.submenu.destroy(); await this.submenu.destroy();
this.submenu = null; this.submenu = null;
} }
// Only destroy window layer if this is not a submenu // Only destroy window layer if this is not a submenu
if (this.windowLayer && !this.parentMenu) { if (this.windowLayer && !this.parentMenu) {
this.windowLayer.destroy(); await this.windowLayer.destroy();
} }
this.style.opacity = '0'; this.style.opacity = '0';
this.style.transform = 'scale(0.95) translateY(-10px)'; this.style.transform = 'scale(0.95) translateY(-10px)';
await domtools.plugins.smartdelay.delayFor(100); await domtools.plugins.smartdelay.delayFor(100);
if (this.parentElement) { if (this.parentElement) {
this.parentElement.removeChild(this); this.parentElement.removeChild(this);
} }
@@ -446,13 +453,14 @@ export class DeesContextmenu extends DeesElement {
* Destroys this menu and all parent menus in the chain * Destroys this menu and all parent menus in the chain
*/ */
public async destroyAll() { public async destroyAll() {
// First destroy parent menus if they exist // Find the root menu (top-level parent)
if (this.parentMenu) { let rootMenu: DeesContextmenu = this;
await this.parentMenu.destroyAll(); while (rootMenu.parentMenu) {
} else { rootMenu = rootMenu.parentMenu;
// If we're at the top level, just destroy this menu
await this.destroy();
} }
// Destroy from the root - this will cascade through all submenus
await rootMenu.destroy();
} }
} }