Compare commits

...

16 Commits

Author SHA1 Message Date
2504251707 v3.47.0
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-11 08:49:14 +00:00
fed130f291 feat(dees-statsgrid): add container-responsive behavior and responsive CSS to dees-statsgrid; bump @design.estate/dees-element dependency to ^2.2.1 2026-03-11 08:49:14 +00:00
4f05b5907b v3.46.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 19:49:46 +00:00
e517320dcd fix(dees-appui): add min-height: 0 to mainmenu and secondarymenu to prevent unintended container height and fix layout stacking 2026-03-10 19:49:46 +00:00
ade5a25b3a v3.46.0
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 16:04:28 +00:00
a396dfea12 feat(dees-tile): unify tile metadata into a consistent bottom info bar and add PDF file-size display 2026-03-10 16:04:28 +00:00
d0105e1b80 v3.45.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 14:42:02 +00:00
1eeebb35e6 fix(dees-appui): substitute route params into URL hash when navigating 2026-03-10 14:42:02 +00:00
14e8b8c533 v3.45.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 13:56:22 +00:00
eaf327ea75 feat(dees-form): register new input components (tags, list, wysiwyg, richtext) and emit change notification for richtext updates 2026-03-10 13:56:22 +00:00
09741e0b37 v3.44.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 12:39:21 +00:00
5cadd1fc7f feat(appui-tabs): add support for left/right tab action buttons and content tab action APIs 2026-03-10 12:39:21 +00:00
1795235c6d v3.43.4
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-09 17:37:50 +00:00
ba7d387acb fix(media): remove deprecated dees-pdf and dees-pdf-preview components and bump several dependencies 2026-03-09 17:37:50 +00:00
26ca16a284 v3.43.3
Some checks failed
Default (tags) / security (push) Failing after 2s
Default (tags) / test (push) Failing after 2s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-02-24 18:25:54 +00:00
3ab3eb5e5e fix(dees-table): use lucide icon identifier for Search action in dees-table 2026-02-24 18:25:54 +00:00
32 changed files with 2849 additions and 3332 deletions

View File

