Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ade5a25b3a | |||
| a396dfea12 | |||
| d0105e1b80 | |||
| 1eeebb35e6 | |||
| 14e8b8c533 | |||
| eaf327ea75 | |||
| 09741e0b37 | |||
| 5cadd1fc7f | |||
| 1795235c6d | |||
| ba7d387acb | |||
| 26ca16a284 | |||
| 3ab3eb5e5e | |||
| da5dbc70e2 | |||
| 19b7981542 |
57
changelog.md
57
changelog.md
@@ -1,5 +1,62 @@
|
||||
# Changelog
|
||||
|
||||
## 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)
|
||||
avoid duplicate log entries, optimize incremental updates, enforce maxEntries, and respect filters when writing logs
|
||||
|
||||
- Prevent property-bound logEntries from duplicating entries already present in logBuffer by deduplicating on timestamp|message
|
||||
- Call updateMetrics() when replaying or appending log entries so metrics stay accurate
|
||||
- Skip processing entirely when the incoming logEntries array is unchanged
|
||||
- Optimize append-only updates by writing only the new tail entries instead of full re-render
|
||||
- Enforce maxEntries when appending to the logBuffer to maintain buffer size
|
||||
- Respect filterMode and searchQuery when deciding whether to write appended entries to the terminal
|
||||
|
||||
## 2026-02-21 - 3.43.1 - fix(dees-chart-log)
|
||||
replay buffered log entries when terminal becomes ready and sync logEntries updates to re-render filtered logs
|
||||
|
||||
|
||||
26
package.json
26
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@design.estate/dees-catalog",
|
||||
"version": "3.43.1",
|
||||
"version": "3.46.0",
|
||||
"private": false,
|
||||
"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",
|
||||
@@ -16,13 +16,13 @@
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"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-wcctools": "^3.8.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.2.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
||||
"@push.rocks/smarti18n": "^1.0.4",
|
||||
"@push.rocks/smartpromise": "^4.2.0",
|
||||
"@push.rocks/smartstring": "^4.1.0",
|
||||
@@ -34,22 +34,22 @@
|
||||
"@tiptap/extension-underline": "^2.23.0",
|
||||
"@tiptap/starter-kit": "^2.23.0",
|
||||
"@tsclass/tsclass": "^9.3.0",
|
||||
"apexcharts": "^5.5.0",
|
||||
"apexcharts": "^5.10.3",
|
||||
"highlight.js": "11.11.1",
|
||||
"ibantools": "^4.5.1",
|
||||
"lucide": "^0.564.0",
|
||||
"lucide": "^0.577.0",
|
||||
"monaco-editor": "0.55.1",
|
||||
"pdfjs-dist": "^4.10.38",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-fit": "^0.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^4.1.2",
|
||||
"@git.zone/tsbundle": "^2.8.3",
|
||||
"@git.zone/tstest": "^3.1.8",
|
||||
"@git.zone/tswatch": "^3.1.0",
|
||||
"@git.zone/tsbuild": "^4.3.0",
|
||||
"@git.zone/tsbundle": "^2.9.1",
|
||||
"@git.zone/tstest": "^3.3.2",
|
||||
"@git.zone/tswatch": "^3.3.0",
|
||||
"@push.rocks/projectinfo": "^5.0.2",
|
||||
"@types/node": "^25.2.3"
|
||||
"@types/node": "^25.4.0"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
|
||||
4836
pnpm-lock.yaml
generated
4836
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@design.estate/dees-catalog',
|
||||
version: '3.43.1',
|
||||
version: '3.46.0',
|
||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||
}
|
||||
|
||||
@@ -53,6 +53,12 @@ export class DeesAppuiMaincontent extends DeesElement {
|
||||
@property({ type: Number })
|
||||
accessor tabsAutoHideThreshold: number = 0;
|
||||
|
||||
@property({ type: Array })
|
||||
accessor tabActionsLeft: interfaces.ITabAction[] = [];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor tabActionsRight: interfaces.ITabAction[] = [];
|
||||
|
||||
public static styles = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
@@ -106,6 +112,8 @@ export class DeesAppuiMaincontent extends DeesElement {
|
||||
.tabStyle=${'horizontal'}
|
||||
.autoHide=${this.tabsAutoHide}
|
||||
.autoHideThreshold=${this.tabsAutoHideThreshold}
|
||||
.actionsLeft=${this.tabActionsLeft}
|
||||
.actionsRight=${this.tabActionsRight}
|
||||
@tab-select=${(e: CustomEvent) => this.handleTabSelect(e)}
|
||||
@tab-close=${(e: CustomEvent) => this.handleTabClose(e)}
|
||||
></dees-appui-tabs>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { html, cssManager, css, DeesElement, customElement, state } from '@desig
|
||||
import * as interfaces from '../../interfaces/index.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')
|
||||
class DemoCloseableTabs extends DeesElement {
|
||||
@state()
|
||||
@@ -18,24 +18,6 @@ class DemoCloseableTabs extends DeesElement {
|
||||
:host {
|
||||
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 {
|
||||
margin-top: 16px;
|
||||
padding: 12px 16px;
|
||||
@@ -66,17 +48,27 @@ class DemoCloseableTabs extends DeesElement {
|
||||
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() {
|
||||
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`
|
||||
<dees-appui-tabs
|
||||
.tabs=${this.tabs}
|
||||
.actionsRight=${rightActions}
|
||||
@tab-close=${(e: CustomEvent) => this.removeTab(e.detail.tab.key)}
|
||||
></dees-appui-tabs>
|
||||
<div class="controls">
|
||||
<button @click=${() => this.addTab()}>+ Add New Tab</button>
|
||||
</div>
|
||||
<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}
|
||||
</div>
|
||||
`;
|
||||
@@ -232,6 +224,16 @@ export const demoFunc = () => {
|
||||
{ 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`
|
||||
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
|
||||
${text}
|
||||
@@ -279,7 +281,17 @@ export const demoFunc = () => {
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -41,6 +41,12 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
@property({ type: Number })
|
||||
accessor autoHideThreshold: number = 0;
|
||||
|
||||
@property({ type: Array })
|
||||
accessor actionsLeft: interfaces.ITabAction[] = [];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor actionsRight: interfaces.ITabAction[] = [];
|
||||
|
||||
// Scroll state for fade indicators
|
||||
@state()
|
||||
private accessor canScrollLeft: boolean = false;
|
||||
@@ -73,6 +79,8 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* Scroll fade indicators */
|
||||
@@ -105,6 +113,72 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
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 {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
@@ -121,12 +195,14 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: transparent transparent;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
@@ -144,11 +220,13 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
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)')};
|
||||
}
|
||||
|
||||
.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)')};
|
||||
}
|
||||
|
||||
@@ -331,13 +409,20 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
const containerClass = `tabsContainer ${this.tabStyle}`;
|
||||
|
||||
if (isHorizontal) {
|
||||
const hasLeftActions = this.actionsLeft && this.actionsLeft.length > 0;
|
||||
const hasRightActions = this.actionsRight && this.actionsRight.length > 0;
|
||||
|
||||
return html`
|
||||
<div class="${wrapperClass}">
|
||||
<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))}
|
||||
${hasLeftActions ? this.renderActions(this.actionsLeft, 'left') : ''}
|
||||
<div class="scroll-area">
|
||||
<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 class="scroll-fade scroll-fade-right ${this.canScrollRight ? 'visible' : ''}"></div>
|
||||
${hasRightActions ? this.renderActions(this.actionsRight, 'right') : ''}
|
||||
${this.showTabIndicator ? html`<div class="tabIndicator"></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 {
|
||||
const isSelected = tab === this.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) {
|
||||
e.stopPropagation(); // Don't select tab when closing
|
||||
|
||||
@@ -423,14 +532,9 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
if (this.tabs && this.tabs.length > 0) {
|
||||
this.selectTab(this.tabs[0]);
|
||||
}
|
||||
|
||||
// Set up ResizeObserver for scroll state updates
|
||||
// Tab selection is handled by updated() lifecycle
|
||||
this.setupResizeObserver();
|
||||
|
||||
// Initial scroll state check
|
||||
requestAnimationFrame(() => {
|
||||
this.updateScrollState();
|
||||
});
|
||||
@@ -503,8 +607,24 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
async updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('tabs') && this.tabs && this.tabs.length > 0 && !this.selectedTab) {
|
||||
this.selectTab(this.tabs[0]);
|
||||
if (changedProperties.has('tabs')) {
|
||||
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')) {
|
||||
|
||||
@@ -143,6 +143,12 @@ export class DeesAppui extends DeesElement {
|
||||
@property({ type: Object })
|
||||
accessor maincontentSelectedTab: interfaces.IMenuItem | undefined = undefined;
|
||||
|
||||
@property({ type: Array })
|
||||
accessor contentTabActionsLeft: interfaces.ITabAction[] = [];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor contentTabActionsRight: interfaces.ITabAction[] = [];
|
||||
|
||||
// References to child components
|
||||
@state()
|
||||
accessor appbar: DeesAppuiBar | undefined = undefined;
|
||||
@@ -306,6 +312,8 @@ export class DeesAppui extends DeesElement {
|
||||
.showTabs=${this.maincontentTabsVisible}
|
||||
.tabsAutoHide=${this.contentTabsAutoHide}
|
||||
.tabsAutoHideThreshold=${this.contentTabsAutoHideThreshold}
|
||||
.tabActionsLeft=${this.contentTabActionsLeft}
|
||||
.tabActionsRight=${this.contentTabActionsRight}
|
||||
@tab-select=${(e: CustomEvent) => this.handleContentTabSelect(e)}
|
||||
@tab-close=${(e: CustomEvent) => this.handleContentTabClose(e)}
|
||||
>
|
||||
@@ -699,6 +707,20 @@ export class DeesAppui extends DeesElement {
|
||||
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
|
||||
// ==========================================
|
||||
@@ -853,8 +875,13 @@ export class DeesAppui extends DeesElement {
|
||||
try {
|
||||
await this.loadView(view, params);
|
||||
|
||||
// Update URL hash
|
||||
const route = view.route || viewId;
|
||||
// Update URL hash (substitute params into route pattern)
|
||||
let route = view.route || viewId;
|
||||
if (params) {
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
route = route.replace(`:${key}`, val);
|
||||
}
|
||||
}
|
||||
const newHash = `#${route}`;
|
||||
if (window.location.hash !== newHash) {
|
||||
window.history.pushState({ viewId }, '', newHash);
|
||||
|
||||
@@ -385,11 +385,23 @@ export class DeesChartLog extends DeesElement {
|
||||
this.domtoolsInstance = await this.domtoolsPromise;
|
||||
await this.initializeTerminal();
|
||||
|
||||
// Process any initial log entries
|
||||
if (this.logEntries.length > 0) {
|
||||
// initializeTerminal() already replayed logBuffer (from addLog/updateLog).
|
||||
// Now handle logEntries set via property binding before terminal was ready.
|
||||
if (this.logEntries.length > 0 && this.logBuffer.length === 0) {
|
||||
this.logBuffer = [...this.logEntries];
|
||||
for (const entry of this.logEntries) {
|
||||
this.updateMetrics(entry.level);
|
||||
this.writeLogEntry(entry);
|
||||
}
|
||||
} else if (this.logEntries.length > 0 && this.logBuffer.length > 0) {
|
||||
const bufferSet = new Set(this.logBuffer.map(e => `${e.timestamp}|${e.message}`));
|
||||
for (const entry of this.logEntries) {
|
||||
if (!bufferSet.has(`${entry.timestamp}|${entry.message}`)) {
|
||||
this.logBuffer.push(entry);
|
||||
this.updateMetrics(entry.level);
|
||||
this.writeLogEntry(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,7 +467,46 @@ export class DeesChartLog extends DeesElement {
|
||||
public updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('logEntries') && this.terminalReady && this.logEntries.length > 0) {
|
||||
this.logBuffer = [...this.logEntries];
|
||||
const oldEntries: ILogEntry[] = changedProperties.get('logEntries') || [];
|
||||
const newEntries = this.logEntries;
|
||||
|
||||
// Same content? Skip entirely.
|
||||
if (
|
||||
oldEntries.length === newEntries.length &&
|
||||
oldEntries.length > 0 &&
|
||||
oldEntries[oldEntries.length - 1].timestamp === newEntries[newEntries.length - 1].timestamp &&
|
||||
oldEntries[oldEntries.length - 1].message === newEntries[newEntries.length - 1].message
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Append-only? Write only the new tail entries incrementally.
|
||||
if (
|
||||
newEntries.length > oldEntries.length &&
|
||||
oldEntries.length > 0 &&
|
||||
oldEntries[oldEntries.length - 1].timestamp === newEntries[oldEntries.length - 1].timestamp &&
|
||||
oldEntries[oldEntries.length - 1].message === newEntries[oldEntries.length - 1].message
|
||||
) {
|
||||
const tailEntries = newEntries.slice(oldEntries.length);
|
||||
for (const entry of tailEntries) {
|
||||
this.logBuffer.push(entry);
|
||||
this.updateMetrics(entry.level);
|
||||
|
||||
// Enforce maxEntries
|
||||
if (this.logBuffer.length > this.maxEntries) {
|
||||
this.logBuffer.shift();
|
||||
}
|
||||
|
||||
// Respect filter mode
|
||||
if (!this.filterMode || !this.searchQuery || this.entryMatchesFilter(entry)) {
|
||||
this.writeLogEntry(entry);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Different content — full re-render
|
||||
this.logBuffer = [...newEntries];
|
||||
this.reRenderFilteredLogs();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -544,7 +544,7 @@ export class DeesTable<T> extends DeesElement {
|
||||
if (!existing) {
|
||||
this.dataActions.unshift({
|
||||
name: 'Search',
|
||||
iconName: 'magnifyingGlass',
|
||||
iconName: 'lucide:Search',
|
||||
type: ['header'],
|
||||
actionFunc: async () => {
|
||||
console.log('open search');
|
||||
|
||||
@@ -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 { DeesInputToggle } from '../../00group-input/dees-input-toggle/dees-input-toggle.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 { DeesTable } from '../../00group-dataview/dees-table/index.js';
|
||||
import { demoFunc } from './dees-form.demo.js';
|
||||
@@ -41,6 +45,10 @@ const FORM_INPUT_TYPES = [
|
||||
DeesInputText,
|
||||
DeesInputToggle,
|
||||
DeesInputTypelist,
|
||||
DeesInputTags,
|
||||
DeesInputList,
|
||||
DeesInputWysiwyg,
|
||||
DeesInputRichtext,
|
||||
DeesTable,
|
||||
];
|
||||
|
||||
@@ -58,6 +66,10 @@ export type TFormInputElement =
|
||||
| DeesInputText
|
||||
| DeesInputToggle
|
||||
| DeesInputTypelist
|
||||
| DeesInputTags
|
||||
| DeesInputList
|
||||
| DeesInputWysiwyg
|
||||
| DeesInputRichtext
|
||||
| DeesTable<any>;
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -269,6 +269,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
|
||||
onUpdate: ({ editor }) => {
|
||||
this.value = editor.getHTML();
|
||||
this.updateWordCount();
|
||||
this.changeSubject.next(this.value);
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('input', {
|
||||
detail: { value: this.value },
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
`;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
export * from './component.js';
|
||||
@@ -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;
|
||||
}
|
||||
`,
|
||||
];
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './component.js';
|
||||
@@ -162,10 +162,6 @@ export class DeesTileAudio extends DeesTileBase {
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
${this.duration > 0 ? html`
|
||||
<div class="tile-badge-corner">${this.formatTime(this.duration)}</div>
|
||||
` : ''}
|
||||
|
||||
<div class="play-overlay">
|
||||
<div class="play-circle">
|
||||
<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> {
|
||||
return {
|
||||
src: this.src,
|
||||
|
||||
@@ -145,10 +145,6 @@ export class DeesTileFolder extends DeesTileBase {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tile-badge-corner">
|
||||
${this.items.length} item${this.items.length !== 1 ? 's' : ''}
|
||||
</div>
|
||||
|
||||
${this.clickable ? html`
|
||||
<div class="tile-overlay">
|
||||
<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> {
|
||||
return {
|
||||
name: this.name,
|
||||
|
||||
@@ -55,14 +55,6 @@ export class DeesTileImage extends DeesTileBase {
|
||||
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;
|
||||
|
||||
@@ -97,19 +89,6 @@ export class DeesTileImage extends DeesTileBase {
|
||||
` : ''}
|
||||
</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`
|
||||
<div class="tile-overlay">
|
||||
<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> {
|
||||
return {
|
||||
src: this.src,
|
||||
|
||||
@@ -81,14 +81,6 @@ export class DeesTileNote extends DeesTileBase {
|
||||
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 {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@@ -132,10 +124,6 @@ export class DeesTileNote extends DeesTileBase {
|
||||
|
||||
return html`
|
||||
<div class="note-content">
|
||||
${this.language ? html`
|
||||
<div class="tile-badge-topright note-language">${this.language}</div>
|
||||
` : ''}
|
||||
|
||||
${this.title ? html`
|
||||
<div class="note-header">
|
||||
<div class="note-title">${this.title}</div>
|
||||
@@ -147,11 +135,6 @@ export class DeesTileNote extends DeesTileBase {
|
||||
${!this.isHovering ? html`<div class="note-fade"></div>` : ''}
|
||||
</div>
|
||||
|
||||
${this.isHovering && lines.length > 12 ? html`
|
||||
<div class="tile-badge-corner">
|
||||
Line ${this.getVisibleLineRange(lines.length)}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
${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> {
|
||||
return {
|
||||
title: this.title,
|
||||
|
||||
@@ -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 { tileBaseStyles } from '../dees-tile-shared/styles.js';
|
||||
import { PdfManager } from '../dees-pdf-shared/PdfManager.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 { demo as demoFunc } from './demo.js';
|
||||
|
||||
@@ -37,6 +37,9 @@ export class DeesTilePdf extends DeesTileBase {
|
||||
@property({ type: Boolean })
|
||||
accessor isA4Format: boolean = true;
|
||||
|
||||
@state()
|
||||
accessor fileSize: number = 0;
|
||||
|
||||
private renderPagesTask: Promise<void> | null = null;
|
||||
private renderPagesQueued: boolean = false;
|
||||
private pdfDocument: any;
|
||||
@@ -54,18 +57,6 @@ export class DeesTilePdf extends DeesTileBase {
|
||||
></canvas>
|
||||
</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`
|
||||
<div class="tile-overlay">
|
||||
<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> {
|
||||
return {
|
||||
pdfUrl: this.pdfUrl,
|
||||
@@ -141,6 +148,13 @@ export class DeesTilePdf extends DeesTileBase {
|
||||
this.pdfDocument = await PdfManager.loadDocument(this.pdfUrl);
|
||||
this.pageCount = this.pdfDocument.numPages;
|
||||
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.loading = false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
import { html, cssManager } from '@design.estate/dees-element';
|
||||
|
||||
export const demo = () => {
|
||||
const samplePdfs = [
|
||||
@@ -29,7 +29,7 @@ export const demo = () => {
|
||||
<style>
|
||||
.demo-container {
|
||||
padding: 40px;
|
||||
background: #f5f5f5;
|
||||
background: ${cssManager.bdTheme('#f5f5f5', '#0a0a0a')};
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
@@ -40,6 +40,7 @@ export const demo = () => {
|
||||
margin-bottom: 20px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
}
|
||||
|
||||
.preview-grid {
|
||||
@@ -59,6 +60,7 @@ export const demo = () => {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
min-width: 100px;
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -10,10 +10,11 @@ export const tilePdfStyles = css`
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
padding: 8px 8px 28px 8px;
|
||||
}
|
||||
|
||||
.preview-stack.non-a4 {
|
||||
padding: 12px;
|
||||
padding: 12px 12px 28px 12px;
|
||||
}
|
||||
|
||||
.preview-canvas {
|
||||
|
||||
@@ -59,9 +59,7 @@ export abstract class DeesTileBase extends DeesElement {
|
||||
|
||||
${!this.loading && !this.error ? this.renderTileContent() : ''}
|
||||
|
||||
${this.label ? html`
|
||||
<div class="tile-label">${this.label}</div>
|
||||
` : ''}
|
||||
${this.renderBottomBar()}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -69,6 +67,11 @@ export abstract class DeesTileBase extends DeesElement {
|
||||
/** Subclasses implement this to render their specific content */
|
||||
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> {
|
||||
await super.connectedCallback();
|
||||
this.setupIntersectionObserver();
|
||||
|
||||
@@ -15,8 +15,9 @@ export const tileBaseStyles = [
|
||||
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;
|
||||
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)')};
|
||||
|
||||
}
|
||||
|
||||
.tile-container.clickable {
|
||||
@@ -24,7 +25,6 @@ export const tileBaseStyles = [
|
||||
}
|
||||
|
||||
.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)')};
|
||||
}
|
||||
|
||||
@@ -71,90 +71,39 @@ export const tileBaseStyles = [
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tile-info {
|
||||
.tile-info-bar {
|
||||
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;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 4px 8px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100% / 0.95)', 'hsl(215 20% 12% / 0.95)')};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
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;
|
||||
}
|
||||
|
||||
.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;
|
||||
z-index: 25;
|
||||
font-variant-numeric: tabular-nums;
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tile-badge-topright {
|
||||
position: absolute;
|
||||
top: 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;
|
||||
backdrop-filter: blur(8px);
|
||||
z-index: 15;
|
||||
pointer-events: none;
|
||||
.info-label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Shift bottom badges up when label is present */
|
||||
.tile-container:has(.tile-label) .tile-badge-corner {
|
||||
bottom: 33px;
|
||||
.info-spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tile-container:has(.tile-label) .tile-info {
|
||||
bottom: 33px;
|
||||
.info-detail {
|
||||
white-space: nowrap;
|
||||
opacity: 0.7;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tile-loading,
|
||||
@@ -200,40 +149,12 @@ export const tileBaseStyles = [
|
||||
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 {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Size variants */
|
||||
:host([size="small"]) .tile-container {
|
||||
width: 150px;
|
||||
|
||||
@@ -140,10 +140,6 @@ export class DeesTileVideo extends DeesTileBase {
|
||||
` : ''}
|
||||
</div>
|
||||
|
||||
${this.duration > 0 ? html`
|
||||
<div class="tile-badge-corner">${this.formatTime(this.duration)}</div>
|
||||
` : ''}
|
||||
|
||||
${!this.isHovering ? html`
|
||||
<div class="play-overlay">
|
||||
<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> {
|
||||
return {
|
||||
src: this.src,
|
||||
|
||||
@@ -5,8 +5,6 @@ export * from './dees-video-viewer/index.js';
|
||||
export * from './dees-preview/index.js';
|
||||
|
||||
// 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-viewer/index.js';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { TemplateResult } from '@design.estate/dees-element';
|
||||
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 { ISecondaryMenuGroup, ISecondaryMenuItem } from './secondarymenu.js';
|
||||
|
||||
@@ -134,6 +134,8 @@ export type TDeesAppui = HTMLElement & {
|
||||
removeContentTab: (tabKey: string) => void;
|
||||
selectContentTab: (tabKey: string) => void;
|
||||
getSelectedContentTab: () => IMenuItem | undefined;
|
||||
setContentTabActionsLeft: (actions: ITabAction[]) => void;
|
||||
setContentTabActionsRight: (actions: ITabAction[]) => void;
|
||||
activityLog: IActivityLogAPI;
|
||||
setActivityLogVisible: (visible: boolean) => void;
|
||||
toggleActivityLog: () => void;
|
||||
|
||||
@@ -7,3 +7,11 @@ export interface IMenuItem {
|
||||
closeable?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export interface ITabAction {
|
||||
id: string;
|
||||
iconName: string;
|
||||
action: () => void | Promise<void>;
|
||||
tooltip?: string;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ export const CDN_VERSIONS = {
|
||||
xtermAddonFit: '0.8.0',
|
||||
xtermAddonSearch: '0.13.0',
|
||||
highlightJs: '11.11.1',
|
||||
apexcharts: '5.3.6',
|
||||
tiptap: '2.23.0',
|
||||
fontawesome: '7.1.0',
|
||||
apexcharts: '5.10.3',
|
||||
tiptap: '2.27.2',
|
||||
fontawesome: '7.2.0',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user