Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c1456c0c1 | |||
| cc71f232d2 | |||
| 8a4d69694c | |||
| e45810dd06 | |||
| 45a2743312 | |||
| c5b50f3eb0 | |||
| aedd4a041c | |||
| 1f37474d3f | |||
| 76748a81c9 | |||
| 1e432ca92e |
41
changelog.md
41
changelog.md
@@ -1,5 +1,46 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-12-29 - 3.7.1 - fix(dees-appui-maincontent)
|
||||||
|
migrate main content layout to CSS Grid and enable topbar collapse transitions
|
||||||
|
|
||||||
|
- Replace absolute positioning with CSS Grid on :host and .maincontainer to enable natural document flow
|
||||||
|
- Make .topbar a grid and animate collapse via grid-template-rows; switch :host([notabs]) to grid-template-rows: 0fr instead of display:none to allow transitions
|
||||||
|
- Set .maincontainer to display:contents and add min-height:0 on content areas and topbar children to fix overflow/scrolling and flex/grid height issues
|
||||||
|
- Remove positional styles (position:absolute/top/left/right/bottom) so content scrolls correctly and layout is more robust
|
||||||
|
|
||||||
|
## 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)
|
||||||
|
document new App UI APIs to control main/secondary menu and content tabs visibility
|
||||||
|
|
||||||
|
- Added docs for setMainMenuCollapsed(), setMainMenuVisible(), setSecondaryMenuCollapsed(), setSecondaryMenuVisible(), and setContentTabsVisible() programmatic APIs.
|
||||||
|
- Included a TypeScript example showing how to hide secondary menu, hide content tabs, and collapse the main menu in a view's onActivate hook.
|
||||||
|
|
||||||
|
## 2025-12-29 - 3.6.0 - feat(dees-appui)
|
||||||
|
add visibility toggles for main/secondary menus and ability to show/hide content tabs; expose corresponding setters and appconfig entries
|
||||||
|
|
||||||
|
- ts_web/elements/00group-appui/dees-appui-base: added boolean properties mainmenuVisible, secondarymenuVisible, maincontentTabsVisible; render main and secondary menus conditionally; pass showTabs to dees-appui-maincontent; added setter methods: setMainMenuVisible, setSecondaryMenuCollapsed, setSecondaryMenuVisible, setContentTabsVisible.
|
||||||
|
- ts_web/elements/00group-appui/dees-appui-maincontent: added showTabs property, support for a notabs attribute via styles, updated() and firstUpdated() to apply notabs state so tabs can be hidden/shown dynamically.
|
||||||
|
- ts_web/elements/interfaces/appconfig.ts: expanded appconfig interface to include setMainMenuVisible, setSecondaryMenuCollapsed, setSecondaryMenuVisible, setContentTabsVisible so host app can control visibility programmatically.
|
||||||
|
- No breaking changes: defaults preserve existing behavior (menus and tabs remain visible by default).
|
||||||
|
|
||||||
|
## 2025-12-29 - 3.5.1 - fix(dees-appui-view)
|
||||||
|
remove DeesAppuiView component, its demo, documentation snippet, and related exports
|
||||||
|
|
||||||
|
- Deleted component implementation: ts_web/elements/00group-appui/dees-appui-view/dees-appui-view.ts
|
||||||
|
- Deleted demo file: ts_web/elements/00group-appui/dees-appui-view/dees-appui-view.demo.ts
|
||||||
|
- Removed index re-export: ts_web/elements/00group-appui/dees-appui-view/index.ts (deleted) and removed export from ts_web/elements/00group-appui/index.ts
|
||||||
|
- Removed documentation section for DeesAppuiView from readme.md
|
||||||
|
- Breaking change: any consumers using the <dees-appui-view> component or its public API (selectTab, getMenuItems, getTabs) must be migrated to alternate components/approach
|
||||||
|
|
||||||
## 2025-12-29 - 3.5.0 - feat(theme,interfaces)
|
## 2025-12-29 - 3.5.0 - feat(theme,interfaces)
|
||||||
Introduce a global theming system and unify menu/tab interfaces; migrate components to use themeDefaultStyles and update APIs accordingly
|
Introduce a global theming system and unify menu/tab interfaces; migrate components to use themeDefaultStyles and update APIs accordingly
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@design.estate/dees-catalog",
|
"name": "@design.estate/dees-catalog",
|
||||||
"version": "3.5.0",
|
"version": "3.7.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"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.",
|
||||||
"main": "dist_ts_web/index.js",
|
"main": "dist_ts_web/index.js",
|
||||||
|
|||||||
46
readme.md
46
readme.md
@@ -54,7 +54,7 @@ For developers working on this library, please refer to the [UI Components Playb
|
|||||||
|----------|------------|
|
|----------|------------|
|
||||||
| **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols) |
|
| **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols) |
|
||||||
| **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesFormSubmit`](#deesformsubmit) |
|
| **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesFormSubmit`](#deesformsubmit) |
|
||||||
| **Layout** | [`DeesAppuiBase`](#deesappuibase), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesAppuiView`](#deesappuiview), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
|
| **Layout** | [`DeesAppuiBase`](#deesappuibase), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
|
||||||
| **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination) |
|
| **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination) |
|
||||||
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) |
|
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) |
|
||||||
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
|
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
|
||||||
@@ -666,9 +666,26 @@ class MyApp extends DeesElement {
|
|||||||
- `navigateToView(viewId, params?)` - Navigate between views
|
- `navigateToView(viewId, params?)` - Navigate between views
|
||||||
- `setAppBarMenus()`, `setBreadcrumbs()`, `setUser()` - Control the app bar
|
- `setAppBarMenus()`, `setBreadcrumbs()`, `setUser()` - Control the app bar
|
||||||
- `setMainMenu()`, `setMainMenuSelection()`, `setMainMenuBadge()` - Control main navigation
|
- `setMainMenu()`, `setMainMenuSelection()`, `setMainMenuBadge()` - Control main navigation
|
||||||
- `setSecondaryMenu()`, `setContentTabs()` - Control view-specific UI
|
- `setMainMenuCollapsed()`, `setMainMenuVisible()` - Control main menu visibility
|
||||||
|
- `setSecondaryMenu()`, `setSecondaryMenuCollapsed()`, `setSecondaryMenuVisible()` - Control secondary menu
|
||||||
|
- `setContentTabs()`, `setContentTabsVisible()` - Control view-specific UI
|
||||||
- `activityLog.add()`, `activityLog.addMany()`, `activityLog.clear()` - Manage activity entries
|
- `activityLog.add()`, `activityLog.addMany()`, `activityLog.clear()` - Manage activity entries
|
||||||
|
|
||||||
|
**View Visibility Control:**
|
||||||
|
```typescript
|
||||||
|
// In your view's onActivate hook
|
||||||
|
onActivate(context: IViewActivationContext) {
|
||||||
|
// Hide secondary menu for a fullscreen view
|
||||||
|
context.appui.setSecondaryMenuVisible(false);
|
||||||
|
|
||||||
|
// Hide content tabs
|
||||||
|
context.appui.setContentTabsVisible(false);
|
||||||
|
|
||||||
|
// Collapse main menu to give more space
|
||||||
|
context.appui.setMainMenuCollapsed(true);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### `DeesAppuiMainmenu`
|
#### `DeesAppuiMainmenu`
|
||||||
Main navigation menu component for application-wide navigation.
|
Main navigation menu component for application-wide navigation.
|
||||||
|
|
||||||
@@ -775,23 +792,6 @@ Professional application bar component with hierarchical menus, breadcrumb navig
|
|||||||
- **User Account Section** - Avatar with status indicator
|
- **User Account Section** - Avatar with status indicator
|
||||||
- **Accessibility** - Full ARIA support with menubar roles
|
- **Accessibility** - Full ARIA support with menubar roles
|
||||||
|
|
||||||
#### `DeesAppuiView`
|
|
||||||
View wrapper component for single-view layouts with internal tabs.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
<dees-appui-view
|
|
||||||
.viewConfig=${{
|
|
||||||
id: 'settings',
|
|
||||||
name: 'Settings',
|
|
||||||
tabs: [
|
|
||||||
{ key: 'General', iconName: 'lucide:settings', action: () => {}, content: html`<div>General settings</div>` },
|
|
||||||
{ key: 'Security', iconName: 'lucide:shield', action: () => {}, content: html`<div>Security settings</div>` }
|
|
||||||
]
|
|
||||||
}}
|
|
||||||
.paddingPx=${16} // Default padding for tab content
|
|
||||||
></dees-appui-view>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `DeesAppuiTabs`
|
#### `DeesAppuiTabs`
|
||||||
Reusable tab component with horizontal/vertical layout support.
|
Reusable tab component with horizontal/vertical layout support.
|
||||||
|
|
||||||
@@ -1234,14 +1234,6 @@ interface IMenuItem {
|
|||||||
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extended tab interface for views with content
|
|
||||||
interface IViewTab extends IMenuItem {
|
|
||||||
content?: TemplateResult | (() => TemplateResult);
|
|
||||||
useSlotName?: boolean;
|
|
||||||
slotName?: string;
|
|
||||||
paddingPx?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Menu group interface for organized menus
|
// Menu group interface for organized menus
|
||||||
interface IMenuGroup {
|
interface IMenuGroup {
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-catalog',
|
name: '@design.estate/dees-catalog',
|
||||||
version: '3.5.0',
|
version: '3.7.1',
|
||||||
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.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,16 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
@property({ type: Boolean })
|
@property({ type: Boolean })
|
||||||
accessor secondarymenuCollapsed: boolean = false;
|
accessor secondarymenuCollapsed: boolean = false;
|
||||||
|
|
||||||
|
// Visibility states
|
||||||
|
@property({ type: Boolean })
|
||||||
|
accessor mainmenuVisible: boolean = true;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
accessor secondarymenuVisible: boolean = true;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
accessor maincontentTabsVisible: boolean = true;
|
||||||
|
|
||||||
// Properties for maincontent
|
// Properties for maincontent
|
||||||
@property({ type: Array })
|
@property({ type: Array })
|
||||||
accessor maincontentTabs: interfaces.IMenuItem[] = [];
|
accessor maincontentTabs: interfaces.IMenuItem[] = [];
|
||||||
@@ -213,28 +223,33 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
@profile-menu-select=${(e: CustomEvent) => this.handleAppbarProfileMenuSelect(e)}
|
@profile-menu-select=${(e: CustomEvent) => this.handleAppbarProfileMenuSelect(e)}
|
||||||
></dees-appui-appbar>
|
></dees-appui-appbar>
|
||||||
<div class="maingrid">
|
<div class="maingrid">
|
||||||
<dees-appui-mainmenu
|
${this.mainmenuVisible ? html`
|
||||||
.logoIcon=${this.mainmenuLogoIcon}
|
<dees-appui-mainmenu
|
||||||
.logoText=${this.mainmenuLogoText}
|
.logoIcon=${this.mainmenuLogoIcon}
|
||||||
.menuGroups=${this.mainmenuGroups}
|
.logoText=${this.mainmenuLogoText}
|
||||||
.bottomTabs=${this.mainmenuBottomTabs}
|
.menuGroups=${this.mainmenuGroups}
|
||||||
.tabs=${this.mainmenuTabs}
|
.bottomTabs=${this.mainmenuBottomTabs}
|
||||||
.selectedTab=${this.mainmenuSelectedTab}
|
.tabs=${this.mainmenuTabs}
|
||||||
.collapsed=${this.mainmenuCollapsed}
|
.selectedTab=${this.mainmenuSelectedTab}
|
||||||
@tab-select=${(e: CustomEvent) => this.handleMainmenuTabSelect(e)}
|
.collapsed=${this.mainmenuCollapsed}
|
||||||
@collapse-change=${(e: CustomEvent) => this.handleMainmenuCollapseChange(e)}
|
@tab-select=${(e: CustomEvent) => this.handleMainmenuTabSelect(e)}
|
||||||
></dees-appui-mainmenu>
|
@collapse-change=${(e: CustomEvent) => this.handleMainmenuCollapseChange(e)}
|
||||||
<dees-appui-secondarymenu
|
></dees-appui-mainmenu>
|
||||||
.heading=${this.secondarymenuHeading}
|
` : ''}
|
||||||
.groups=${this.secondarymenuGroups}
|
${this.secondarymenuVisible ? html`
|
||||||
.selectedItem=${this.secondarymenuSelectedItem}
|
<dees-appui-secondarymenu
|
||||||
.collapsed=${this.secondarymenuCollapsed}
|
.heading=${this.secondarymenuHeading}
|
||||||
@item-select=${(e: CustomEvent) => this.handleSecondarymenuItemSelect(e)}
|
.groups=${this.secondarymenuGroups}
|
||||||
@collapse-change=${(e: CustomEvent) => this.handleSecondarymenuCollapseChange(e)}
|
.selectedItem=${this.secondarymenuSelectedItem}
|
||||||
></dees-appui-secondarymenu>
|
.collapsed=${this.secondarymenuCollapsed}
|
||||||
|
@item-select=${(e: CustomEvent) => this.handleSecondarymenuItemSelect(e)}
|
||||||
|
@collapse-change=${(e: CustomEvent) => this.handleSecondarymenuCollapseChange(e)}
|
||||||
|
></dees-appui-secondarymenu>
|
||||||
|
` : ''}
|
||||||
<dees-appui-maincontent
|
<dees-appui-maincontent
|
||||||
.tabs=${this.maincontentTabs}
|
.tabs=${this.maincontentTabs}
|
||||||
.selectedTab=${this.maincontentSelectedTab}
|
.selectedTab=${this.maincontentSelectedTab}
|
||||||
|
.showTabs=${this.maincontentTabsVisible}
|
||||||
@tab-select=${(e: CustomEvent) => this.handleContentTabSelect(e)}
|
@tab-select=${(e: CustomEvent) => this.handleContentTabSelect(e)}
|
||||||
>
|
>
|
||||||
<div class="view-container"></div>
|
<div class="view-container"></div>
|
||||||
@@ -425,6 +440,34 @@ export class DeesAppuiBase extends DeesElement {
|
|||||||
this.mainmenuCollapsed = collapsed;
|
this.mainmenuCollapsed = collapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set main menu visibility
|
||||||
|
*/
|
||||||
|
public setMainMenuVisible(visible: boolean): void {
|
||||||
|
this.mainmenuVisible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set secondary menu collapsed state
|
||||||
|
*/
|
||||||
|
public setSecondaryMenuCollapsed(collapsed: boolean): void {
|
||||||
|
this.secondarymenuCollapsed = collapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set secondary menu visibility
|
||||||
|
*/
|
||||||
|
public setSecondaryMenuVisible(visible: boolean): void {
|
||||||
|
this.secondarymenuVisible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set content tabs visibility
|
||||||
|
*/
|
||||||
|
public setContentTabsVisible(visible: boolean): void {
|
||||||
|
this.maincontentTabsVisible = visible;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a badge on a main menu item
|
* Set a badge on a main menu item
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ export class DeesAppuiMaincontent extends DeesElement {
|
|||||||
@property({ type: Object })
|
@property({ type: Object })
|
||||||
accessor selectedTab: interfaces.IMenuItem | null = null;
|
accessor selectedTab: interfaces.IMenuItem | null = null;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
accessor showTabs: boolean = true;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
themeDefaultStyles,
|
themeDefaultStyles,
|
||||||
cssManager.defaultStyles,
|
cssManager.defaultStyles,
|
||||||
@@ -50,33 +53,36 @@ export class DeesAppuiMaincontent extends DeesElement {
|
|||||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||||
:host {
|
:host {
|
||||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
color: ${cssManager.bdTheme('#333', '#fff')};
|
||||||
display: block;
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
position: relative;
|
|
||||||
background: ${cssManager.bdTheme('#ffffff', '#161616')};
|
background: ${cssManager.bdTheme('#ffffff', '#161616')};
|
||||||
}
|
}
|
||||||
|
|
||||||
.maincontainer {
|
.maincontainer {
|
||||||
position: absolute;
|
display: contents;
|
||||||
height: 100%;
|
|
||||||
right: 0px;
|
|
||||||
top: 0px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar {
|
.topbar {
|
||||||
position: absolute;
|
display: grid;
|
||||||
width: 100%;
|
grid-template-rows: 1fr;
|
||||||
|
overflow: hidden;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
transition: grid-template-rows 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar > * {
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-area {
|
.content-area {
|
||||||
position: absolute;
|
|
||||||
top: 60px;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([notabs]) .topbar {
|
||||||
|
grid-template-rows: 0fr;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
];
|
];
|
||||||
@@ -112,8 +118,23 @@ export class DeesAppuiMaincontent extends DeesElement {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updated(changedProperties: Map<string | number | symbol, unknown>) {
|
||||||
|
super.updated(changedProperties);
|
||||||
|
if (changedProperties.has('showTabs')) {
|
||||||
|
if (this.showTabs) {
|
||||||
|
this.removeAttribute('notabs');
|
||||||
|
} else {
|
||||||
|
this.setAttribute('notabs', '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
|
async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
|
||||||
await super.firstUpdated(_changedProperties);
|
await super.firstUpdated(_changedProperties);
|
||||||
|
// Apply initial notabs state
|
||||||
|
if (!this.showTabs) {
|
||||||
|
this.setAttribute('notabs', '');
|
||||||
|
}
|
||||||
// Tab selection is now handled by the dees-appui-tabs component
|
// Tab selection is now handled by the dees-appui-tabs component
|
||||||
// But we need to ensure the tabs component is ready
|
// But we need to ensure the tabs component is ready
|
||||||
const tabsComponent = this.shadowRoot.querySelector('dees-appui-tabs') as DeesAppuiTabs;
|
const tabsComponent = this.shadowRoot.querySelector('dees-appui-tabs') as DeesAppuiTabs;
|
||||||
|
|||||||
@@ -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>
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -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>
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
import { html } from '@design.estate/dees-element';
|
|
||||||
|
|
||||||
export const demoFunc = () => html`
|
|
||||||
<dees-appui-view
|
|
||||||
.viewConfig=${{
|
|
||||||
id: 'demo-view',
|
|
||||||
name: 'Demo View',
|
|
||||||
description: 'A demonstration view',
|
|
||||||
iconName: 'lucide:home',
|
|
||||||
tabs: [
|
|
||||||
{
|
|
||||||
key: 'overview',
|
|
||||||
iconName: 'lucide:lineChart',
|
|
||||||
action: () => console.log('Overview tab'),
|
|
||||||
content: html`<div style="padding: 20px;">Overview Content</div>`
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'details',
|
|
||||||
iconName: 'lucide:fileText',
|
|
||||||
action: () => console.log('Details tab'),
|
|
||||||
content: html`<div style="padding: 20px;">Details Content</div>`
|
|
||||||
}
|
|
||||||
],
|
|
||||||
menuItems: [
|
|
||||||
{ key: 'General', action: () => console.log('General') },
|
|
||||||
{ key: 'Advanced', action: () => console.log('Advanced') },
|
|
||||||
]
|
|
||||||
}}
|
|
||||||
></dees-appui-view>
|
|
||||||
`;
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
import * as interfaces from '../../interfaces/index.js';
|
|
||||||
|
|
||||||
import {
|
|
||||||
DeesElement,
|
|
||||||
type TemplateResult,
|
|
||||||
property,
|
|
||||||
customElement,
|
|
||||||
html,
|
|
||||||
css,
|
|
||||||
cssManager,
|
|
||||||
state,
|
|
||||||
} from '@design.estate/dees-element';
|
|
||||||
|
|
||||||
import '../dees-appui-tabs/dees-appui-tabs.js';
|
|
||||||
import type { DeesAppuiTabs } from '../dees-appui-tabs/dees-appui-tabs.js';
|
|
||||||
import { demoFunc } from './dees-appui-view.demo.js';
|
|
||||||
import { themeDefaultStyles } from '../../00theme.js';
|
|
||||||
|
|
||||||
export interface IViewTab extends interfaces.IMenuItem {
|
|
||||||
content?: TemplateResult | (() => TemplateResult);
|
|
||||||
useSlotName?: boolean;
|
|
||||||
slotName?: string;
|
|
||||||
paddingPx?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IAppView {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
description?: string;
|
|
||||||
iconName?: string;
|
|
||||||
tabs: IViewTab[];
|
|
||||||
menuItems?: interfaces.IMenuItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
@customElement('dees-appui-view')
|
|
||||||
export class DeesAppuiView extends DeesElement {
|
|
||||||
public static demo = demoFunc;
|
|
||||||
|
|
||||||
// INSTANCE
|
|
||||||
@property({ type: Object })
|
|
||||||
accessor viewConfig: IAppView;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
accessor selectedTab: IViewTab | null = null;
|
|
||||||
|
|
||||||
@state()
|
|
||||||
accessor tabs: DeesAppuiTabs;
|
|
||||||
|
|
||||||
@property({ type: Number })
|
|
||||||
accessor paddingPx: number = 16;
|
|
||||||
|
|
||||||
public static styles = [
|
|
||||||
themeDefaultStyles,
|
|
||||||
cssManager.defaultStyles,
|
|
||||||
css`
|
|
||||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
|
||||||
:host {
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: #161616;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-container {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-header {
|
|
||||||
background: #000000;
|
|
||||||
border-bottom: 1px solid #333;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.view-content {
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
overflow: auto;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content.active {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
dees-appui-tabs {
|
|
||||||
height: 60px;
|
|
||||||
}
|
|
||||||
`,
|
|
||||||
];
|
|
||||||
|
|
||||||
public render(): TemplateResult {
|
|
||||||
if (!this.viewConfig) {
|
|
||||||
return html`<div>No view configuration provided</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return html`
|
|
||||||
<div class="view-container">
|
|
||||||
<div class="view-header">
|
|
||||||
<dees-appui-tabs
|
|
||||||
.tabs=${this.viewConfig.tabs}
|
|
||||||
.selectedTab=${this.selectedTab}
|
|
||||||
@tab-select=${(e: CustomEvent) => this.handleTabSelect(e)}
|
|
||||||
></dees-appui-tabs>
|
|
||||||
</div>
|
|
||||||
<div class="view-content">
|
|
||||||
${this.viewConfig.tabs.map((tab) => {
|
|
||||||
const isActive = tab === this.selectedTab;
|
|
||||||
const content = typeof tab.content === 'function' ? tab.content() : tab.content;
|
|
||||||
const padding = tab.paddingPx ?? this.paddingPx;
|
|
||||||
return html`
|
|
||||||
<div class="tab-content ${isActive ? 'active' : ''}" style="padding: ${padding}px;">
|
|
||||||
${content
|
|
||||||
? content
|
|
||||||
: tab.useSlotName
|
|
||||||
? html`<slot name="${tab.slotName || tab.key}"></slot>`
|
|
||||||
: ''}
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async firstUpdated() {
|
|
||||||
this.tabs = this.shadowRoot.querySelector('dees-appui-tabs');
|
|
||||||
|
|
||||||
if (this.viewConfig?.tabs?.length > 0) {
|
|
||||||
this.selectedTab = this.viewConfig.tabs[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private handleTabSelect(e: CustomEvent) {
|
|
||||||
this.selectedTab = e.detail.tab;
|
|
||||||
|
|
||||||
// Re-emit the event with view context
|
|
||||||
this.dispatchEvent(new CustomEvent('view-tab-select', {
|
|
||||||
detail: {
|
|
||||||
view: this.viewConfig,
|
|
||||||
tab: e.detail.tab
|
|
||||||
},
|
|
||||||
bubbles: true,
|
|
||||||
composed: true
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public methods for external control
|
|
||||||
public selectTab(tabKey: string) {
|
|
||||||
const tab = this.viewConfig.tabs.find(t => t.key === tabKey);
|
|
||||||
if (tab) {
|
|
||||||
this.selectedTab = tab;
|
|
||||||
if (this.tabs) {
|
|
||||||
this.tabs.selectedTab = tab;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public getMenuItems(): interfaces.IMenuItem[] {
|
|
||||||
return this.viewConfig?.menuItems || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public getTabs(): IViewTab[] {
|
|
||||||
return this.viewConfig?.tabs || [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export * from './dees-appui-view.js';
|
|
||||||
@@ -7,4 +7,3 @@ export * from './dees-appui-mainmenu/index.js';
|
|||||||
export * from './dees-appui-secondarymenu/index.js';
|
export * from './dees-appui-secondarymenu/index.js';
|
||||||
export * from './dees-appui-profiledropdown/index.js';
|
export * from './dees-appui-profiledropdown/index.js';
|
||||||
export * from './dees-appui-tabs/index.js';
|
export * from './dees-appui-tabs/index.js';
|
||||||
export * from './dees-appui-view/index.js';
|
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ export type TDeesAppuiBase = HTMLElement & {
|
|||||||
removeMainMenuItem: (groupName: string, tabKey: string) => void;
|
removeMainMenuItem: (groupName: string, tabKey: string) => void;
|
||||||
setMainMenuSelection: (tabKey: string) => void;
|
setMainMenuSelection: (tabKey: string) => void;
|
||||||
setMainMenuCollapsed: (collapsed: boolean) => void;
|
setMainMenuCollapsed: (collapsed: boolean) => void;
|
||||||
|
setMainMenuVisible: (visible: boolean) => void;
|
||||||
|
setSecondaryMenuCollapsed: (collapsed: boolean) => void;
|
||||||
|
setSecondaryMenuVisible: (visible: boolean) => void;
|
||||||
|
setContentTabsVisible: (visible: boolean) => void;
|
||||||
setMainMenuBadge: (tabKey: string, badge: string | number) => void;
|
setMainMenuBadge: (tabKey: string, badge: string | number) => void;
|
||||||
clearMainMenuBadge: (tabKey: string) => void;
|
clearMainMenuBadge: (tabKey: string) => void;
|
||||||
setSecondaryMenu: (config: { heading?: string; groups: IMenuGroup[] }) => void;
|
setSecondaryMenu: (config: { heading?: string; groups: IMenuGroup[] }) => void;
|
||||||
|
|||||||
Reference in New Issue
Block a user