Compare commits

...

17 Commits

Author SHA1 Message Date
d82e5603a7 v3.41.5
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-02-01 23:51:53 +00:00
7e2386bcdf fix(dees-service-lib-loader): prevent horizontal scrollbar by offsetting xterm WidthCache measurement container 2026-02-01 23:51:53 +00:00
eba2a03355 v3.41.4
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-29 13:21:30 +00:00
06c01f0690 fix(): no changes 2026-01-29 13:21:30 +00:00
91e03eb9c4 v3.41.3
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-29 07:46:19 +00:00
b7f3f47c61 fix(dees-pdf-viewer): use in-memory PDF data for download and print; add robust print wrapper, cleanup and error handling 2026-01-29 07:46:19 +00:00
0a83f0e136 update 2026-01-28 17:10:37 +00:00
2b048cf34f update 2026-01-28 17:02:52 +00:00
7e50b8cb3f update 2026-01-28 16:46:07 +00:00
b97601a876 v3.41.2
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-01-28 16:17:05 +00:00
5ddeb8fe7c fix(dees-pdf-viewer): account for devicePixelRatio when setting canvas dimensions and scale 2D context to render crisp PDF pages and thumbnails on high-DPI displays 2026-01-28 16:17:05 +00:00
25f46162c5 v3.41.1
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-27 14:02:25 +00:00
b84b0e7ce6 fix(dataview-codebox): fix dees-dataview codebox layout to ensure full-height, proper flex behavior and scrolling; bump internal dependencies 2026-01-27 14:02:25 +00:00
69840de3a6 v3.41.0
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-27 12:36:46 +00:00
85badfbd21 feat(docs): document new media & tile components and expand Workspace/IDE component docs; update component count to 90+ 2026-01-27 12:36:46 +00:00
264e460365 v3.40.0
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-27 11:36:24 +00:00
bfda6b75e7 feat(dees-tile): unify tile badge styling and markup; replace component-specific badge classes with shared tile-badge classes and update related imports/tests 2026-01-27 11:36:24 +00:00
25 changed files with 796 additions and 209 deletions

View File

@@ -1,5 +1,59 @@
# Changelog # Changelog
## 2026-02-01 - 3.41.5 - fix(dees-service-lib-loader)
prevent horizontal scrollbar by offsetting xterm WidthCache measurement container
- Injects additional CSS into DeesServiceLibLoader to move xterm.js WidthCache measurement div off-screen horizontally (selector: body > div[style*="top: -50000px"][style*="width: 50000px"])
- Fixes root cause where xterm creates a large-width measurement container (width: 50000px) on document.body that expands scrollWidth and causes a horizontal scrollbar
- Change applied in ts_web/services/DeesServiceLibLoader.ts by concatenating the fix CSS into the injected stylesheet
## 2026-01-29 - 3.41.4 - fix()
no changes
- No files changed in this commit; no code or documentation modified
- No release required
## 2026-01-29 - 3.41.3 - fix(dees-pdf-viewer)
use in-memory PDF data for download and print; add robust print wrapper, cleanup and error handling
- Download and Print now use pdfDocument.getData() to create Blob URLs so in-memory PDFs (pdf.js) can be saved/printed.
- Print flow now opens an HTML wrapper with an iframe to allow onafterprint handling, auto-close, popup-fallback and timed cleanup of Blob URLs.
- Added try/catch logging, URL.revokeObjectURL calls and safety timeouts to avoid resource leaks.
- Removed context menu items that relied on the raw PDF URL (Open in New Tab, Copy PDF URL); Download/Print actions now await the async handlers.
## 2026-01-28 - 3.41.2 - fix(dees-pdf-viewer)
account for devicePixelRatio when setting canvas dimensions and scale 2D context to render crisp PDF pages and thumbnails on high-DPI displays
- Multiply canvas.width and canvas.height by window.devicePixelRatio (dpr) and use Math.floor to set the actual pixel buffer size
- Call ctx.scale(dpr, dpr) so drawing is rendered at device pixels while keeping CSS display size unchanged
- Apply the same high-DPI adjustments to both main page rendering and thumbnail generation
- Keep canvas.style.width and canvas.style.height set to viewport dimensions to preserve layout
## 2026-01-27 - 3.41.1 - fix(dataview-codebox)
fix dees-dataview codebox layout to ensure full-height, proper flex behavior and scrolling; bump internal dependencies
- Updated CSS in ts_web/elements/00group-dataview/dees-dataview-codebox/dees-dataview-codebox.ts: added height:100%, box-sizing, display:flex and flex-direction:column on container, set flex-shrink on header elements, made code grid overflow:auto with flex:1 and min-height:0 to prevent overflow issues.
- Bumped dependencies in package.json: @design.estate/dees-domtools from ^2.3.7 to ^2.3.8 and @design.estate/dees-element from ^2.1.5 to ^2.1.6.
- Non-breaking visual/layout fix — suitable for a patch release.
## 2026-01-27 - 3.41.0 - feat(docs)
document new media & tile components and expand Workspace/IDE component docs; update component count to 90+
- Updated README component count from 70+ to 90+.
- Added Media & Tile components documentation (DeesTilePdf, DeesTileImage, DeesTileAudio, DeesTileVideo, DeesTileNote, DeesTileFolder, DeesPreview, DeesPdfViewer, DeesImageViewer, DeesAudioViewer, DeesVideoViewer).
- Expanded Workspace/IDE documentation and introduced workspace subcomponents (DeesWorkspace, DeesWorkspaceMonaco, DeesWorkspaceDiffEditor, DeesWorkspaceFiletree, DeesWorkspaceTerminal, DeesWorkspaceTerminalPreview, DeesWorkspaceMarkdown, DeesWorkspaceMarkdownoutlet, DeesWorkspaceBottombar).
- Enhanced Contextmenu docs to demonstrate nested submenus and programmatic API usage.
- Added ITileFolderItem interface example for tile folder items.
## 2026-01-27 - 3.40.0 - feat(dees-tile)
unify tile badge styling and markup; replace component-specific badge classes with shared tile-badge classes and update related imports/tests
- Add shared badge styles: .tile-badge-corner, .tile-badge-topright, .tile-badge and layout rules in dees-tile-shared/styles.ts
- Update tile components (audio, video, image, folder, note, pdf) to use new badge markup and remove duplicated badge CSS
- Shift badge positioning when a tile label is present (.tile-container:has(.tile-label))
- Remove older per-component badge rules (duration-badge, item-count-badge, dimension-badge, note-language/note-line-indicator, preview-page-indicator) and replace with unified classes
- Update tests to import dees-contextmenu and dees-dashboardgrid from new 00group-overlay / 00group-layout paths to reflect folder reorganization
## 2026-01-27 - 3.39.1 - fix(dees-tile-note) ## 2026-01-27 - 3.39.1 - fix(dees-tile-note)
use horizontal pointer position to scroll note body by computing percentage from clientX and element width instead of clientY and height use horizontal pointer position to scroll note body by computing percentage from clientX and element width instead of clientY and height

View File