@@ -1,5 +1,65 @@
# Changelog # Changelog
## 2026-03-11 - 3.47.0 - feat(dees-statsgrid)
add container-responsive behavior and responsive CSS to dees-statsgrid; bump @design.estate/dees-element dependency to ^2.2.1
- Added @containerResponsive decorator and import to dees-statsgrid
- Added cssManager.cssForPhablet and cssManager.cssForPhone responsive style blocks to adjust layout, spacing and font sizes on smaller viewports
- Bumped dependency @design.estate/dees-element from ^2.1.6 to ^2.2.1
## 2026-03-10 - 3.46.1 - fix(dees-appui)
add min-height: 0 to mainmenu and secondarymenu to prevent unintended container height and fix layout stacking
- Modified ts_web/elements/00group-appui/dees-appui/dees-appui.ts: added min-height: 0 to .maingrid > dees-appui-mainmenu and .maingrid > dees-appui-secondarymenu
- Fixes layout issues where children or flexbox-derived min-height could cause menu containers to expand and interfere with z-index stacking
## 2026-03-10 - 3.46.0 - feat(dees-tile)
unify tile metadata into a consistent bottom info bar and add PDF file-size display
- Introduce renderBottomBar() hook in DeesTileBase and remove per-component bottom badges/labels in favor of a unified info bar.
- Implement renderBottomBar in audio, video, image, folder, note and pdf tiles to show label, counts, dimensions, duration, language/line info and page counts.
- PDF tile: add fileSize state, attempt to read download info and display formatted file size in the info bar; show currentPreviewPage/pageCount when hovering.
- Styling changes: replace legacy badges/labels with .tile-info-bar (.info-label, .info-detail, .info-spacer); adjust padding, font sizing, z-index, and remove hover translate for clickable tiles.
- PDF demo and styles: use cssManager theming for demo colors and adjust preview padding.
- Bump devDependencies: @git.zone/tswatch -> ^3.3.0 and @types/node -> ^25.4.0
## 2026-03-10 - 3.45.1 - fix(dees-appui)
substitute route params into URL hash when navigating
- Replaces :param placeholders in view.route with provided params before updating the URL hash.
- Ensures window.history.pushState is called with the resolved route so URLs do not contain literal parameter tokens.
## 2026-03-10 - 3.45.0 - feat(dees-form)
register new input components (tags, list, wysiwyg, richtext) and emit change notification for richtext updates
- Added imports and registration of DeesInputTags, DeesInputList, DeesInputWysiwyg, and DeesInputRichtext in dees-form
- Extended TFormInputElement union type to include the new input components
- DeesInputRichtext now calls changeSubject.next(this.value) in the editor onUpdate handler to propagate changes
## 2026-03-10 - 3.44.0 - feat(appui-tabs)
add support for left/right tab action buttons and content tab action APIs
- Introduce ITabAction interface and add actionsLeft/actionsRight properties to dees-appui-tabs, dees-appui-maincontent, and dees-appui.
- Render action buttons with new styles and renderActions() helper, including disabled state and click handlers; wire actions into tab components.
- Add public clear() on dees-appui-tabs and improve tab selection logic to reset selection when tabs become empty or when the selected tab is removed.
- Expose setContentTabActionsLeft and setContentTabActionsRight on the DeesAppui programmatic API and update interfaces/appconfig accordingly.
- Update demos to showcase action buttons, add clear-all behavior, and adjust layout/styling for action areas.
## 2026-03-09 - 3.43.4 - fix(media)
remove deprecated dees-pdf and dees-pdf-preview components and bump several dependencies
- Removed deprecated PDF components and related demos/styles: ts_web/elements/00group-media/dees-pdf/* and ts_web/elements/00group-media/dees-pdf-preview/*
- Removed exports for dees-pdf and dees-pdf-preview from ts_web/elements/00group-media/index.ts (public API removal)
- Dependency upgrades: @design.estate/dees-domtools → ^2.3.9, apexcharts → ^5.10.3, lucide → ^0.577.0, @fortawesome/* → ^7.2.0
- DevDependency upgrades: @git.zone/tsbuild → ^4.3.0, @git.zone/tsbundle → ^2.9.1, @git.zone/tstest → ^3.3.2, @git.zone/tswatch → ^3.2.5, @types/node → ^25.3.5
- Updated ts_web/services/versions.ts to align CDN/version constants (apexcharts, tiptap → 2.27.2, fontawesome)
## 2026-02-24 - 3.43.3 - fix(dees-table)
use lucide icon identifier for Search action in dees-table
- Replaced iconName 'magnifyingGlass' with 'lucide:Search' in ts_web/elements/00group-dataview/dees-table/dees-table.ts
- Updates the icon identifier for the header 'Search' action; no functional behavior changed
## 2026-02-21 - 3.43.2 - fix(dees-chart-log) ## 2026-02-21 - 3.43.2 - fix(dees-chart-log)
avoid duplicate log entries, optimize incremental updates, enforce maxEntries, and respect filters when writing logs avoid duplicate log entries, optimize incremental updates, enforce maxEntries, and respect filters when writing logs

View File

@@ -1,6 +1,6 @@
{ {
"name": "@design.estate/dees-catalog", "name": "@design.estate/dees-catalog",
"version": "3.43.2", "version": "3.47.0",
"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",
@@ -16,13 +16,13 @@
"author": "Lossless GmbH", "author": "Lossless GmbH",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@design.estate/dees-domtools": "^2.3.8", "@design.estate/dees-domtools": "^2.3.9",
"@design.estate/dees-element": "^2.1.6", "@design.estate/dees-element": "^2.2.1",
"@design.estate/dees-wcctools": "^3.8.0", "@design.estate/dees-wcctools": "^3.8.0",
"@fortawesome/fontawesome-svg-core": "^7.1.0", "@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-brands-svg-icons": "^7.1.0", "@fortawesome/free-brands-svg-icons": "^7.2.0",
"@fortawesome/free-regular-svg-icons": "^7.1.0", "@fortawesome/free-regular-svg-icons": "^7.2.0",
"@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.2.0",
"@push.rocks/smarti18n": "^1.0.4", "@push.rocks/smarti18n": "^1.0.4",
"@push.rocks/smartpromise": "^4.2.0", "@push.rocks/smartpromise": "^4.2.0",
"@push.rocks/smartstring": "^4.1.0", "@push.rocks/smartstring": "^4.1.0",
@@ -34,22 +34,22 @@
"@tiptap/extension-underline": "^2.23.0", "@tiptap/extension-underline": "^2.23.0",
"@tiptap/starter-kit": "^2.23.0", "@tiptap/starter-kit": "^2.23.0",
"@tsclass/tsclass": "^9.3.0", "@tsclass/tsclass": "^9.3.0",
"apexcharts": "^5.5.0", "apexcharts": "^5.10.3",
"highlight.js": "11.11.1", "highlight.js": "11.11.1",
"ibantools": "^4.5.1", "ibantools": "^4.5.1",
"lucide": "^0.564.0", "lucide": "^0.577.0",
"monaco-editor": "0.55.1", "monaco-editor": "0.55.1",
"pdfjs-dist": "^4.10.38", "pdfjs-dist": "^4.10.38",
"xterm": "^5.3.0", "xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0" "xterm-addon-fit": "^0.8.0"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^4.1.2", "@git.zone/tsbuild": "^4.3.0",
"@git.zone/tsbundle": "^2.8.3", "@git.zone/tsbundle": "^2.9.1",
"@git.zone/tstest": "^3.1.8", "@git.zone/tstest": "^3.3.2",
"@git.zone/tswatch": "^3.1.0", "@git.zone/tswatch": "^3.3.0",
"@push.rocks/projectinfo": "^5.0.2", "@push.rocks/projectinfo": "^5.0.2",
"@types/node": "^25.2.3" "@types/node": "^25.4.0"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",

4883
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@design.estate/dees-catalog', name: '@design.estate/dees-catalog',
version: '3.43.2', version: '3.47.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

@@ -53,6 +53,12 @@ export class DeesAppuiMaincontent extends DeesElement {
@property({ type: Number }) @property({ type: Number })
accessor tabsAutoHideThreshold: number = 0; accessor tabsAutoHideThreshold: number = 0;
@property({ type: Array })
accessor tabActionsLeft: interfaces.ITabAction[] = [];
@property({ type: Array })
accessor tabActionsRight: interfaces.ITabAction[] = [];
public static styles = [ public static styles = [
themeDefaultStyles, themeDefaultStyles,
cssManager.defaultStyles, cssManager.defaultStyles,
@@ -106,6 +112,8 @@ export class DeesAppuiMaincontent extends DeesElement {
.tabStyle=${'horizontal'} .tabStyle=${'horizontal'}
.autoHide=${this.tabsAutoHide} .autoHide=${this.tabsAutoHide}
.autoHideThreshold=${this.tabsAutoHideThreshold} .autoHideThreshold=${this.tabsAutoHideThreshold}
.actionsLeft=${this.tabActionsLeft}
.actionsRight=${this.tabActionsRight}
@tab-select=${(e: CustomEvent) => this.handleTabSelect(e)} @tab-select=${(e: CustomEvent) => this.handleTabSelect(e)}
@tab-close=${(e: CustomEvent) => this.handleTabClose(e)} @tab-close=${(e: CustomEvent) => this.handleTabClose(e)}
></dees-appui-tabs> ></dees-appui-tabs>

View File

@@ -2,7 +2,7 @@ import { html, cssManager, css, DeesElement, customElement, state } from '@desig
import * as interfaces from '../../interfaces/index.js'; import * as interfaces from '../../interfaces/index.js';
import type { DeesAppuiTabs } from './dees-appui-tabs.js'; import type { DeesAppuiTabs } from './dees-appui-tabs.js';
// Interactive demo component for closeable tabs // Interactive demo component for closeable tabs with action buttons
@customElement('demo-closeable-tabs') @customElement('demo-closeable-tabs')
class DemoCloseableTabs extends DeesElement { class DemoCloseableTabs extends DeesElement {
@state() @state()
@@ -18,24 +18,6 @@ class DemoCloseableTabs extends DeesElement {
:host { :host {
display: block; display: block;
} }
.controls {
display: flex;
gap: 8px;
margin-top: 16px;
}
button {
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
border: 1px solid ${cssManager.bdTheme('rgba(59, 130, 246, 0.3)', 'rgba(59, 130, 246, 0.3)')};
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
font-size: 13px;
transition: all 0.15s ease;
}
button:hover {
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.2)', 'rgba(59, 130, 246, 0.2)')};
}
.info { .info {
margin-top: 16px; margin-top: 16px;
padding: 12px 16px; padding: 12px 16px;
@@ -66,17 +48,27 @@ class DemoCloseableTabs extends DeesElement {
this.tabs = this.tabs.filter(t => t.key !== tabKey); this.tabs = this.tabs.filter(t => t.key !== tabKey);
} }
private clearAll() {
const tabsEl = this.shadowRoot!.querySelector('dees-appui-tabs') as DeesAppuiTabs;
tabsEl?.clear();
this.tabs = [];
this.tabCounter = 0;
}
render() { render() {
const rightActions: interfaces.ITabAction[] = [
{ id: 'add', iconName: 'lucide:plus', action: () => this.addTab(), tooltip: 'New Tab' },
{ id: 'clear', iconName: 'lucide:trash2', action: () => this.clearAll(), tooltip: 'Clear All Tabs' },
];
return html` return html`
<dees-appui-tabs <dees-appui-tabs
.tabs=${this.tabs} .tabs=${this.tabs}
.actionsRight=${rightActions}
@tab-close=${(e: CustomEvent) => this.removeTab(e.detail.tab.key)} @tab-close=${(e: CustomEvent) => this.removeTab(e.detail.tab.key)}
></dees-appui-tabs> ></dees-appui-tabs>
<div class="controls">
<button @click=${() => this.addTab()}>+ Add New Tab</button>
</div>
<div class="info"> <div class="info">
Click the X button on tabs to close them. The "Main" tab is not closeable. Click the X button on tabs to close them. Use the + button to add tabs and the trash button to clear all.
<br>Current tabs: ${this.tabs.length} <br>Current tabs: ${this.tabs.length}
</div> </div>
`; `;
@@ -232,6 +224,16 @@ export const demoFunc = () => {
{ key: 'Archived', action: () => console.log('Archived clicked') }, { key: 'Archived', action: () => console.log('Archived clicked') },
]; ];
const actionsLeft: interfaces.ITabAction[] = [
{ id: 'back', iconName: 'lucide:arrowLeft', action: () => console.log('Back'), tooltip: 'Go Back' },
];
const actionsRight: interfaces.ITabAction[] = [
{ id: 'add', iconName: 'lucide:plus', action: () => console.log('Add tab'), tooltip: 'New Tab' },
{ id: 'search', iconName: 'lucide:search', action: () => console.log('Search'), tooltip: 'Search Tabs' },
{ id: 'disabled', iconName: 'lucide:lock', action: () => {}, tooltip: 'Disabled Action', disabled: true },
];
const demoContent = (text: string) => html` const demoContent = (text: string) => html`
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};"> <div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
${text} ${text}
@@ -279,7 +281,17 @@ export const demoFunc = () => {
</div> </div>
<div class="section"> <div class="section">
<div class="section-title">Closeable Tabs (Browser-style)</div> <div class="section-title">Tabs with Action Buttons</div>
<dees-appui-tabs
.tabs=${horizontalTabs}
.actionsLeft=${actionsLeft}
.actionsRight=${actionsRight}
></dees-appui-tabs>
${demoContent('Action buttons can be placed on either side of the tab bar. They remain fixed while tabs scroll. The lock icon shows a disabled action.')}
</div>
<div class="section">
<div class="section-title">Closeable Tabs with Actions</div>
<demo-closeable-tabs></demo-closeable-tabs> <demo-closeable-tabs></demo-closeable-tabs>
</div> </div>

View File

@@ -41,6 +41,12 @@ export class DeesAppuiTabs extends DeesElement {
@property({ type: Number }) @property({ type: Number })
accessor autoHideThreshold: number = 0; accessor autoHideThreshold: number = 0;
@property({ type: Array })
accessor actionsLeft: interfaces.ITabAction[] = [];
@property({ type: Array })
accessor actionsRight: interfaces.ITabAction[] = [];
// Scroll state for fade indicators // Scroll state for fade indicators
@state() @state()
private accessor canScrollLeft: boolean = false; private accessor canScrollLeft: boolean = false;
@@ -73,6 +79,8 @@ export class DeesAppuiTabs extends DeesElement {
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')}; border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
display: flex;
align-items: stretch;
} }
/* Scroll fade indicators */ /* Scroll fade indicators */
@@ -105,6 +113,72 @@ export class DeesAppuiTabs extends DeesElement {
opacity: 1; opacity: 1;
} }
.scroll-area {
position: relative;
flex: 1;
min-width: 0;
overflow: hidden;
display: flex;
}
/* Tab action buttons */
.tab-actions {
display: flex;
align-items: center;
gap: 2px;
flex-shrink: 0;
padding: 0 4px;
}
.tab-actions.left {
padding-left: 12px;
padding-right: 8px;
border-right: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
}
.tab-actions.right {
padding-right: 12px;
padding-left: 8px;
border-left: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
}
.tab-action-button {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 6px;
cursor: pointer;
transition: background 0.15s ease, color 0.15s ease;
background: transparent;
color: ${cssManager.bdTheme('#71717a', '#71717a')};
flex-shrink: 0;
}
.tab-action-button:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.06)')};
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
}
.tab-action-button:active {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')};
}
.tab-action-button.disabled {
opacity: 0.4;
cursor: not-allowed;
}
.tab-action-button.disabled:hover {
background: transparent;
color: ${cssManager.bdTheme('#71717a', '#71717a')};
}
.tab-action-button dees-icon {
font-size: 16px;
}
.tabsContainer { .tabsContainer {
position: relative; position: relative;
user-select: none; user-select: none;
@@ -121,12 +195,14 @@ export class DeesAppuiTabs extends DeesElement {
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: transparent transparent; scrollbar-color: transparent transparent;
height: 100%; height: 100%;
width: 100%;
padding: 0 16px; padding: 0 16px;
gap: 4px; gap: 4px;
} }
/* Show scrollbar on hover */ /* Show scrollbar on hover */
.tabs-wrapper:hover .tabsContainer.horizontal { .tabs-wrapper:hover .tabsContainer.horizontal,
.scroll-area:hover .tabsContainer.horizontal {
scrollbar-color: ${cssManager.bdTheme('rgba(0,0,0,0.2)', 'rgba(255,255,255,0.2)')} transparent; scrollbar-color: ${cssManager.bdTheme('rgba(0,0,0,0.2)', 'rgba(255,255,255,0.2)')} transparent;
} }
@@ -144,11 +220,13 @@ export class DeesAppuiTabs extends DeesElement {
transition: background 0.2s ease; transition: background 0.2s ease;
} }
.tabs-wrapper:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb { .tabs-wrapper:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb,
.scroll-area:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb {
background: ${cssManager.bdTheme('rgba(0,0,0,0.2)', 'rgba(255,255,255,0.2)')}; background: ${cssManager.bdTheme('rgba(0,0,0,0.2)', 'rgba(255,255,255,0.2)')};
} }
.tabs-wrapper:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb:hover { .tabs-wrapper:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb:hover,
.scroll-area:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb:hover {
background: ${cssManager.bdTheme('rgba(0,0,0,0.35)', 'rgba(255,255,255,0.35)')}; background: ${cssManager.bdTheme('rgba(0,0,0,0.35)', 'rgba(255,255,255,0.35)')};
} }
@@ -331,13 +409,20 @@ export class DeesAppuiTabs extends DeesElement {
const containerClass = `tabsContainer ${this.tabStyle}`; const containerClass = `tabsContainer ${this.tabStyle}`;
if (isHorizontal) { if (isHorizontal) {
const hasLeftActions = this.actionsLeft && this.actionsLeft.length > 0;
const hasRightActions = this.actionsRight && this.actionsRight.length > 0;
return html` return html`
<div class="${wrapperClass}"> <div class="${wrapperClass}">
<div class="scroll-fade scroll-fade-left ${this.canScrollLeft ? 'visible' : ''}"></div> ${hasLeftActions ? this.renderActions(this.actionsLeft, 'left') : ''}
<div class="${containerClass}" @scroll=${this.handleScroll}> <div class="scroll-area">
${this.tabs.map(tab => this.renderTab(tab, isHorizontal))} <div class="scroll-fade scroll-fade-left ${this.canScrollLeft ? 'visible' : ''}"></div>
<div class="${containerClass}" @scroll=${this.handleScroll}>
${this.tabs.map(tab => this.renderTab(tab, isHorizontal))}
</div>
<div class="scroll-fade scroll-fade-right ${this.canScrollRight ? 'visible' : ''}"></div>
</div> </div>
<div class="scroll-fade scroll-fade-right ${this.canScrollRight ? 'visible' : ''}"></div> ${hasRightActions ? this.renderActions(this.actionsRight, 'right') : ''}
${this.showTabIndicator ? html`<div class="tabIndicator"></div>` : ''} ${this.showTabIndicator ? html`<div class="tabIndicator"></div>` : ''}
</div> </div>
`; `;
@@ -353,6 +438,22 @@ export class DeesAppuiTabs extends DeesElement {
`; `;
} }
private renderActions(actions: interfaces.ITabAction[], position: 'left' | 'right'): TemplateResult {
return html`
<div class="tab-actions ${position}">
${actions.map(action => html`
<div
class="tab-action-button ${action.disabled ? 'disabled' : ''}"
title="${action.tooltip || action.id}"
@click=${() => !action.disabled && action.action()}
>
<dees-icon .icon=${action.iconName}></dees-icon>
</div>
`)}
</div>
`;
}
private renderTab(tab: interfaces.IMenuItem, isHorizontal: boolean): TemplateResult { private renderTab(tab: interfaces.IMenuItem, isHorizontal: boolean): TemplateResult {
const isSelected = tab === this.selectedTab; const isSelected = tab === this.selectedTab;
const classes = `tab ${isSelected ? 'selectedTab' : ''}`; const classes = `tab ${isSelected ? 'selectedTab' : ''}`;
@@ -406,6 +507,14 @@ export class DeesAppuiTabs extends DeesElement {
})); }));
} }
/**
* Clear all tabs and reset selection.
*/
public clear(): void {
this.tabs = [];
this.selectedTab = null;
}
private closeTab(e: Event, tab: interfaces.IMenuItem) { private closeTab(e: Event, tab: interfaces.IMenuItem) {
e.stopPropagation(); // Don't select tab when closing e.stopPropagation(); // Don't select tab when closing
@@ -423,14 +532,9 @@ export class DeesAppuiTabs extends DeesElement {
} }
firstUpdated() { firstUpdated() {
if (this.tabs && this.tabs.length > 0) { // Tab selection is handled by updated() lifecycle
this.selectTab(this.tabs[0]);
}
// Set up ResizeObserver for scroll state updates
this.setupResizeObserver(); this.setupResizeObserver();
// Initial scroll state check
requestAnimationFrame(() => { requestAnimationFrame(() => {
this.updateScrollState(); this.updateScrollState();
}); });
@@ -503,8 +607,24 @@ export class DeesAppuiTabs extends DeesElement {
async updated(changedProperties: Map<string, any>) { async updated(changedProperties: Map<string, any>) {
super.updated(changedProperties); super.updated(changedProperties);
if (changedProperties.has('tabs') && this.tabs && this.tabs.length > 0 && !this.selectedTab) { if (changedProperties.has('tabs')) {
this.selectTab(this.tabs[0]); if (!this.tabs || this.tabs.length === 0) {
// Tabs are empty => reset selection
if (this.selectedTab !== null) {
this.selectedTab = null;
this.dispatchEvent(new CustomEvent('tab-select', {
detail: { tab: null },
bubbles: true,
composed: true,
}));
}
} else if (this.selectedTab && !this.tabs.includes(this.selectedTab)) {
// Selected tab was removed => select first available
this.selectTab(this.tabs[0]);
} else if (!this.selectedTab) {
// Tabs exist but nothing selected => select first
this.selectTab(this.tabs[0]);
}
} }
if (changedProperties.has('selectedTab') || changedProperties.has('tabs')) { if (changedProperties.has('selectedTab') || changedProperties.has('tabs')) {

View File

@@ -143,6 +143,12 @@ export class DeesAppui extends DeesElement {
@property({ type: Object }) @property({ type: Object })
accessor maincontentSelectedTab: interfaces.IMenuItem | undefined = undefined; accessor maincontentSelectedTab: interfaces.IMenuItem | undefined = undefined;
@property({ type: Array })
accessor contentTabActionsLeft: interfaces.ITabAction[] = [];
@property({ type: Array })
accessor contentTabActionsRight: interfaces.ITabAction[] = [];
// References to child components // References to child components
@state() @state()
accessor appbar: DeesAppuiBar | undefined = undefined; accessor appbar: DeesAppuiBar | undefined = undefined;
@@ -213,11 +219,13 @@ export class DeesAppui extends DeesElement {
.maingrid > dees-appui-mainmenu { .maingrid > dees-appui-mainmenu {
position: relative; position: relative;
z-index: 3; z-index: 3;
min-height: 0;
} }
.maingrid > dees-appui-secondarymenu { .maingrid > dees-appui-secondarymenu {
position: relative; position: relative;
z-index: 2; z-index: 2;
min-height: 0;
} }
.maingrid > dees-appui-maincontent { .maingrid > dees-appui-maincontent {
@@ -306,6 +314,8 @@ export class DeesAppui extends DeesElement {
.showTabs=${this.maincontentTabsVisible} .showTabs=${this.maincontentTabsVisible}
.tabsAutoHide=${this.contentTabsAutoHide} .tabsAutoHide=${this.contentTabsAutoHide}
.tabsAutoHideThreshold=${this.contentTabsAutoHideThreshold} .tabsAutoHideThreshold=${this.contentTabsAutoHideThreshold}
.tabActionsLeft=${this.contentTabActionsLeft}
.tabActionsRight=${this.contentTabActionsRight}
@tab-select=${(e: CustomEvent) => this.handleContentTabSelect(e)} @tab-select=${(e: CustomEvent) => this.handleContentTabSelect(e)}
@tab-close=${(e: CustomEvent) => this.handleContentTabClose(e)} @tab-close=${(e: CustomEvent) => this.handleContentTabClose(e)}
> >
@@ -699,6 +709,20 @@ export class DeesAppui extends DeesElement {
return this.maincontentSelectedTab; return this.maincontentSelectedTab;
} }
/**
* Set content tab action buttons on the left side
*/
public setContentTabActionsLeft(actions: interfaces.ITabAction[]): void {
this.contentTabActionsLeft = [...actions];
}
/**
* Set content tab action buttons on the right side
*/
public setContentTabActionsRight(actions: interfaces.ITabAction[]): void {
this.contentTabActionsRight = [...actions];
}
// ========================================== // ==========================================
// PROGRAMMATIC API: ACTIVITY LOG // PROGRAMMATIC API: ACTIVITY LOG
// ========================================== // ==========================================
@@ -853,8 +877,13 @@ export class DeesAppui extends DeesElement {
try { try {
await this.loadView(view, params); await this.loadView(view, params);
// Update URL hash // Update URL hash (substitute params into route pattern)
const route = view.route || viewId; let route = view.route || viewId;
if (params) {
for (const [key, val] of Object.entries(params)) {
route = route.replace(`:${key}`, val);
}
}
const newHash = `#${route}`; const newHash = `#${route}`;
if (window.location.hash !== newHash) { if (window.location.hash !== newHash) {
window.history.pushState({ viewId }, '', newHash); window.history.pushState({ viewId }, '', newHash);

View File

@@ -10,6 +10,7 @@ import {
css, css,
unsafeCSS, unsafeCSS,
cssManager, cssManager,
containerResponsive,
} from '@design.estate/dees-element'; } from '@design.estate/dees-element';
import type { TemplateResult } from '@design.estate/dees-element'; import type { TemplateResult } from '@design.estate/dees-element';
@@ -93,6 +94,7 @@ export interface IStatsTile {
actions?: plugins.tsclass.website.IMenuItem[]; actions?: plugins.tsclass.website.IMenuItem[];
} }
@containerResponsive()
@customElement('dees-statsgrid') @customElement('dees-statsgrid')
export class DeesStatsGrid extends DeesElement { export class DeesStatsGrid extends DeesElement {
public static demo = demoFunc; public static demo = demoFunc;
@@ -801,6 +803,33 @@ export class DeesStatsGrid extends DeesElement {
z-index: 1000; z-index: 1000;
} }
`, `,
// Container-responsive: when this statsgrid is narrow
cssManager.cssForPhablet(css`
:host {
--tile-padding: 12px;
--value-font-size: 22px;
--title-font-size: 12px;
--grid-gap: 8px;
}
.stats-grid {
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 8px;
}
.stats-tile {
grid-column: span 1 !important;
}
`, this),
cssManager.cssForPhone(css`
:host {
--tile-padding: 10px;
--value-font-size: 20px;
--header-spacing: 8px;
}
.stats-grid {
grid-template-columns: 1fr;
gap: 6px;
}
`, this),
]; ];
constructor() { constructor() {

View File

@@ -544,7 +544,7 @@ export class DeesTable<T> extends DeesElement {
if (!existing) { if (!existing) {
this.dataActions.unshift({ this.dataActions.unshift({
name: 'Search', name: 'Search',
iconName: 'magnifyingGlass', iconName: 'lucide:Search',
type: ['header'], type: ['header'],
actionFunc: async () => { actionFunc: async () => {
console.log('open search'); console.log('open search');

View File

@@ -22,6 +22,10 @@ import { DeesInputMultitoggle } from '../../00group-input/dees-input-multitoggle
import { DeesInputPhone } from '../../00group-input/dees-input-phone/dees-input-phone.js'; import { DeesInputPhone } from '../../00group-input/dees-input-phone/dees-input-phone.js';
import { DeesInputToggle } from '../../00group-input/dees-input-toggle/dees-input-toggle.js'; import { DeesInputToggle } from '../../00group-input/dees-input-toggle/dees-input-toggle.js';
import { DeesInputTypelist } from '../../00group-input/dees-input-typelist/dees-input-typelist.js'; import { DeesInputTypelist } from '../../00group-input/dees-input-typelist/dees-input-typelist.js';
import { DeesInputTags } from '../../00group-input/dees-input-tags/dees-input-tags.js';
import { DeesInputList } from '../../00group-input/dees-input-list/dees-input-list.js';
import { DeesInputWysiwyg } from '../../00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
import { DeesInputRichtext } from '../../00group-input/dees-input-richtext/component.js';
import { DeesFormSubmit } from '../dees-form-submit/dees-form-submit.js'; import { DeesFormSubmit } from '../dees-form-submit/dees-form-submit.js';
import { DeesTable } from '../../00group-dataview/dees-table/index.js'; import { DeesTable } from '../../00group-dataview/dees-table/index.js';
import { demoFunc } from './dees-form.demo.js'; import { demoFunc } from './dees-form.demo.js';
@@ -41,6 +45,10 @@ const FORM_INPUT_TYPES = [
DeesInputText, DeesInputText,
DeesInputToggle, DeesInputToggle,
DeesInputTypelist, DeesInputTypelist,
DeesInputTags,
DeesInputList,
DeesInputWysiwyg,
DeesInputRichtext,
DeesTable, DeesTable,
]; ];
@@ -58,6 +66,10 @@ export type TFormInputElement =
| DeesInputText | DeesInputText
| DeesInputToggle | DeesInputToggle
| DeesInputTypelist | DeesInputTypelist
| DeesInputTags
| DeesInputList
| DeesInputWysiwyg
| DeesInputRichtext
| DeesTable<any>; | DeesTable<any>;
declare global { declare global {

View File

@@ -269,6 +269,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
onUpdate: ({ editor }) => { onUpdate: ({ editor }) => {
this.value = editor.getHTML(); this.value = editor.getHTML();
this.updateWordCount(); this.updateWordCount();
this.changeSubject.next(this.value);
this.dispatchEvent( this.dispatchEvent(
new CustomEvent('input', { new CustomEvent('input', {
detail: { value: this.value }, detail: { value: this.value },

View File

@@ -1,24 +0,0 @@
import { customElement } from '@design.estate/dees-element';
import { DeesTilePdf } from '../dees-tile-pdf/component.js';
declare global {
interface HTMLElementTagNameMap {
'dees-pdf-preview': DeesPdfPreview;
}
}
/**
* @deprecated Use <dees-tile-pdf> instead. This component will be removed in a future release.
*/
@customElement('dees-pdf-preview')
export class DeesPdfPreview extends DeesTilePdf {
public static demoGroups: never[] = []; // Hide from demo catalog
public connectedCallback(): Promise<void> {
console.warn(
'[dees-pdf-preview] is deprecated. Use <dees-tile-pdf> instead. ' +
'This component will be removed in a future release.'
);
return super.connectedCallback();
}
}

View File

@@ -1,189 +0,0 @@
import { html } from '@design.estate/dees-element';
export const demo = () => {
const samplePdfs = [
'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf',
'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf',
];
const generateGridItems = (count: number) => {
const items = [];
for (let i = 0; i < count; i++) {
const pdfUrl = samplePdfs[i % samplePdfs.length];
items.push(html`
<dees-pdf-preview
pdfUrl="${pdfUrl}"
maxPages="3"
stackOffset="6"
clickable="true"
grid-mode
@pdf-preview-click=${(e: CustomEvent) => {
console.log('PDF Preview clicked:', e.detail);
alert(`PDF clicked: ${e.detail.pageCount} pages`);
}}
></dees-pdf-preview>
`);
}
return items;
};
return html`
<style>
.demo-container {
padding: 40px;
background: #f5f5f5;
}
.demo-section {
margin-bottom: 60px;
}
h3 {
margin-bottom: 20px;
font-size: 18px;
font-weight: 600;
}
.preview-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 24px;
}
.preview-row {
display: flex;
gap: 24px;
align-items: center;
margin-bottom: 20px;
}
.preview-label {
font-size: 14px;
font-weight: 500;
min-width: 100px;
}
.performance-stats {
margin-top: 20px;
padding: 16px;
background: white;
border-radius: 8px;
font-size: 14px;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 12px;
margin-top: 12px;
}
.stat-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.stat-label {
font-size: 12px;
color: #666;
}
.stat-value {
font-size: 16px;
font-weight: 600;
}
</style>
<div class="demo-container">
<div class="demo-section">
<h3>Single PDF Preview with Stacked Pages</h3>
<dees-pdf-preview
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
maxPages="3"
stackOffset="8"
clickable="true"
></dees-pdf-preview>
</div>
<div class="demo-section">
<h3>Different Sizes</h3>
<div class="preview-row">
<div class="preview-label">Small:</div>
<dees-pdf-preview
size="small"
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf"
maxPages="2"
stackOffset="6"
clickable="true"
></dees-pdf-preview>
</div>
<div class="preview-row">
<div class="preview-label">Default:</div>
<dees-pdf-preview
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf"
maxPages="3"
stackOffset="8"
clickable="true"
></dees-pdf-preview>
</div>
<div class="preview-row">
<div class="preview-label">Large:</div>
<dees-pdf-preview
size="large"
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf"
maxPages="4"
stackOffset="10"
clickable="true"
></dees-pdf-preview>
</div>
</div>
<div class="demo-section">
<h3>Non-Clickable Preview</h3>
<dees-pdf-preview
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf"
maxPages="3"
stackOffset="8"
clickable="false"
></dees-pdf-preview>
</div>
<div class="demo-section">
<h3>Performance Grid - 50 PDFs with Lazy Loading</h3>
<p style="margin-bottom: 20px; font-size: 14px; color: #666;">
This grid demonstrates the performance optimizations with 50 PDF previews.
Scroll to see lazy loading in action - previews render only when visible.
</p>
<div class="preview-grid">
${generateGridItems(50)}
</div>
<div class="performance-stats">
<h4>Performance Features</h4>
<div class="stats-grid">
<div class="stat-item">
<span class="stat-label">Lazy Loading</span>
<span class="stat-value">✓ Enabled</span>
</div>
<div class="stat-item">
<span class="stat-label">Canvas Pooling</span>
<span class="stat-value">✓ Active</span>
</div>
<div class="stat-item">
<span class="stat-label">Memory Management</span>
<span class="stat-value">✓ Optimized</span>
</div>
<div class="stat-item">
<span class="stat-label">Intersection Observer</span>
<span class="stat-value">200px margin</span>
</div>
</div>
</div>
</div>
</div>
`;
};

View File

@@ -1 +0,0 @@
export * from './component.js';

View File

@@ -1,223 +0,0 @@
import { css, cssManager } from '@design.estate/dees-element';
export const previewStyles = [
cssManager.defaultStyles,
css`
:host {
display: inline-block;
position: relative;
}
.preview-container {
position: relative;
width: 200px;
height: 260px;
background: ${cssManager.bdTheme('hsl(0 0% 98%)', 'hsl(215 20% 14%)')};
border-radius: 4px;
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
box-shadow: 0 1px 3px ${cssManager.bdTheme('rgba(0, 0, 0, 0.12)', 'rgba(0, 0, 0, 0.24)')};
}
.preview-container.clickable {
cursor: pointer;
}
.preview-container.clickable:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px ${cssManager.bdTheme('rgba(0, 0, 0, 0.12)', 'rgba(0, 0, 0, 0.3)')};
}
.preview-container.clickable:hover .preview-overlay {
opacity: 1;
}
.preview-stack {
position: relative;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
overflow: hidden;
}
.preview-stack.non-a4 {
padding: 12px;
}
.preview-canvas {
position: relative;
background: white;
display: block;
max-width: 100%;
max-height: 100%;
width: auto;
height: auto;
object-fit: contain;
image-rendering: auto;
-webkit-font-smoothing: antialiased;
box-shadow: 0 1px 3px ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(0, 0, 0, 0.3)')};
}
.non-a4 .preview-canvas {
border: 1px solid ${cssManager.bdTheme('hsl(214 31% 92%)', 'hsl(217 25% 24%)')};
border-radius: 4px;
}
.preview-info {
position: absolute;
bottom: 8px;
left: 8px;
right: 8px;
padding: 6px 10px;
background: ${cssManager.bdTheme('hsl(0 0% 100% / 0.92)', 'hsl(215 20% 12% / 0.92)')};
border-radius: 6px;
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: ${cssManager.bdTheme('hsl(215 16% 45%)', 'hsl(215 16% 75%)')};
backdrop-filter: blur(12px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
z-index: 10;
}
.preview-info dees-icon {
font-size: 13px;
color: ${cssManager.bdTheme('hsl(217 91% 60%)', 'hsl(213 93% 68%)')};
}
.preview-pages {
font-weight: 500;
font-size: 11px;
}
.preview-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.7)', 'rgba(0, 0, 0, 0.8)')};
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
opacity: 0;
transition: opacity 0.2s ease;
z-index: 20;
}
.preview-overlay dees-icon {
font-size: 24px;
color: white;
}
.preview-overlay span {
font-size: 14px;
font-weight: 500;
color: white;
}
.preview-loading,
.preview-error {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
color: ${cssManager.bdTheme('hsl(215 16% 45%)', 'hsl(215 16% 75%)')};
}
.preview-loading {
background: ${cssManager.bdTheme('hsl(0 0% 99%)', 'hsl(215 20% 14%)')};
}
.preview-error {
background: ${cssManager.bdTheme('hsl(0 72% 98%)', 'hsl(0 62% 20%)')};
color: ${cssManager.bdTheme('hsl(0 72% 40%)', 'hsl(0 70% 68%)')};
}
.preview-spinner {
width: 24px;
height: 24px;
border-radius: 50%;
border: 2px solid ${cssManager.bdTheme('hsl(214 31% 86%)', 'hsl(217 25% 28%)')};
border-top-color: ${cssManager.bdTheme('hsl(217 91% 60%)', 'hsl(213 93% 68%)')};
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.preview-text {
font-size: 13px;
font-weight: 500;
}
.preview-error dees-icon {
font-size: 32px;
}
.preview-page-indicator {
position: absolute;
top: 8px;
left: 8px;
right: 8px;
padding: 5px 8px;
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.7)', 'hsl(0 0% 100% / 0.9)')};
color: ${cssManager.bdTheme('white', 'hsl(215 20% 12%)')};
border-radius: 4px;
font-size: 11px;
font-weight: 600;
text-align: center;
backdrop-filter: blur(12px);
z-index: 15;
pointer-events: none;
animation: fadeIn 0.2s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Responsive sizes */
:host([size="small"]) .preview-container {
width: 150px;
height: 195px;
}
:host([size="large"]) .preview-container {
width: 250px;
height: 325px;
}
/* Grid optimizations */
:host([grid-mode]) .preview-container {
will-change: auto;
}
:host([grid-mode]) .preview-canvas {
image-rendering: -webkit-optimize-contrast;
image-rendering: crisp-edges;
}
`,
];

View File

@@ -1,161 +0,0 @@
import { DeesElement, property, html, customElement, domtools, type TemplateResult, type CSSResult, } from '@design.estate/dees-element';
import { Deferred } from '@push.rocks/smartpromise';
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
import '../../00group-utility/dees-icon/dees-icon.js';
// import type pdfjsTypes from 'pdfjs-dist';
declare global {
interface HTMLElementTagNameMap {
'dees-pdf': DeesPdf;
}
}
/**
* @deprecated Use DeesPdfViewer or DeesTilePdf instead
* - DeesPdfViewer: Full-featured PDF viewing with controls, navigation, zoom
* - DeesTilePdf: Lightweight, performance-optimized tile preview for grids
*/
@customElement('dees-pdf')
export class DeesPdf extends DeesElement {
// DEMO
public static demo = () => html` <dees-pdf></dees-pdf> `;
public static demoGroups = ['Media', 'PDF'];
// INSTANCE
@property()
accessor pdfUrl: string =
'https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf';
constructor() {
super();
// you have access to all kinds of things through this.
// this.setAttribute('gotIt','true');
}
public render(): TemplateResult {
return html`
<style>
:host {
font-family: 'Geist Sans', sans-serif;
display: block;
box-sizing: border-box;
max-width: 800px;
}
:host([hidden]) {
display: none;
}
#pdfcanvas {
box-shadow: 0px 0px 5px #ccc;
width: 100%;
cursor: pointer;
}
</style>
<canvas
id="pdfcanvas"
.height=${0}
.width=${0}
></canvas>
`;
}
public static pdfJsReady: Promise<any>;
public static pdfjsLib: any // typeof pdfjsTypes;
public async connectedCallback() {
super.connectedCallback();
if (!DeesPdf.pdfJsReady) {
const pdfJsReadyDeferred = domtools.plugins.smartpromise.defer();
DeesPdf.pdfJsReady = pdfJsReadyDeferred.promise;
// @ts-ignore
DeesPdf.pdfjsLib = await import('https://cdn.jsdelivr.net/npm/pdfjs-dist@4.0.379/+esm');
DeesPdf.pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdn.jsdelivr.net/npm/pdfjs-dist@4.0.379/build/pdf.worker.mjs';
pdfJsReadyDeferred.resolve();
}
await DeesPdf.pdfJsReady;
this.displayContent();
}
public async displayContent() {
await DeesPdf.pdfJsReady;
// Asynchronous download of PDF
const loadingTask = DeesPdf.pdfjsLib.getDocument(this.pdfUrl);
loadingTask.promise.then(
(pdf) => {
console.log('PDF loaded');
// Fetch the first page
const pageNumber = 1;
pdf.getPage(pageNumber).then((page) => {
console.log('Page loaded');
const scale = 10;
const viewport = page.getViewport({ scale: scale });
// Prepare canvas using PDF page dimensions
const canvas: any = this.shadowRoot.querySelector('#pdfcanvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
// Render PDF page into canvas context
const renderContext = {
canvasContext: context,
viewport: viewport,
};
const renderTask = page.render(renderContext);
renderTask.promise.then(function () {
console.log('Page rendered');
});
});
},
(reason) => {
// PDF loading error
console.error(reason);
}
);
}
/**
* Provide context menu items for the global context menu handler
*/
public getContextMenuItems() {
return [
{
name: 'Open PDF in New Tab',
iconName: 'lucide:ExternalLink',
action: async () => {
window.open(this.pdfUrl, '_blank');
}
},
{ divider: true },
{
name: 'Copy PDF URL',
iconName: 'lucide:Copy',
action: async () => {
await navigator.clipboard.writeText(this.pdfUrl);
}
},
{
name: 'Download PDF',
iconName: 'lucide:Download',
action: async () => {
const link = document.createElement('a');
link.href = this.pdfUrl;
link.download = this.pdfUrl.split('/').pop() || 'document.pdf';
link.click();
}
}
];
}
}

View File

@@ -1 +0,0 @@
export * from './component.js';

View File

@@ -162,10 +162,6 @@ export class DeesTileAudio extends DeesTileBase {
` : ''} ` : ''}
</div> </div>
${this.duration > 0 ? html`
<div class="tile-badge-corner">${this.formatTime(this.duration)}</div>
` : ''}
<div class="play-overlay"> <div class="play-overlay">
<div class="play-circle"> <div class="play-circle">
<dees-icon icon="lucide:Play"></dees-icon> <dees-icon icon="lucide:Play"></dees-icon>
@@ -181,6 +177,17 @@ export class DeesTileAudio extends DeesTileBase {
`; `;
} }
protected renderBottomBar(): TemplateResult | string {
if (!this.label && !this.duration) return '';
return html`
<div class="tile-info-bar">
${this.label ? html`<span class="info-label" title="${this.label}">${this.label}</span>` : ''}
<span class="info-spacer"></span>
${this.duration > 0 ? html`<span class="info-detail">${this.formatTime(this.duration)}</span>` : ''}
</div>
`;
}
protected getTileClickDetail(): Record<string, unknown> { protected getTileClickDetail(): Record<string, unknown> {
return { return {
src: this.src, src: this.src,

View File

@@ -145,10 +145,6 @@ export class DeesTileFolder extends DeesTileBase {
</div> </div>
</div> </div>
<div class="tile-badge-corner">
${this.items.length} item${this.items.length !== 1 ? 's' : ''}
</div>
${this.clickable ? html` ${this.clickable ? html`
<div class="tile-overlay"> <div class="tile-overlay">
<dees-icon icon="lucide:FolderOpen"></dees-icon> <dees-icon icon="lucide:FolderOpen"></dees-icon>
@@ -158,6 +154,17 @@ export class DeesTileFolder extends DeesTileBase {
`; `;
} }
protected renderBottomBar(): TemplateResult | string {
if (!this.label && !this.items.length) return '';
return html`
<div class="tile-info-bar">
${this.label ? html`<span class="info-label" title="${this.label}">${this.label}</span>` : ''}
<span class="info-spacer"></span>
<span class="info-detail">${this.items.length} item${this.items.length !== 1 ? 's' : ''}</span>
</div>
`;
}
protected getTileClickDetail(): Record<string, unknown> { protected getTileClickDetail(): Record<string, unknown> {
return { return {
name: this.name, name: this.name,

View File

@@ -55,14 +55,6 @@ export class DeesTileImage extends DeesTileBase {
opacity: 0; opacity: 0;
} }
.tile-badge-topright.dimension-badge {
opacity: 0;
transition: opacity 0.2s ease;
}
.tile-container.clickable:hover .tile-badge-topright.dimension-badge {
opacity: 1;
}
`, `,
] as any; ] as any;
@@ -97,19 +89,6 @@ export class DeesTileImage extends DeesTileBase {
` : ''} ` : ''}
</div> </div>
${this.imageWidth > 0 && this.imageHeight > 0 ? html`
<div class="tile-badge-topright dimension-badge">
${this.imageWidth} × ${this.imageHeight}
</div>
` : ''}
${this.imageLoaded ? html`
<div class="tile-info">
<dees-icon icon="lucide:Image"></dees-icon>
<span class="tile-info-text">${this.imageWidth} × ${this.imageHeight}</span>
</div>
` : ''}
${this.clickable ? html` ${this.clickable ? html`
<div class="tile-overlay"> <div class="tile-overlay">
<dees-icon icon="lucide:Eye"></dees-icon> <dees-icon icon="lucide:Eye"></dees-icon>
@@ -119,6 +98,19 @@ export class DeesTileImage extends DeesTileBase {
`; `;
} }
protected renderBottomBar(): TemplateResult | string {
if (!this.label && !(this.imageWidth > 0)) return '';
return html`
<div class="tile-info-bar">
${this.label ? html`<span class="info-label" title="${this.label}">${this.label}</span>` : ''}
<span class="info-spacer"></span>
${this.imageWidth > 0 && this.imageHeight > 0
? html`<span class="info-detail">${this.imageWidth} × ${this.imageHeight}</span>`
: ''}
</div>
`;
}
protected getTileClickDetail(): Record<string, unknown> { protected getTileClickDetail(): Record<string, unknown> {
return { return {
src: this.src, src: this.src,

View File

@@ -81,14 +81,6 @@ export class DeesTileNote extends DeesTileBase {
pointer-events: none; pointer-events: none;
} }
.tile-badge-topright.note-language {
background: ${cssManager.bdTheme('hsl(215 20% 92%)', 'hsl(215 20% 88%)')};
color: ${cssManager.bdTheme('hsl(215 16% 50%)', 'hsl(215 16% 40%)')};
font-size: 9px;
text-transform: uppercase;
z-index: 5;
}
.note-lines { .note-lines {
position: absolute; position: absolute;
top: 0; top: 0;
@@ -132,10 +124,6 @@ export class DeesTileNote extends DeesTileBase {
return html` return html`
<div class="note-content"> <div class="note-content">
${this.language ? html`
<div class="tile-badge-topright note-language">${this.language}</div>
` : ''}
${this.title ? html` ${this.title ? html`
<div class="note-header"> <div class="note-header">
<div class="note-title">${this.title}</div> <div class="note-title">${this.title}</div>
@@ -147,11 +135,6 @@ export class DeesTileNote extends DeesTileBase {
${!this.isHovering ? html`<div class="note-fade"></div>` : ''} ${!this.isHovering ? html`<div class="note-fade"></div>` : ''}
</div> </div>
${this.isHovering && lines.length > 12 ? html`
<div class="tile-badge-corner">
Line ${this.getVisibleLineRange(lines.length)}
</div>
` : ''}
</div> </div>
${this.clickable ? html` ${this.clickable ? html`
@@ -163,6 +146,21 @@ export class DeesTileNote extends DeesTileBase {
`; `;
} }
protected renderBottomBar(): TemplateResult | string {
const lines = this.content.split('\n');
if (!this.label && !this.language && !lines.length) return '';
return html`
<div class="tile-info-bar">
${this.label ? html`<span class="info-label" title="${this.label}">${this.label}</span>` : ''}
<span class="info-spacer"></span>
${this.language ? html`<span class="info-detail">${this.language.toUpperCase()}</span>` : ''}
${this.isHovering && lines.length > 12
? html`<span class="info-detail">Line ${this.getVisibleLineRange(lines.length)}</span>`
: html`<span class="info-detail">${lines.length} lines</span>`}
</div>
`;
}
protected getTileClickDetail(): Record<string, unknown> { protected getTileClickDetail(): Record<string, unknown> {
return { return {
title: this.title, title: this.title,

View File

@@ -1,9 +1,9 @@
import { property, html, customElement, type TemplateResult, type CSSResult } from '@design.estate/dees-element'; import { property, state, html, customElement, type TemplateResult, type CSSResult } from '@design.estate/dees-element';
import { DeesTileBase } from '../dees-tile-shared/DeesTileBase.js'; import { DeesTileBase } from '../dees-tile-shared/DeesTileBase.js';
import { tileBaseStyles } from '../dees-tile-shared/styles.js'; import { tileBaseStyles } from '../dees-tile-shared/styles.js';
import { PdfManager } from '../dees-pdf-shared/PdfManager.js'; import { PdfManager } from '../dees-pdf-shared/PdfManager.js';
import { CanvasPool, type PooledCanvas } from '../dees-pdf-shared/CanvasPool.js'; import { CanvasPool, type PooledCanvas } from '../dees-pdf-shared/CanvasPool.js';
import { PerformanceMonitor, throttle } from '../dees-pdf-shared/utils.js'; import { PerformanceMonitor, throttle, formatFileSize } from '../dees-pdf-shared/utils.js';
import { tilePdfStyles } from './styles.js'; import { tilePdfStyles } from './styles.js';
import { demo as demoFunc } from './demo.js'; import { demo as demoFunc } from './demo.js';
@@ -37,6 +37,9 @@ export class DeesTilePdf extends DeesTileBase {
@property({ type: Boolean }) @property({ type: Boolean })
accessor isA4Format: boolean = true; accessor isA4Format: boolean = true;
@state()
accessor fileSize: number = 0;
private renderPagesTask: Promise<void> | null = null; private renderPagesTask: Promise<void> | null = null;
private renderPagesQueued: boolean = false; private renderPagesQueued: boolean = false;
private pdfDocument: any; private pdfDocument: any;
@@ -54,18 +57,6 @@ export class DeesTilePdf extends DeesTileBase {
></canvas> ></canvas>
</div> </div>
${this.pageCount > 1 && this.isHovering ? html`
<div class="tile-badge">
Page ${this.currentPreviewPage} of ${this.pageCount}
</div>
` : ''}
${this.pageCount > 0 && !this.isHovering ? html`
<div class="tile-badge-corner">
${this.pageCount} page${this.pageCount > 1 ? 's' : ''}
</div>
` : ''}
${this.clickable ? html` ${this.clickable ? html`
<div class="tile-overlay"> <div class="tile-overlay">
<dees-icon icon="lucide:Eye"></dees-icon> <dees-icon icon="lucide:Eye"></dees-icon>
@@ -75,6 +66,22 @@ export class DeesTilePdf extends DeesTileBase {
`; `;
} }
protected renderBottomBar(): TemplateResult | string {
if (!this.pageCount && !this.label) return '';
return html`
<div class="tile-info-bar">
${this.label ? html`<span class="info-label" title="${this.label}">${this.label}</span>` : ''}
<span class="info-spacer"></span>
${this.pageCount > 1 && this.isHovering
? html`<span class="info-detail">${this.currentPreviewPage}/${this.pageCount}</span>`
: this.pageCount > 0
? html`<span class="info-detail">${this.pageCount} pg</span>`
: ''}
${this.fileSize > 0 ? html`<span class="info-detail">${formatFileSize(this.fileSize)}</span>` : ''}
</div>
`;
}
protected getTileClickDetail(): Record<string, unknown> { protected getTileClickDetail(): Record<string, unknown> {
return { return {
pdfUrl: this.pdfUrl, pdfUrl: this.pdfUrl,
@@ -141,6 +148,13 @@ export class DeesTilePdf extends DeesTileBase {
this.pdfDocument = await PdfManager.loadDocument(this.pdfUrl); this.pdfDocument = await PdfManager.loadDocument(this.pdfUrl);
this.pageCount = this.pdfDocument.numPages; this.pageCount = this.pdfDocument.numPages;
this.currentPreviewPage = 1; this.currentPreviewPage = 1;
try {
const downloadInfo = await this.pdfDocument.getDownloadInfo();
this.fileSize = downloadInfo.length;
} catch {
// File size unavailable — not critical
}
this.loadedPdfUrl = this.pdfUrl; this.loadedPdfUrl = this.pdfUrl;
this.loading = false; this.loading = false;

View File

@@ -1,4 +1,4 @@
import { html } from '@design.estate/dees-element'; import { html, cssManager } from '@design.estate/dees-element';
export const demo = () => { export const demo = () => {
const samplePdfs = [ const samplePdfs = [
@@ -29,7 +29,7 @@ export const demo = () => {
<style> <style>
.demo-container { .demo-container {
padding: 40px; padding: 40px;
background: #f5f5f5; background: ${cssManager.bdTheme('#f5f5f5', '#0a0a0a')};
} }
.demo-section { .demo-section {
@@ -40,6 +40,7 @@ export const demo = () => {
margin-bottom: 20px; margin-bottom: 20px;
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
.preview-grid { .preview-grid {
@@ -59,6 +60,7 @@ export const demo = () => {
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
min-width: 100px; min-width: 100px;
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
} }
</style> </style>

View File

@@ -10,10 +10,11 @@ export const tilePdfStyles = css`
justify-content: center; justify-content: center;
box-sizing: border-box; box-sizing: border-box;
overflow: hidden; overflow: hidden;
padding: 8px 8px 28px 8px;
} }
.preview-stack.non-a4 { .preview-stack.non-a4 {
padding: 12px; padding: 12px 12px 28px 12px;
} }
.preview-canvas { .preview-canvas {

View File

@@ -59,9 +59,7 @@ export abstract class DeesTileBase extends DeesElement {
${!this.loading && !this.error ? this.renderTileContent() : ''} ${!this.loading && !this.error ? this.renderTileContent() : ''}
${this.label ? html` ${this.renderBottomBar()}
<div class="tile-label">${this.label}</div>
` : ''}
</div> </div>
`; `;
} }
@@ -69,6 +67,11 @@ export abstract class DeesTileBase extends DeesElement {
/** Subclasses implement this to render their specific content */ /** Subclasses implement this to render their specific content */
protected abstract renderTileContent(): TemplateResult; protected abstract renderTileContent(): TemplateResult;
/** Subclasses override this to render a bottom info bar with metadata */
protected renderBottomBar(): TemplateResult | string {
return '';
}
public async connectedCallback(): Promise<void> { public async connectedCallback(): Promise<void> {
await super.connectedCallback(); await super.connectedCallback();
this.setupIntersectionObserver(); this.setupIntersectionObserver();

View File

@@ -15,8 +15,9 @@ export const tileBaseStyles = [
background: ${cssManager.bdTheme('hsl(0 0% 98%)', 'hsl(215 20% 14%)')}; background: ${cssManager.bdTheme('hsl(0 0% 98%)', 'hsl(215 20% 14%)')};
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease; transition: box-shadow 0.2s ease;
box-shadow: 0 1px 3px ${cssManager.bdTheme('rgba(0, 0, 0, 0.12)', 'rgba(0, 0, 0, 0.24)')}; box-shadow: 0 1px 3px ${cssManager.bdTheme('rgba(0, 0, 0, 0.12)', 'rgba(0, 0, 0, 0.24)')};
} }
.tile-container.clickable { .tile-container.clickable {
@@ -24,7 +25,6 @@ export const tileBaseStyles = [
} }
.tile-container.clickable:hover { .tile-container.clickable:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px ${cssManager.bdTheme('rgba(0, 0, 0, 0.12)', 'rgba(0, 0, 0, 0.3)')}; box-shadow: 0 8px 24px ${cssManager.bdTheme('rgba(0, 0, 0, 0.12)', 'rgba(0, 0, 0, 0.3)')};
} }
@@ -71,90 +71,39 @@ export const tileBaseStyles = [
color: white; color: white;
} }
.tile-info { .tile-info-bar {
position: absolute; position: absolute;
bottom: 8px; bottom: 0;
left: 8px; left: 0;
right: 8px; right: 0;
padding: 6px 10px; padding: 4px 8px;
background: ${cssManager.bdTheme('hsl(0 0% 100% / 0.92)', 'hsl(215 20% 12% / 0.92)')}; background: ${cssManager.bdTheme('hsl(0 0% 100% / 0.95)', 'hsl(215 20% 12% / 0.95)')};
border-radius: 6px;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 6px; gap: 6px;
font-size: 12px; font-size: 10px;
font-weight: 500;
color: ${cssManager.bdTheme('hsl(215 16% 45%)', 'hsl(215 16% 75%)')}; color: ${cssManager.bdTheme('hsl(215 16% 45%)', 'hsl(215 16% 75%)')};
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); z-index: 25;
z-index: 10;
}
.tile-info dees-icon {
font-size: 13px;
color: ${cssManager.bdTheme('hsl(217 91% 60%)', 'hsl(213 93% 68%)')};
}
.tile-info-text {
font-weight: 500;
font-size: 11px;
}
.tile-badge {
position: absolute;
top: 8px;
left: 8px;
right: 8px;
padding: 5px 8px;
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.7)', 'hsl(0 0% 100% / 0.9)')};
color: ${cssManager.bdTheme('white', 'hsl(215 20% 12%)')};
border-radius: 4px;
font-size: 11px;
font-weight: 600;
text-align: center;
backdrop-filter: blur(12px);
z-index: 15;
pointer-events: none;
animation: fadeIn 0.2s ease;
}
.tile-badge-corner {
position: absolute;
bottom: 8px;
right: 8px;
padding: 3px 8px;
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.6)', 'hsl(0 0% 100% / 0.85)')};
color: ${cssManager.bdTheme('white', 'hsl(215 20% 12%)')};
border-radius: 4px;
font-size: 10px;
font-weight: 600;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
backdrop-filter: blur(8px);
z-index: 10;
pointer-events: none;
} }
.tile-badge-topright { .info-label {
position: absolute; white-space: nowrap;
top: 8px; overflow: hidden;
right: 8px; text-overflow: ellipsis;
padding: 3px 8px; min-width: 0;
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.6)', 'hsl(0 0% 100% / 0.85)')};
color: ${cssManager.bdTheme('white', 'hsl(215 20% 12%)')};
border-radius: 4px;
font-size: 10px;
font-weight: 600;
backdrop-filter: blur(8px);
z-index: 15;
pointer-events: none;
} }
/* Shift bottom badges up when label is present */ .info-spacer {
.tile-container:has(.tile-label) .tile-badge-corner { flex: 1;
bottom: 33px;
} }
.tile-container:has(.tile-label) .tile-info { .info-detail {
bottom: 33px; white-space: nowrap;
opacity: 0.7;
flex-shrink: 0;
} }
.tile-loading, .tile-loading,
@@ -200,40 +149,12 @@ export const tileBaseStyles = [
font-weight: 500; font-weight: 500;
} }
.tile-label {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 6px 10px;
background: ${cssManager.bdTheme('hsl(0 0% 100% / 0.95)', 'hsl(215 20% 12% / 0.95)')};
font-size: 11px;
font-weight: 500;
color: ${cssManager.bdTheme('hsl(215 16% 35%)', 'hsl(215 16% 75%)')};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
z-index: 10;
backdrop-filter: blur(12px);
}
@keyframes spin { @keyframes spin {
to { to {
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(-4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Size variants */ /* Size variants */
:host([size="small"]) .tile-container { :host([size="small"]) .tile-container {
width: 150px; width: 150px;

View File

@@ -140,10 +140,6 @@ export class DeesTileVideo extends DeesTileBase {
` : ''} ` : ''}
</div> </div>
${this.duration > 0 ? html`
<div class="tile-badge-corner">${this.formatTime(this.duration)}</div>
` : ''}
${!this.isHovering ? html` ${!this.isHovering ? html`
<div class="play-overlay"> <div class="play-overlay">
<dees-icon icon="lucide:Play"></dees-icon> <dees-icon icon="lucide:Play"></dees-icon>
@@ -159,6 +155,17 @@ export class DeesTileVideo extends DeesTileBase {
`; `;
} }
protected renderBottomBar(): TemplateResult | string {
if (!this.label && !this.duration) return '';
return html`
<div class="tile-info-bar">
${this.label ? html`<span class="info-label" title="${this.label}">${this.label}</span>` : ''}
<span class="info-spacer"></span>
${this.duration > 0 ? html`<span class="info-detail">${this.formatTime(this.duration)}</span>` : ''}
</div>
`;
}
protected getTileClickDetail(): Record<string, unknown> { protected getTileClickDetail(): Record<string, unknown> {
return { return {
src: this.src, src: this.src,

View File

@@ -5,8 +5,6 @@ export * from './dees-video-viewer/index.js';
export * from './dees-preview/index.js'; export * from './dees-preview/index.js';
// PDF Components // PDF Components
export * from './dees-pdf/index.js'; // @deprecated - Use dees-pdf-viewer or dees-tile-pdf instead
export * from './dees-pdf-preview/index.js'; // @deprecated - Use dees-tile-pdf instead
export * from './dees-pdf-shared/index.js'; export * from './dees-pdf-shared/index.js';
export * from './dees-pdf-viewer/index.js'; export * from './dees-pdf-viewer/index.js';

View File

@@ -1,6 +1,6 @@
import type { TemplateResult } from '@design.estate/dees-element'; import type { TemplateResult } from '@design.estate/dees-element';
import type { IAppBarMenuItem } from './appbarmenuitem.js'; import type { IAppBarMenuItem } from './appbarmenuitem.js';
import type { IMenuItem } from './tab.js'; import type { IMenuItem, ITabAction } from './tab.js';
import type { IMenuGroup } from './menugroup.js'; import type { IMenuGroup } from './menugroup.js';
import type { ISecondaryMenuGroup, ISecondaryMenuItem } from './secondarymenu.js'; import type { ISecondaryMenuGroup, ISecondaryMenuItem } from './secondarymenu.js';
@@ -134,6 +134,8 @@ export type TDeesAppui = HTMLElement & {
removeContentTab: (tabKey: string) => void; removeContentTab: (tabKey: string) => void;
selectContentTab: (tabKey: string) => void; selectContentTab: (tabKey: string) => void;
getSelectedContentTab: () => IMenuItem | undefined; getSelectedContentTab: () => IMenuItem | undefined;
setContentTabActionsLeft: (actions: ITabAction[]) => void;
setContentTabActionsRight: (actions: ITabAction[]) => void;
activityLog: IActivityLogAPI; activityLog: IActivityLogAPI;
setActivityLogVisible: (visible: boolean) => void; setActivityLogVisible: (visible: boolean) => void;
toggleActivityLog: () => void; toggleActivityLog: () => void;

View File

@@ -7,3 +7,11 @@ export interface IMenuItem {
closeable?: boolean; closeable?: boolean;
onClose?: () => void; onClose?: () => void;
} }
export interface ITabAction {
id: string;
iconName: string;
action: () => void | Promise<void>;
tooltip?: string;
disabled?: boolean;
}

View File

@@ -7,9 +7,9 @@ export const CDN_VERSIONS = {
xtermAddonFit: '0.8.0', xtermAddonFit: '0.8.0',
xtermAddonSearch: '0.13.0', xtermAddonSearch: '0.13.0',
highlightJs: '11.11.1', highlightJs: '11.11.1',
apexcharts: '5.3.6', apexcharts: '5.10.3',
tiptap: '2.23.0', tiptap: '2.27.2',
fontawesome: '7.1.0', fontawesome: '7.2.0',
} as const; } as const;
/** /**