@@ -1,6 +1,6 @@
{ {
"name": "@design.estate/dees-catalog", "name": "@design.estate/dees-catalog",
"version": "3.39.1", "version": "3.41.5",
"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,8 +16,8 @@
"author": "Lossless GmbH", "author": "Lossless GmbH",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@design.estate/dees-domtools": "^2.3.7", "@design.estate/dees-domtools": "^2.3.8",
"@design.estate/dees-element": "^2.1.5", "@design.estate/dees-element": "^2.1.6",
"@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.1.0",
"@fortawesome/free-brands-svg-icons": "^7.1.0", "@fortawesome/free-brands-svg-icons": "^7.1.0",

34
pnpm-lock.yaml generated
View File

@@ -9,11 +9,11 @@ importers:
.: .:
dependencies: dependencies:
'@design.estate/dees-domtools': '@design.estate/dees-domtools':
specifier: ^2.3.7 specifier: ^2.3.8
version: 2.3.7 version: 2.3.8
'@design.estate/dees-element': '@design.estate/dees-element':
specifier: ^2.1.5 specifier: ^2.1.6
version: 2.1.5 version: 2.1.6
'@design.estate/dees-wcctools': '@design.estate/dees-wcctools':
specifier: ^3.8.0 specifier: ^3.8.0
version: 3.8.0 version: 3.8.0
@@ -352,11 +352,11 @@ packages:
'@design.estate/dees-comms@1.0.30': '@design.estate/dees-comms@1.0.30':
resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==} resolution: {integrity: sha512-KchMlklJfKAjQiJiR0xmofXtQ27VgZtBIxcMwPE9d+h3jJRv+lPZxzBQVOM0eyM0uS44S5vJMZ11IeV4uDXSHg==}
'@design.estate/dees-domtools@2.3.7': '@design.estate/dees-domtools@2.3.8':
resolution: {integrity: sha512-MXoDBrP7JTOpni8b12aFXHJKnKBoQppM8cYBuL9cesRmCVGdB7p39XMRQ7dRyMhmmyr66L3cOczhiCV6febCwg==} resolution: {integrity: sha512-jUG9GMvPxKMwmRIZ9oLTL3c8hHvHuiwIk8cTrYnuZzGO/uJJ5/czk9o6LRXUuCOOG7TRLtqgOpK8EEQgaadfZA==}
'@design.estate/dees-element@2.1.5': '@design.estate/dees-element@2.1.6':
resolution: {integrity: sha512-czUOFvBiUKi34I+/keDRDc71fuORZS0NfbSuD2jJ4D1ODiTPjaZ6A6SkdQ2QqCEzVsx73XF99Pu8pxPnaOLnHg==} resolution: {integrity: sha512-7zyHkUjB8UEQgT9VbB2IJtc/yuPt9CI5JGel3b6BxA1kecY64ceIjFvof1uIkc0QP8q2fMLLY45r1c+9zDTjzg==}
'@design.estate/dees-wcctools@3.8.0': '@design.estate/dees-wcctools@3.8.0':
resolution: {integrity: sha512-CC14iVKUrguzD9jIrdPBd9fZ4egVJEZMxl5y8iy0l7WLumeoYvGsoXj5INVkRPLRVLqziIdi4Je1hXqHt2NU+g==} resolution: {integrity: sha512-CC14iVKUrguzD9jIrdPBd9fZ4egVJEZMxl5y8iy0l7WLumeoYvGsoXj5INVkRPLRVLqziIdi4Je1hXqHt2NU+g==}
@@ -4730,8 +4730,8 @@ snapshots:
'@design.estate/dees-catalog@3.37.0(@tiptap/pm@2.27.2)': '@design.estate/dees-catalog@3.37.0(@tiptap/pm@2.27.2)':
dependencies: dependencies:
'@design.estate/dees-domtools': 2.3.7 '@design.estate/dees-domtools': 2.3.8
'@design.estate/dees-element': 2.1.5 '@design.estate/dees-element': 2.1.6
'@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.1.0
'@fortawesome/free-brands-svg-icons': 7.1.0 '@fortawesome/free-brands-svg-icons': 7.1.0
@@ -4770,7 +4770,7 @@ snapshots:
'@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdelay': 3.0.5
broadcast-channel: 7.3.0 broadcast-channel: 7.3.0
'@design.estate/dees-domtools@2.3.7': '@design.estate/dees-domtools@2.3.8':
dependencies: dependencies:
'@api.global/typedrequest': 3.2.5 '@api.global/typedrequest': 3.2.5
'@design.estate/dees-comms': 1.0.30 '@design.estate/dees-comms': 1.0.30
@@ -4796,9 +4796,9 @@ snapshots:
- supports-color - supports-color
- vue - vue
'@design.estate/dees-element@2.1.5': '@design.estate/dees-element@2.1.6':
dependencies: dependencies:
'@design.estate/dees-domtools': 2.3.7 '@design.estate/dees-domtools': 2.3.8
'@push.rocks/isounique': 1.0.5 '@push.rocks/isounique': 1.0.5
'@push.rocks/smartrx': 3.0.10 '@push.rocks/smartrx': 3.0.10
lit: 3.3.2 lit: 3.3.2
@@ -4810,8 +4810,8 @@ snapshots:
'@design.estate/dees-wcctools@3.8.0': '@design.estate/dees-wcctools@3.8.0':
dependencies: dependencies:
'@design.estate/dees-domtools': 2.3.7 '@design.estate/dees-domtools': 2.3.8
'@design.estate/dees-element': 2.1.5 '@design.estate/dees-element': 2.1.6
'@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdelay': 3.0.5
lit: 3.3.2 lit: 3.3.2
transitivePeerDependencies: transitivePeerDependencies:
@@ -5921,7 +5921,7 @@ snapshots:
'@push.rocks/smartntml@2.0.8': '@push.rocks/smartntml@2.0.8':
dependencies: dependencies:
'@design.estate/dees-element': 2.1.5 '@design.estate/dees-element': 2.1.6
'@happy-dom/global-registrator': 15.11.7 '@happy-dom/global-registrator': 15.11.7
'@push.rocks/smartpromise': 4.2.3 '@push.rocks/smartpromise': 4.2.3
fake-indexeddb: 6.2.5 fake-indexeddb: 6.2.5
@@ -6178,7 +6178,7 @@ snapshots:
'@push.rocks/taskbuffer@3.5.0': '@push.rocks/taskbuffer@3.5.0':
dependencies: dependencies:
'@design.estate/dees-element': 2.1.5 '@design.estate/dees-element': 2.1.6
'@push.rocks/lik': 6.2.2 '@push.rocks/lik': 6.2.2
'@push.rocks/smartdelay': 3.0.5 '@push.rocks/smartdelay': 3.0.5
'@push.rocks/smartlog': 3.1.10 '@push.rocks/smartlog': 3.1.10

274
readme.md
View File

@@ -1,6 +1,6 @@
# @design.estate/dees-catalog # @design.estate/dees-catalog
A comprehensive web components library built with TypeScript and LitElement, providing **70+ production-ready UI components** for building modern web applications with consistent design and behavior. 🚀 A comprehensive web components library built with TypeScript and LitElement, providing **90+ production-ready UI components** for building modern web applications with consistent design and behavior. 🚀
[![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/) [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
[![LitElement](https://img.shields.io/badge/LitElement-4.0+-orange.svg)](https://lit.dev/) [![LitElement](https://img.shields.io/badge/LitElement-4.0+-orange.svg)](https://lit.dev/)
@@ -18,6 +18,8 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
- 🔧 **TypeScript-First** — Fully typed APIs with excellent IDE support - 🔧 **TypeScript-First** — Fully typed APIs with excellent IDE support
- 🧩 **Modular** — Use only what you need, tree-shakeable architecture - 🧩 **Modular** — Use only what you need, tree-shakeable architecture
- 🏗️ **Full App Shell**`dees-appui` provides a complete application framework with menus, routing, activity log, and bottom bar - 🏗️ **Full App Shell**`dees-appui` provides a complete application framework with menus, routing, activity log, and bottom bar
- 🎬 **Media Components** — Rich tile-based previews for PDFs, images, audio, video, notes, and folders
- 💻 **IDE Workspace** — Full workspace component with Monaco editor, file tree, terminal, and diff viewer
## 📦 Installation ## 📦 Installation
@@ -55,12 +57,13 @@ For developers working on this library, please refer to the [UI Components Playb
|----------|------------| |----------|------------|
| **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols), [`DeesActionbar`](#deesactionbar) | | **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols), [`DeesActionbar`](#deesactionbar) |
| **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputToggle`](#deesinputtoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputList`](#deesinputlist), [`DeesInputProfilepicture`](#deesinputprofilepicture), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesInputCode`](#deesinputcode), [`DeesFormSubmit`](#deesformsubmit) | | **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputToggle`](#deesinputtoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputList`](#deesinputlist), [`DeesInputProfilepicture`](#deesinputprofilepicture), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesInputCode`](#deesinputcode), [`DeesFormSubmit`](#deesformsubmit) |
| **App Shell (Layout)** | [`DeesAppui`](#deesappui-), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiBottombar`](#deesappuibottombar), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) | | **App Shell (Layout)** | [`DeesAppui`](#deesappui-), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiBottombar`](#deesappuibottombar), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
| **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination) | | **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination) |
| **Media & Tiles** | [`DeesTilePdf`](#deestilepdf), [`DeesTileImage`](#deestileimage), [`DeesTileAudio`](#deestileaudio), [`DeesTileVideo`](#deestilevideo), [`DeesTileNote`](#deestilenote), [`DeesTileFolder`](#deestilefolder), [`DeesPreview`](#deespreview), [`DeesPdfViewer`](#deespdfviewer), [`DeesPdfPreview`](#deespdfpreview), [`DeesImageViewer`](#deesimageviewer), [`DeesAudioViewer`](#deesaudioviewer), [`DeesVideoViewer`](#deesvideoviewer) |
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) | | **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) |
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) | | **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
| **Navigation** | [`DeesStepper`](#deesstepper), [`DeesProgressbar`](#deesprogressbar) | | **Navigation** | [`DeesStepper`](#deesstepper), [`DeesProgressbar`](#deesprogressbar) |
| **Workspace / IDE** | [`DeesEditor`](#deeseditor), [`DeesTerminal`](#deesterminal), [`DeesUpdater`](#deesupdater) | | **Workspace / IDE** | [`DeesWorkspace`](#deesworkspace), [`DeesWorkspaceMonaco`](#deesworkspacemonaco), [`DeesWorkspaceDiffEditor`](#deesworkspacediffeditor), [`DeesWorkspaceFiletree`](#deesworkspacefiletree), [`DeesWorkspaceTerminal`](#deesworkspaceterminal), [`DeesWorkspaceTerminalPreview`](#deesworkspaceterminalpreview), [`DeesWorkspaceMarkdown`](#deesworkspacemarkdown), [`DeesWorkspaceMarkdownoutlet`](#deesworkspacemarkdownoutlet), [`DeesWorkspaceBottombar`](#deesworkspacebottombar) |
| **Theming** | [`DeesTheme`](#deestheme) | | **Theming** | [`DeesTheme`](#deestheme) |
| **Pre-built Templates** | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) | | **Pre-built Templates** | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) |
| **Shopping** | [`DeesShoppingProductcard`](#deesshoppingproductcard) | | **Shopping** | [`DeesShoppingProductcard`](#deesshoppingproductcard) |
@@ -1234,6 +1237,146 @@ Pagination component for navigating through large datasets.
--- ---
### Media & Tile Components 🎬
A rich collection of tile-based preview components for displaying media files in grids. All tiles share a consistent base class (`DeesTileBase`) with lazy loading via `IntersectionObserver`, hover interactions, click events, context menus, and three size variants (`small`, `default`, `large`).
All tile badges use a unified styling system with label-awareness — when a `label` is set, bottom badges automatically shift up to avoid overlapping.
#### `DeesTilePdf`
PDF document tile with live page preview on hover.
```typescript
<dees-tile-pdf
pdfUrl="/documents/report.pdf"
label="Annual Report"
clickable
@tile-click=${handleClick}
></dees-tile-pdf>
```
**Key Features:**
- Renders first page as canvas preview
- Hover to scrub through pages (mouse X position maps to page number)
- Shows page count badge, hover page indicator
- Detects A4/Letter vs non-standard aspect ratios
#### `DeesTileImage`
Image tile with lazy loading and dimension display.
```typescript
<dees-tile-image
src="/photos/landscape.jpg"
alt="Mountain landscape"
label="landscape.jpg"
clickable
@tile-click=${handleClick}
></dees-tile-image>
```
**Key Features:**
- Lazy loads image on scroll into view
- Shows image dimensions on hover (e.g. "1920 × 1080")
- Checkerboard background for transparent images
#### `DeesTileAudio`
Audio file tile with waveform visualization.
```typescript
<dees-tile-audio
src="/music/track.mp3"
title="Summer Vibes"
artist="DJ Example"
clickable
@tile-click=${handleClick}
></dees-tile-audio>
```
**Key Features:**
- Generates waveform visualization from audio data
- Shows duration badge (e.g. "3:42")
- Displays title and artist metadata
- Play overlay on hover
#### `DeesTileVideo`
Video tile with thumbnail capture and hover preview.
```typescript
<dees-tile-video
src="/videos/intro.mp4"
poster="/thumbs/intro.jpg"
label="Introduction"
clickable
@tile-click=${handleClick}
></dees-tile-video>
```
**Key Features:**
- Auto-captures first frame as thumbnail (or uses provided `poster`)
- Plays video preview on hover
- Shows duration badge
- Play button overlay
#### `DeesTileNote`
Code/text snippet tile with syntax-aware display.
```typescript
<dees-tile-note
title="config.ts"
language="TypeScript"
.content=${codeString}
clickable
@tile-click=${handleClick}
></dees-tile-note>
```
**Key Features:**
- Monospace font with line numbers
- Language badge (top-right)
- Scrollable content on hover (mouse X position controls scroll)
- Line indicator badge while scrolling
- Gradient fade at bottom
#### `DeesTileFolder`
Folder tile with 2×2 content preview grid.
```typescript
<dees-tile-folder
name="Project Assets"
.items=${[
{ type: 'image', name: 'logo.png', thumbnailSrc: '/thumbs/logo.png' },
{ type: 'pdf', name: 'spec.pdf' },
{ type: 'audio', name: 'jingle.mp3' },
{ type: 'video', name: 'demo.mp4' },
]}
clickable
@tile-click=${handleClick}
></dees-tile-folder>
```
**Key Features:**
- 2×2 preview grid showing first 4 items (thumbnails or type icons)
- Item count badge (e.g. "12 items")
- Folder icon header with name
- Supports: `pdf`, `image`, `audio`, `video`, `note`, `folder`, `unknown` types
#### `DeesPreview`
Unified preview component that auto-selects the right tile type based on content.
#### `DeesPdfViewer` / `DeesPdfPreview`
Full PDF viewing components with navigation controls.
#### `DeesImageViewer`
Full-screen image viewer with zoom and pan.
#### `DeesAudioViewer`
Audio playback component with waveform and controls.
#### `DeesVideoViewer`
Video playback component with standard controls.
---
### Visualization Components ### Visualization Components
#### `DeesChartArea` #### `DeesChartArea`
@@ -1294,16 +1437,41 @@ DeesModal.createAndShow({
``` ```
#### `DeesContextmenu` #### `DeesContextmenu`
Context menu component for right-click actions. Context menu component for right-click actions with nested submenu support.
```typescript ```typescript
<dees-contextmenu // Programmatic usage
.items=${[ DeesContextmenu.openContextMenuWithOptions(mouseEvent, [
{ label: 'Edit', icon: 'edit', action: () => handleEdit() }, {
{ label: 'Delete', icon: 'trash', action: () => handleDelete() } name: 'Edit',
]} iconName: 'lucide:edit',
position="right" action: async () => handleEdit()
></dees-contextmenu> },
{ divider: true },
{
name: 'More Options',
iconName: 'lucide:moreHorizontal',
submenu: [
{ name: 'Duplicate', iconName: 'lucide:copy', action: async () => handleDuplicate() },
{ name: 'Archive', iconName: 'lucide:archive', action: async () => handleArchive() },
]
},
{
name: 'Delete',
iconName: 'lucide:trash2',
action: async () => handleDelete()
}
]);
// Component-based (implement getContextMenuItems on any element)
class MyComponent extends DeesElement {
getContextMenuItems() {
return [
{ name: 'View Details', iconName: 'lucide:eye', action: async () => { ... } },
{ name: 'Edit', iconName: 'lucide:edit', action: async () => { ... } },
];
}
}
``` ```
#### `DeesSpeechbubble` #### `DeesSpeechbubble`
@@ -1395,51 +1563,68 @@ Theme provider component that wraps children and provides CSS custom properties
--- ---
### Workspace / IDE Components ### Workspace / IDE Components 💻
#### `DeesEditor` A full-featured IDE workspace component suite for building browser-based code editors, terminal interfaces, and documentation viewers.
Code editor component with syntax highlighting and code completion, powered by Monaco Editor.
#### `DeesWorkspace`
Top-level workspace shell that composes editor, file tree, terminal, and bottom bar into an IDE-like layout.
```typescript ```typescript
<dees-editor <dees-workspace></dees-workspace>
```
#### `DeesWorkspaceMonaco`
Monaco Editor integration for code editing with full IntelliSense, syntax highlighting, and language support.
```typescript
<dees-workspace-monaco
.value=${code} .value=${code}
@change=${handleCodeChange}
.language=${'typescript'} .language=${'typescript'}
.theme=${'vs-dark'} @change=${handleCodeChange}
.options=${{ ></dees-workspace-monaco>
lineNumbers: true,
minimap: { enabled: true },
autoClosingBrackets: 'always'
}}
></dees-editor>
``` ```
#### `DeesTerminal` #### `DeesWorkspaceDiffEditor`
Terminal emulator component for command-line interface. Side-by-side diff editor powered by Monaco for comparing file versions.
```typescript ```typescript
<dees-terminal <dees-workspace-diff-editor
.commands=${{ .originalValue=${originalCode}
'echo': (args) => `Echo: ${args.join(' ')}`, .modifiedValue=${modifiedCode}
'help': () => 'Available commands: echo, help' .language=${'typescript'}
}} ></dees-workspace-diff-editor>
.prompt=${'$'}
.welcomeMessage=${'Welcome! Type "help" for available commands.'}
></dees-terminal>
``` ```
#### `DeesUpdater` #### `DeesWorkspaceFiletree`
Component for managing application updates and version control. File tree navigation component with expand/collapse, file icons, and selection.
```typescript ```typescript
<dees-updater <dees-workspace-filetree
.currentVersion=${'1.5.2'} .files=${fileTreeData}
.checkInterval=${3600000} @file-select=${handleFileSelect}
.autoUpdate=${false} ></dees-workspace-filetree>
@update-available=${handleUpdateAvailable}
></dees-updater>
``` ```
#### `DeesWorkspaceTerminal`
Terminal emulator component powered by xterm.js.
```typescript
<dees-workspace-terminal></dees-workspace-terminal>
```
#### `DeesWorkspaceTerminalPreview`
Terminal with integrated preview pane for output visualization.
#### `DeesWorkspaceMarkdown`
Markdown editor with live preview.
#### `DeesWorkspaceMarkdownoutlet`
Read-only markdown renderer for documentation display.
#### `DeesWorkspaceBottombar`
IDE-style bottom status bar for the workspace.
--- ---
### Pre-built Templates ### Pre-built Templates
@@ -1582,6 +1767,13 @@ interface IViewActivationContext {
viewId: string; viewId: string;
params?: Record<string, string>; params?: Record<string, string>;
} }
// Tile folder item (for DeesTileFolder)
interface ITileFolderItem {
type: 'pdf' | 'image' | 'audio' | 'video' | 'note' | 'folder' | 'unknown';
thumbnailSrc?: string;
name: string;
}
``` ```
--- ---

View File

@@ -1,6 +1,6 @@
import { expect, tap } from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js'; import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
import { demoFunc } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.demo.js'; import { demoFunc } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.demo.js';
tap.test('should render context menu demo', async () => { tap.test('should render context menu demo', async () => {
// Create demo container // Create demo container

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js'; import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
tap.test('should close all parent menus when clicking action in nested submenu', async () => { tap.test('should close all parent menus when clicking action in nested submenu', async () => {
let actionCalled = false; let actionCalled = false;

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js'; import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
import { DeesElement, customElement, html } from '@design.estate/dees-element'; import { DeesElement, customElement, html } from '@design.estate/dees-element';
// Create a test element with shadow DOM // Create a test element with shadow DOM

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js'; import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
tap.test('should show context menu with nested submenu', async () => { tap.test('should show context menu with nested submenu', async () => {
// Create a test element with context menu items // Create a test element with context menu items

View File

@@ -3,8 +3,8 @@ import { tap, expect } from '@git.zone/tstest/tapbundle';
import { import {
resolveWidgetPlacement, resolveWidgetPlacement,
collectCollisions, collectCollisions,
} from '../ts_web/elements/dees-dashboardgrid/layout.ts'; } from '../ts_web/elements/00group-layout/dees-dashboardgrid/layout.ts';
import type { DashboardWidget } from '../ts_web/elements/dees-dashboardgrid/types.ts'; import type { DashboardWidget } from '../ts_web/elements/00group-layout/dees-dashboardgrid/types.ts';
tap.test('dashboardgrid does not overlap widgets after swap attempt', async () => { tap.test('dashboardgrid does not overlap widgets after swap attempt', async () => {
const widgets: DashboardWidget[] = [ const widgets: DashboardWidget[] = [

View File

@@ -0,0 +1,79 @@
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
import * as deesCatalog from '../ts_web/index.js';
tap.test('PDF viewer should render text layer', async () => {
const viewer = await webhelpers.fixture(
webhelpers.html`
<dees-pdf-viewer
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
initialZoom="page-fit"
style="height: 600px; width: 100%;"
></dees-pdf-viewer>
`
) as deesCatalog.DeesPdfViewer;
// Wait for PDF to load and render
await new Promise(resolve => setTimeout(resolve, 5000));
await viewer.updateComplete;
expect(viewer.totalPages).toBeGreaterThan(0);
const textLayer = viewer.shadowRoot?.querySelector('.text-layer[data-page="1"]');
expect(textLayer).toBeTruthy();
const textSpans = textLayer?.querySelectorAll('span');
expect(textSpans?.length).toBeGreaterThan(0);
console.log(`Text layer has ${textSpans?.length} spans`);
});
tap.test('Text should be selectable', async () => {
const viewer = await webhelpers.fixture(
webhelpers.html`
<dees-pdf-viewer
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
initialZoom="page-fit"
style="height: 600px; width: 100%;"
></dees-pdf-viewer>
`
) as deesCatalog.DeesPdfViewer;
// Wait for PDF to load and render
await new Promise(resolve => setTimeout(resolve, 5000));
const textLayer = viewer.shadowRoot?.querySelector('.text-layer[data-page="1"]');
const firstSpan = textLayer?.querySelector('span') as HTMLElement;
if (firstSpan?.textContent) {
const range = document.createRange();
const textNode = firstSpan.firstChild;
if (textNode) {
range.setStart(textNode, 0);
range.setEnd(textNode, Math.min(5, textNode.textContent?.length || 0));
const selection = window.getSelection();
selection?.removeAllRanges();
selection?.addRange(range);
expect(selection?.toString().length).toBeGreaterThan(0);
console.log('Selected text:', selection?.toString());
}
}
});
tap.test('endOfContent element exists for selection boundary', async () => {
const viewer = await webhelpers.fixture(
webhelpers.html`
<dees-pdf-viewer
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
initialZoom="page-fit"
style="height: 600px; width: 100%;"
></dees-pdf-viewer>
`
) as deesCatalog.DeesPdfViewer;
// Wait for PDF to load and render
await new Promise(resolve => setTimeout(resolve, 5000));
const endOfContent = viewer.shadowRoot?.querySelector('.text-layer[data-page="1"] .endOfContent');
expect(endOfContent).toBeTruthy();
});
export default tap.start();

View File

@@ -1,6 +1,6 @@
import { expect, tap } from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js'; import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js'; import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
tap.test('should change block type via context menu', async () => { tap.test('should change block type via context menu', async () => {
// Create WYSIWYG editor with a paragraph // Create WYSIWYG editor with a paragraph

View File

@@ -1,6 +1,6 @@
import { expect, tap } from '@git.zone/tstest/tapbundle'; import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js'; import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js'; import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
tap.test('should show context menu on WYSIWYG blocks', async () => { tap.test('should show context menu on WYSIWYG blocks', async () => {
// Create WYSIWYG editor // Create WYSIWYG editor

View File

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

@@ -52,6 +52,8 @@ export class DeesDataviewCodebox extends DeesElement {
text-align: left; text-align: left;
font-size: 16px; font-size: 16px;
font-family: ${cssGeistFontFamily}; font-family: ${cssGeistFontFamily};
height: 100%;
box-sizing: border-box;
} }
.mainbox { .mainbox {
position: relative; position: relative;
@@ -61,6 +63,10 @@ export class DeesDataviewCodebox extends DeesElement {
background: ${cssManager.bdTheme('#ffffff', '#09090b')}; background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border-radius: 6px; border-radius: 6px;
overflow: hidden; overflow: hidden;
display: flex;
flex-direction: column;
height: 100%;
box-sizing: border-box;
} }
.appbar { .appbar {
@@ -74,6 +80,7 @@ export class DeesDataviewCodebox extends DeesElement {
line-height: 32px; line-height: 32px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-shrink: 0;
} }
.appbar .fileName { .appbar .fileName {
@@ -95,6 +102,7 @@ export class DeesDataviewCodebox extends DeesElement {
justify-content: flex-end; justify-content: flex-end;
align-items: stretch; align-items: stretch;
overflow: hidden; overflow: hidden;
flex-shrink: 0;
} }
.spacesLabel { .spacesLabel {
@@ -121,7 +129,9 @@ export class DeesDataviewCodebox extends DeesElement {
.codegrid { .codegrid {
display: grid; display: grid;
grid-template-columns: 50px auto; grid-template-columns: 50px auto;
overflow: hidden; overflow: auto;
flex: 1;
min-height: 0;
} }
.lineNumbers { .lineNumbers {

View File

@@ -52,7 +52,7 @@ export class DeesPdfViewer extends DeesElement {
accessor thumbnailData: Array<{page: number, rendered: boolean}> = []; accessor thumbnailData: Array<{page: number, rendered: boolean}> = [];
@property({ type: Array }) @property({ type: Array })
accessor pageData: Array<{page: number, rendered: boolean, rendering: boolean}> = []; accessor pageData: Array<{page: number, rendered: boolean, rendering: boolean, textLayerRendered: boolean}> = [];
private pdfDocument: any; private pdfDocument: any;
private renderState: RenderState = 'idle'; private renderState: RenderState = 'idle';
@@ -63,6 +63,7 @@ export class DeesPdfViewer extends DeesElement {
private currentRenderPromise: Promise<void> | null = null; private currentRenderPromise: Promise<void> | null = null;
private thumbnailRenderTasks: any[] = []; private thumbnailRenderTasks: any[] = [];
private pageRenderTasks: Map<number, any> = new Map(); private pageRenderTasks: Map<number, any> = new Map();
private textLayerRenderTasks: Map<number, any> = new Map();
private canvas: HTMLCanvasElement | undefined; private canvas: HTMLCanvasElement | undefined;
private ctx: CanvasRenderingContext2D | undefined; private ctx: CanvasRenderingContext2D | undefined;
private viewerMain: HTMLElement | null = null; private viewerMain: HTMLElement | null = null;
@@ -230,6 +231,7 @@ export class DeesPdfViewer extends DeesElement {
<div class="page-wrapper" data-page="${item.page}"> <div class="page-wrapper" data-page="${item.page}">
<div class="canvas-container"> <div class="canvas-container">
<canvas class="page-canvas" data-page="${item.page}"></canvas> <canvas class="page-canvas" data-page="${item.page}"></canvas>
<div class="text-layer" data-page="${item.page}"></div>
</div> </div>
</div> </div>
` `
@@ -330,7 +332,8 @@ export class DeesPdfViewer extends DeesElement {
this.pageData = Array.from({length: this.totalPages}, (_, i) => ({ this.pageData = Array.from({length: this.totalPages}, (_, i) => ({
page: i + 1, page: i + 1,
rendered: false, rendered: false,
rendering: false rendering: false,
textLayerRendered: false,
})); }));
// Set loading to false to render the pages // Set loading to false to render the pages
@@ -444,9 +447,10 @@ export class DeesPdfViewer extends DeesElement {
const page = await this.pdfDocument.getPage(pageNum); const page = await this.pdfDocument.getPage(pageNum);
const viewport = this.computeViewport(page); const viewport = this.computeViewport(page);
// Set canvas dimensions // Set canvas dimensions with device pixel ratio for sharp rendering
canvas.height = viewport.height; const dpr = window.devicePixelRatio || 1;
canvas.width = viewport.width; canvas.width = Math.floor(viewport.width * dpr);
canvas.height = Math.floor(viewport.height * dpr);
canvas.style.width = `${viewport.width}px`; canvas.style.width = `${viewport.width}px`;
canvas.style.height = `${viewport.height}px`; canvas.style.height = `${viewport.height}px`;
@@ -457,6 +461,9 @@ export class DeesPdfViewer extends DeesElement {
return; return;
} }
// Scale context for high-DPI displays
ctx.scale(dpr, dpr);
const renderContext = { const renderContext = {
canvasContext: ctx, canvasContext: ctx,
viewport: viewport, viewport: viewport,
@@ -472,6 +479,9 @@ export class DeesPdfViewer extends DeesElement {
pageInfo.rendering = false; pageInfo.rendering = false;
this.pageRenderTasks.delete(pageNum); this.pageRenderTasks.delete(pageNum);
// Render text layer for selection
await this.renderTextLayer(pageNum);
// Update page data to reflect rendered state // Update page data to reflect rendered state
this.requestUpdate('pageData'); this.requestUpdate('pageData');
} catch (error: any) { } catch (error: any) {
@@ -483,6 +493,132 @@ export class DeesPdfViewer extends DeesElement {
} }
} }
private async renderTextLayer(pageNum: number): Promise<void> {
const pageInfo = this.pageData.find(p => p.page === pageNum);
if (!pageInfo || pageInfo.textLayerRendered) return;
try {
const textLayerDiv = this.shadowRoot?.querySelector(
`.text-layer[data-page="${pageNum}"]`
) as HTMLElement;
if (!textLayerDiv) return;
textLayerDiv.innerHTML = '';
const page = await this.pdfDocument.getPage(pageNum);
const textContent = await page.getTextContent();
const viewport = this.computeViewport(page);
// @ts-ignore - Dynamic import of pdfjs
const pdfjs = await import('https://cdn.jsdelivr.net/npm/pdfjs-dist@4.0.379/+esm');
textLayerDiv.style.width = `${viewport.width}px`;
textLayerDiv.style.height = `${viewport.height}px`;
// Set the scale factor CSS variable - required by PDF.js text layer
textLayerDiv.style.setProperty('--scale-factor', String(viewport.scale));
const textLayerRenderTask = pdfjs.renderTextLayer({
textContentSource: textContent,
container: textLayerDiv,
viewport: viewport,
});
this.textLayerRenderTasks.set(pageNum, textLayerRenderTask);
await textLayerRenderTask.promise;
// Add endOfContent for selection boundary
const endOfContent = document.createElement('div');
endOfContent.className = 'endOfContent';
textLayerDiv.appendChild(endOfContent);
// Custom drag selection for Shadow DOM compatibility
// caretRangeFromPoint doesn't pierce shadow DOM, so we find spans manually
let isDragging = false;
let anchorNode: Node | null = null;
let anchorOffset = 0;
const getTextPositionFromPoint = (x: number, y: number): { node: Node; offset: number } | null => {
// Find span at coordinates by checking bounding rects
const spans = Array.from(textLayerDiv.querySelectorAll('span'));
for (const span of spans) {
const rect = span.getBoundingClientRect();
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
const textNode = span.firstChild;
if (textNode && textNode.nodeType === Node.TEXT_NODE) {
// Calculate character offset based on x position
const text = textNode.textContent || '';
const charWidth = rect.width / text.length;
const relativeX = x - rect.left;
const offset = Math.min(Math.round(relativeX / charWidth), text.length);
return { node: textNode, offset };
}
}
}
return null;
};
const handleMouseUp = () => {
if (isDragging) {
isDragging = false;
anchorNode = null;
textLayerDiv.classList.remove('selecting');
}
document.removeEventListener('mouseup', handleMouseUp);
document.removeEventListener('mousemove', handleMouseMove);
};
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging || !anchorNode) return;
e.preventDefault();
const pos = getTextPositionFromPoint(e.clientX, e.clientY);
if (pos) {
const selection = window.getSelection();
if (selection) {
try {
selection.setBaseAndExtent(anchorNode, anchorOffset, pos.node, pos.offset);
} catch (err) {
// Ignore errors from invalid selections
}
}
}
};
textLayerDiv.addEventListener('mousedown', (e: MouseEvent) => {
if (e.button !== 0) return;
const pos = getTextPositionFromPoint(e.clientX, e.clientY);
if (pos) {
// Prevent native selection behavior
e.preventDefault();
isDragging = true;
anchorNode = pos.node;
anchorOffset = pos.offset;
textLayerDiv.classList.add('selecting');
// Clear existing selection
const selection = window.getSelection();
selection?.removeAllRanges();
// Add document-level listeners for drag
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
});
pageInfo.textLayerRendered = true;
page.cleanup?.();
this.textLayerRenderTasks.delete(pageNum);
} catch (error: any) {
if (error?.name !== 'RenderingCancelledException') {
console.error(`Error rendering text layer for page ${pageNum}:`, error);
}
this.textLayerRenderTasks.delete(pageNum);
}
}
private handleScroll = () => { private handleScroll = () => {
// Throttle scroll events // Throttle scroll events
if (this.scrollThrottleTimeout) { if (this.scrollThrottleTimeout) {
@@ -652,9 +788,10 @@ export class DeesPdfViewer extends DeesElement {
const scale = maxThumbnailWidth / initialViewport.width; const scale = maxThumbnailWidth / initialViewport.width;
const viewport = page.getViewport({ scale }); const viewport = page.getViewport({ scale });
// Set canvas dimensions to actual render size // Set canvas dimensions with device pixel ratio for sharp thumbnails
canvas.width = viewport.width; const dpr = window.devicePixelRatio || 1;
canvas.height = viewport.height; canvas.width = Math.floor(viewport.width * dpr);
canvas.height = Math.floor(viewport.height * dpr);
// Set the display size via style to ensure proper display // Set the display size via style to ensure proper display
canvas.style.width = `${viewport.width}px`; canvas.style.width = `${viewport.width}px`;
@@ -670,6 +807,9 @@ export class DeesPdfViewer extends DeesElement {
continue; continue;
} }
// Scale context for high-DPI displays
context.scale(dpr, dpr);
const renderContext = { const renderContext = {
canvasContext: context, canvasContext: context,
viewport: viewport, viewport: viewport,
@@ -763,6 +903,7 @@ export class DeesPdfViewer extends DeesElement {
this.pageData.forEach(page => { this.pageData.forEach(page => {
page.rendered = false; page.rendered = false;
page.rendering = false; page.rendering = false;
page.textLayerRendered = false;
}); });
// Cancel any ongoing render tasks // Cancel any ongoing render tasks
@@ -775,6 +916,16 @@ export class DeesPdfViewer extends DeesElement {
}); });
this.pageRenderTasks.clear(); this.pageRenderTasks.clear();
// Cancel text layer render tasks
this.textLayerRenderTasks.forEach(task => {
try {
task.cancel?.();
} catch (error) {
// Ignore cancellation errors
}
});
this.textLayerRenderTasks.clear();
// Request update to re-render pages // Request update to re-render pages
this.requestUpdate(); this.requestUpdate();
@@ -784,52 +935,138 @@ export class DeesPdfViewer extends DeesElement {
}); });
} }
private downloadPdf() { private async downloadPdf() {
const link = document.createElement('a'); if (!this.pdfDocument) return;
link.href = this.pdfUrl;
link.download = this.pdfUrl.split('/').pop() || 'document.pdf'; try {
link.click(); // Get raw PDF data from the loaded document
const data = await this.pdfDocument.getData();
const blob = new Blob([data.buffer], { type: 'application/pdf' });
const blobUrl = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = blobUrl;
link.download = this.pdfUrl ? this.pdfUrl.split('/').pop() || 'document.pdf' : 'document.pdf';
link.click();
// Clean up blob URL after short delay
setTimeout(() => URL.revokeObjectURL(blobUrl), 1000);
} catch (error) {
console.error('Error downloading PDF:', error);
}
} }
private printPdf() { private async printPdf() {
window.open(this.pdfUrl, '_blank')?.print(); if (!this.pdfDocument) return;
try {
// Get raw PDF data from the loaded document
const data = await this.pdfDocument.getData();
const blob = new Blob([data.buffer], { type: 'application/pdf' });
const pdfUrl = URL.createObjectURL(blob);
// Create an HTML wrapper page that embeds the PDF and handles print/close
// This gives us control over the afterprint event (direct PDF URLs don't support it)
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<title>Print PDF</title>
<style>
* { margin: 0; padding: 0; }
html, body { width: 100%; height: 100%; overflow: hidden; }
iframe { width: 100%; height: 100%; border: none; }
@media print {
html, body, iframe { width: 100%; height: 100%; }
}
</style>
</head>
<body>
<iframe src="${pdfUrl}" type="application/pdf"></iframe>
<script>
window.onload = function() {
setTimeout(function() {
window.focus();
window.print();
}, 500);
};
window.onafterprint = function() {
window.close();
};
// Safety close after 2 minutes
setTimeout(function() { window.close(); }, 120000);
</script>
</body>
</html>
`;
const htmlBlob = new Blob([htmlContent], { type: 'text/html' });
const htmlUrl = URL.createObjectURL(htmlBlob);
const printWindow = window.open(htmlUrl, '_blank', 'width=800,height=600');
if (printWindow) {
// Cleanup blob URLs when window closes
const checkClosed = setInterval(() => {
if (printWindow.closed) {
clearInterval(checkClosed);
URL.revokeObjectURL(pdfUrl);
URL.revokeObjectURL(htmlUrl);
}
}, 500);
// Safety cleanup after 2 minutes
setTimeout(() => {
clearInterval(checkClosed);
URL.revokeObjectURL(pdfUrl);
URL.revokeObjectURL(htmlUrl);
}, 120000);
} else {
// Popup blocked - fall back to direct navigation
window.open(pdfUrl, '_blank');
setTimeout(() => URL.revokeObjectURL(pdfUrl), 60000);
URL.revokeObjectURL(htmlUrl);
}
} catch (error) {
console.error('Error printing PDF:', error);
}
} }
/** /**
* Provide context menu items for right-click functionality * Provide context menu items for right-click functionality
*/ */
public getContextMenuItems() { public getContextMenuItems() {
return [ const items: any[] = [];
{
name: 'Open PDF in New Tab', // Add copy option if text is selected
iconName: 'lucide:ExternalLink', const selection = window.getSelection();
action: async () => { const selectedText = selection?.toString() || '';
window.open(this.pdfUrl, '_blank'); if (selectedText) {
} items.push({
}, name: 'Copy',
{ divider: true },
{
name: 'Copy PDF URL',
iconName: 'lucide:Copy', iconName: 'lucide:Copy',
action: async () => { action: async () => {
await navigator.clipboard.writeText(this.pdfUrl); await navigator.clipboard.writeText(selectedText);
} }
}, });
items.push({ divider: true });
}
items.push(
{ {
name: 'Download PDF', name: 'Download PDF',
iconName: 'lucide:Download', iconName: 'lucide:Download',
action: async () => { action: async () => {
this.downloadPdf(); await this.downloadPdf();
} }
}, },
{ {
name: 'Print PDF', name: 'Print PDF',
iconName: 'lucide:Printer', iconName: 'lucide:Printer',
action: async () => { action: async () => {
this.printPdf(); await this.printPdf();
} }
} }
]; );
return items;
} }
private get canZoomIn(): boolean { private get canZoomIn(): boolean {
@@ -988,6 +1225,16 @@ export class DeesPdfViewer extends DeesElement {
}); });
this.pageRenderTasks.clear(); this.pageRenderTasks.clear();
// Cancel text layer render tasks
this.textLayerRenderTasks.forEach(task => {
try {
task.cancel?.();
} catch (error) {
// Ignore cancellation errors
}
});
this.textLayerRenderTasks.clear();
// Cancel any thumbnail render tasks // Cancel any thumbnail render tasks
for (const task of (this.thumbnailRenderTasks || [])) { for (const task of (this.thumbnailRenderTasks || [])) {
try { try {

View File

@@ -276,6 +276,7 @@ export const viewerStyles = [
border-radius: 4px; border-radius: 4px;
overflow: hidden; overflow: hidden;
display: inline-block; display: inline-block;
position: relative;
} }
.page-canvas { .page-canvas {
@@ -284,6 +285,52 @@ export const viewerStyles = [
image-rendering: crisp-edges; image-rendering: crisp-edges;
} }
/* Text layer for selection */
.text-layer {
position: absolute;
inset: 0;
overflow: visible;
line-height: 1;
text-size-adjust: none;
forced-color-adjust: none;
transform-origin: 0 0;
z-index: 1;
user-select: text;
-webkit-user-select: text;
}
.text-layer span,
.text-layer br {
color: transparent;
position: absolute;
white-space: pre;
cursor: text;
transform-origin: 0% 0%;
user-select: text;
-webkit-user-select: text;
}
.text-layer ::selection {
background: rgba(0, 100, 200, 0.3);
}
.text-layer br::selection {
background: transparent;
}
.text-layer .endOfContent {
display: block;
position: absolute;
inset: 100% 0 0;
z-index: 0;
cursor: default;
user-select: none;
}
.text-layer.selecting .endOfContent {
top: 0;
}
.pdf-viewer.with-sidebar .viewer-main { .pdf-viewer.with-sidebar .viewer-main {
margin-left: 0; margin-left: 0;
} }

View File

@@ -84,20 +84,6 @@ export class DeesTileAudio extends DeesTileBase {
display: block; display: block;
} }
.duration-badge {
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;
backdrop-filter: blur(8px);
z-index: 10;
}
.play-overlay { .play-overlay {
position: absolute; position: absolute;
@@ -177,7 +163,7 @@ export class DeesTileAudio extends DeesTileBase {
</div> </div>
${this.duration > 0 ? html` ${this.duration > 0 ? html`
<div class="duration-badge">${this.formatTime(this.duration)}</div> <div class="tile-badge-corner">${this.formatTime(this.duration)}</div>
` : ''} ` : ''}
<div class="play-overlay"> <div class="play-overlay">

View File

@@ -109,19 +109,6 @@ export class DeesTileFolder extends DeesTileBase {
background: ${cssManager.bdTheme('hsl(215 15% 96%)', 'hsl(215 20% 16%)')}; background: ${cssManager.bdTheme('hsl(215 15% 96%)', 'hsl(215 20% 16%)')};
} }
.item-count-badge {
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;
backdrop-filter: blur(8px);
z-index: 10;
}
`, `,
] as any; ] as any;
@@ -158,7 +145,7 @@ export class DeesTileFolder extends DeesTileBase {
</div> </div>
</div> </div>
<div class="item-count-badge"> <div class="tile-badge-corner">
${this.items.length} item${this.items.length !== 1 ? 's' : ''} ${this.items.length} item${this.items.length !== 1 ? 's' : ''}
</div> </div>

View File

@@ -55,24 +55,12 @@ export class DeesTileImage extends DeesTileBase {
opacity: 0; opacity: 0;
} }
.dimension-badge { .tile-badge-topright.dimension-badge {
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;
opacity: 0; opacity: 0;
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
} }
.tile-container.clickable:hover .dimension-badge { .tile-container.clickable:hover .tile-badge-topright.dimension-badge {
opacity: 1; opacity: 1;
} }
`, `,
@@ -110,7 +98,7 @@ export class DeesTileImage extends DeesTileBase {
</div> </div>
${this.imageWidth > 0 && this.imageHeight > 0 ? html` ${this.imageWidth > 0 && this.imageHeight > 0 ? html`
<div class="dimension-badge"> <div class="tile-badge-topright dimension-badge">
${this.imageWidth} × ${this.imageHeight} ${this.imageWidth} × ${this.imageHeight}
</div> </div>
` : ''} ` : ''}

View File

@@ -81,16 +81,10 @@ export class DeesTileNote extends DeesTileBase {
pointer-events: none; pointer-events: none;
} }
.note-language { .tile-badge-topright.note-language {
position: absolute;
top: 8px;
right: 8px;
padding: 2px 6px;
background: ${cssManager.bdTheme('hsl(215 20% 92%)', 'hsl(215 20% 88%)')}; background: ${cssManager.bdTheme('hsl(215 20% 92%)', 'hsl(215 20% 88%)')};
color: ${cssManager.bdTheme('hsl(215 16% 50%)', 'hsl(215 16% 40%)')}; color: ${cssManager.bdTheme('hsl(215 16% 50%)', 'hsl(215 16% 40%)')};
border-radius: 3px;
font-size: 9px; font-size: 9px;
font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
z-index: 5; z-index: 5;
} }
@@ -116,21 +110,6 @@ export class DeesTileNote extends DeesTileBase {
padding-right: 6px; padding-right: 6px;
} }
.note-line-indicator {
position: absolute;
bottom: 8px;
right: 8px;
padding: 3px 8px;
background: rgba(0, 0, 0, 0.6);
color: white;
border-radius: 4px;
font-size: 9px;
font-weight: 600;
font-variant-numeric: tabular-nums;
backdrop-filter: blur(8px);
z-index: 10;
pointer-events: none;
}
`, `,
] as any; ] as any;
@@ -154,7 +133,7 @@ export class DeesTileNote extends DeesTileBase {
return html` return html`
<div class="note-content"> <div class="note-content">
${this.language ? html` ${this.language ? html`
<div class="note-language">${this.language}</div> <div class="tile-badge-topright note-language">${this.language}</div>
` : ''} ` : ''}
${this.title ? html` ${this.title ? html`
@@ -169,7 +148,7 @@ export class DeesTileNote extends DeesTileBase {
</div> </div>
${this.isHovering && lines.length > 12 ? html` ${this.isHovering && lines.length > 12 ? html`
<div class="note-line-indicator"> <div class="tile-badge-corner">
Line ${this.getVisibleLineRange(lines.length)} Line ${this.getVisibleLineRange(lines.length)}
</div> </div>
` : ''} ` : ''}

View File

@@ -55,15 +55,14 @@ export class DeesTilePdf extends DeesTileBase {
</div> </div>
${this.pageCount > 1 && this.isHovering ? html` ${this.pageCount > 1 && this.isHovering ? html`
<div class="preview-page-indicator"> <div class="tile-badge">
Page ${this.currentPreviewPage} of ${this.pageCount} Page ${this.currentPreviewPage} of ${this.pageCount}
</div> </div>
` : ''} ` : ''}
${this.pageCount > 0 && !this.isHovering ? html` ${this.pageCount > 0 && !this.isHovering ? html`
<div class="tile-info"> <div class="tile-badge-corner">
<dees-icon icon="lucide:FileText"></dees-icon> ${this.pageCount} page${this.pageCount > 1 ? 's' : ''}
<span class="tile-info-text">${this.pageCount} page${this.pageCount > 1 ? 's' : ''}</span>
</div> </div>
` : ''} ` : ''}

View File

@@ -35,24 +35,6 @@ export const tilePdfStyles = css`
border-radius: 4px; border-radius: 4px;
} }
.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;
}
/* Grid optimizations */ /* Grid optimizations */
:host([grid-mode]) .preview-canvas { :host([grid-mode]) .preview-canvas {
image-rendering: -webkit-optimize-contrast; image-rendering: -webkit-optimize-contrast;

View File

@@ -117,6 +117,46 @@ export const tileBaseStyles = [
animation: fadeIn 0.2s ease; 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;
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;
}
/* Shift bottom badges up when label is present */
.tile-container:has(.tile-label) .tile-badge-corner {
bottom: 33px;
}
.tile-container:has(.tile-label) .tile-info {
bottom: 33px;
}
.tile-loading, .tile-loading,
.tile-error { .tile-error {
position: absolute; position: absolute;

View File

@@ -54,20 +54,6 @@ export class DeesTileVideo extends DeesTileBase {
display: block; display: block;
} }
.duration-badge {
position: absolute;
bottom: 8px;
right: 8px;
padding: 3px 8px;
background: rgba(0, 0, 0, 0.7);
color: white;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
font-variant-numeric: tabular-nums;
backdrop-filter: blur(8px);
z-index: 10;
}
.play-overlay { .play-overlay {
position: absolute; position: absolute;
@@ -155,7 +141,7 @@ export class DeesTileVideo extends DeesTileBase {
</div> </div>
${this.duration > 0 ? html` ${this.duration > 0 ? html`
<div class="duration-badge">${this.formatTime(this.duration)}</div> <div class="tile-badge-corner">${this.formatTime(this.duration)}</div>
` : ''} ` : ''}
${!this.isHovering ? html` ${!this.isHovering ? html`

View File

@@ -193,9 +193,20 @@ export class DeesServiceLibLoader {
const response = await fetch(cssUrl); const response = await fetch(cssUrl);
const cssText = await response.text(); const cssText = await response.text();
// Fix for xterm.js WidthCache measurement container causing horizontal scrollbar
// xterm.js creates this on document.body with width: 50000px, top: -50000px
// Moving it off-screen horizontally prevents scrollWidth expansion
const xtermMeasurementFix = `
/* Fix xterm.js WidthCache measurement container causing horizontal scrollbar */
/* xterm creates this on document.body - move it off-screen horizontally too */
body > div[style*="top: -50000px"][style*="width: 50000px"] {
left: -50000px !important;
}
`;
const style = document.createElement('style'); const style = document.createElement('style');
style.id = styleId; style.id = styleId;
style.textContent = cssText; style.textContent = cssText + xtermMeasurementFix;
document.head.appendChild(style); document.head.appendChild(style);
} }