Compare commits

..

34 Commits

Author SHA1 Message Date
8d3a1783fd v3.49.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-04-01 05:00:21 +00:00
af1f660486 fix(ts_web): resolve TypeScript nullability and event typing issues across web components 2026-04-01 05:00:21 +00:00
b1c8a7446e Update README and codebase with new components and fixes
- Added `DeesStorageBrowser` to the Data Display section in README.
- Included `DeesUpdater` in the Theming section of README.
- Corrected the path to the LICENSE file in the README.
- Updated company information section for clarity in README.
- Modified `getFormElements` and `getSubmitButton` methods in `DeesForm` to use `querySelectorAll` for better input selection.
- Updated `apexcharts` version from `5.10.3` to `5.10.4` in versions service.
2026-04-01 04:30:10 +00:00
7e991396e9 feat(config): rename npmextra.json to .smartconfig.json for project setup and configuration 2026-04-01 04:29:55 +00:00
25cbf9bfdd v3.49.0
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-18 15:28:21 +00:00
4d8ba1fefc feat(dataview-statusobject): add last updated footer to status object and refresh demo data 2026-03-18 15:28:21 +00:00
42317459ff v3.48.5
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-03-14 17:39:34 +00:00
932db338c6 fix(repo): no changes to commit 2026-03-14 17:39:34 +00:00
bc4b87b83a v3.48.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-03-14 17:10:20 +00:00
eb055e7214 fix(storage-browser): rename S3-specific storage browser interfaces to generic storage types 2026-03-14 17:10:20 +00:00
c55eb948fe v3.48.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-03-14 16:29:46 +00:00
35779209ea fix(dataview): rename dees-s3-browser exports and custom elements to dees-storage-browser 2026-03-14 16:29:46 +00:00
8c6738ea15 v3.48.2
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-03-12 20:11:52 +00:00
e7da1d8b44 fix(repo): no changes to commit 2026-03-12 20:11:52 +00:00
358d82e7fa v3.48.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-03-12 20:04:12 +00:00
6452e05e1d fix(repo): no changes to commit 2026-03-12 20:04:12 +00:00
07b536ea9a v3.48.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-03-12 15:18:52 +00:00
3fcb0cbf89 feat(dataview): add an S3 browser component with column and list views, file preview, editing, and object management 2026-03-12 15:18:52 +00:00
3285cbf0e7 v3.47.2
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-03-11 21:49:34 +00:00
a2d750b2f6 fix(deps): bump @design.estate/dees-domtools and @design.estate/dees-element dependencies 2026-03-11 21:49:34 +00:00
d4276710e6 v3.47.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-11 18:04:55 +00:00
66d64bf476 fix(dees-statsgrid): add tablet breakpoint to render stats grid as three columns 2026-03-11 18:04:55 +00:00
2504251707 v3.47.0
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-11 08:49:14 +00:00
fed130f291 feat(dees-statsgrid): add container-responsive behavior and responsive CSS to dees-statsgrid; bump @design.estate/dees-element dependency to ^2.2.1 2026-03-11 08:49:14 +00:00
4f05b5907b v3.46.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 19:49:46 +00:00
e517320dcd fix(dees-appui): add min-height: 0 to mainmenu and secondarymenu to prevent unintended container height and fix layout stacking 2026-03-10 19:49:46 +00:00
ade5a25b3a v3.46.0
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 16:04:28 +00:00
a396dfea12 feat(dees-tile): unify tile metadata into a consistent bottom info bar and add PDF file-size display 2026-03-10 16:04:28 +00:00
d0105e1b80 v3.45.1
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 14:42:02 +00:00
1eeebb35e6 fix(dees-appui): substitute route params into URL hash when navigating 2026-03-10 14:42:02 +00:00
14e8b8c533 v3.45.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 13:56:22 +00:00
eaf327ea75 feat(dees-form): register new input components (tags, list, wysiwyg, richtext) and emit change notification for richtext updates 2026-03-10 13:56:22 +00:00
09741e0b37 v3.44.0
Some checks failed
Default (tags) / security (push) Failing after 0s
Default (tags) / test (push) Failing after 0s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-03-10 12:39:21 +00:00
5cadd1fc7f feat(appui-tabs): add support for left/right tab action buttons and content tab action APIs 2026-03-10 12:39:21 +00:00
107 changed files with 5765 additions and 1249 deletions

View File

@@ -1,7 +1,7 @@
{
"json.schemas": [
{
"fileMatch": ["/npmextra.json"],
"fileMatch": ["/.smartconfig.json"],
"schema": {
"type": "object",
"properties": {

View File

@@ -1,5 +1,114 @@
# Changelog
## 2026-04-01 - 3.49.1 - fix(ts_web)
resolve TypeScript nullability and event typing issues across web components
- adds explicit non-null assertions and nullable types to component properties, shadowRoot queries, and modal references
- normalizes demo and component event listeners by casting generic Event objects to CustomEvent where needed
- guards optional form, dropdown, menu, and DOM interactions to prevent invalid access under stricter TypeScript checks
- updates tsconfig to include Node types for the web build environment
## 2026-03-18 - 3.49.0 - feat(dataview-statusobject)
add last updated footer to status object and refresh demo data
- Render a bottom bar that shows the status object's lastUpdated timestamp when available.
- Adjust detail row padding to keep spacing consistent with the new footer layout.
- Update demo status objects to include lastUpdated examples for current, hourly, and daily timestamps.
- Bump @tsclass/tsclass from ^9.3.0 to ^9.5.0.
## 2026-03-14 - 3.48.5 - fix(repo)
no changes to commit
## 2026-03-14 - 3.48.4 - fix(storage-browser)
rename S3-specific storage browser interfaces to generic storage types
- Replaces IS3DataProvider, IS3Object, and IS3ChangeEvent with generic storage interface names across storage browser components
- Updates demo provider naming and user-facing demo text from S3 browser to Storage browser
- Aligns interface and utility comments with storage-agnostic terminology
## 2026-03-14 - 3.48.3 - fix(dataview)
rename dees-s3-browser exports and custom elements to dees-storage-browser
- Replaces the dees-s3-browser module path with dees-storage-browser in dataview exports
- Renames the main custom element from dees-s3-browser to dees-storage-browser
- Renames related columns, keys, preview, demo, interfaces, and utility entry points under the new storage-browser module
## 2026-03-12 - 3.48.2 - fix(repo)
no changes to commit
## 2026-03-12 - 3.48.1 - fix(repo)
no changes to commit
## 2026-03-12 - 3.48.0 - feat(dataview)
add an S3 browser component with column and list views, file preview, editing, and object management
- introduces a new dees-s3-browser module with shared interfaces, utilities, demo, and exports
- supports browsing S3-style prefixes in both column and list layouts with breadcrumb navigation
- adds file preview with text editing, download, and delete actions
- includes create, rename, move, delete, upload, and drag-and-drop handling for files and folders
- adds optional live change stream integration with refresh indicators
## 2026-03-11 - 3.47.2 - fix(deps)
bump @design.estate/dees-domtools and @design.estate/dees-element dependencies
- update @design.estate/dees-domtools from ^2.3.9 to ^2.5.1
- update @design.estate/dees-element from ^2.2.1 to ^2.2.2
## 2026-03-11 - 3.47.1 - fix(dees-statsgrid)
add tablet breakpoint to render stats grid as three columns
- Added cssManager.cssForTablet rule to set .stats-grid grid-template-columns: repeat(3, 1fr).
- Improves responsive layout on tablet devices for dees-statsgrid tiles.
- Change made in ts_web/elements/00group-dataview/dees-statsgrid/dees-statsgrid.ts
## 2026-03-11 - 3.47.0 - feat(dees-statsgrid)
add container-responsive behavior and responsive CSS to dees-statsgrid; bump @design.estate/dees-element dependency to ^2.2.1
- Added @containerResponsive decorator and import to dees-statsgrid
- Added cssManager.cssForPhablet and cssManager.cssForPhone responsive style blocks to adjust layout, spacing and font sizes on smaller viewports
- Bumped dependency @design.estate/dees-element from ^2.1.6 to ^2.2.1
## 2026-03-10 - 3.46.1 - fix(dees-appui)
add min-height: 0 to mainmenu and secondarymenu to prevent unintended container height and fix layout stacking
- Modified ts_web/elements/00group-appui/dees-appui/dees-appui.ts: added min-height: 0 to .maingrid > dees-appui-mainmenu and .maingrid > dees-appui-secondarymenu
- Fixes layout issues where children or flexbox-derived min-height could cause menu containers to expand and interfere with z-index stacking
## 2026-03-10 - 3.46.0 - feat(dees-tile)
unify tile metadata into a consistent bottom info bar and add PDF file-size display
- Introduce renderBottomBar() hook in DeesTileBase and remove per-component bottom badges/labels in favor of a unified info bar.
- Implement renderBottomBar in audio, video, image, folder, note and pdf tiles to show label, counts, dimensions, duration, language/line info and page counts.
- PDF tile: add fileSize state, attempt to read download info and display formatted file size in the info bar; show currentPreviewPage/pageCount when hovering.
- Styling changes: replace legacy badges/labels with .tile-info-bar (.info-label, .info-detail, .info-spacer); adjust padding, font sizing, z-index, and remove hover translate for clickable tiles.
- PDF demo and styles: use cssManager theming for demo colors and adjust preview padding.
- Bump devDependencies: @git.zone/tswatch -> ^3.3.0 and @types/node -> ^25.4.0
## 2026-03-10 - 3.45.1 - fix(dees-appui)
substitute route params into URL hash when navigating
- Replaces :param placeholders in view.route with provided params before updating the URL hash.
- Ensures window.history.pushState is called with the resolved route so URLs do not contain literal parameter tokens.
## 2026-03-10 - 3.45.0 - feat(dees-form)
register new input components (tags, list, wysiwyg, richtext) and emit change notification for richtext updates
- Added imports and registration of DeesInputTags, DeesInputList, DeesInputWysiwyg, and DeesInputRichtext in dees-form
- Extended TFormInputElement union type to include the new input components
- DeesInputRichtext now calls changeSubject.next(this.value) in the editor onUpdate handler to propagate changes
## 2026-03-10 - 3.44.0 - feat(appui-tabs)
add support for left/right tab action buttons and content tab action APIs
- Introduce ITabAction interface and add actionsLeft/actionsRight properties to dees-appui-tabs, dees-appui-maincontent, and dees-appui.
- Render action buttons with new styles and renderActions() helper, including disabled state and click handlers; wire actions into tab components.
- Add public clear() on dees-appui-tabs and improve tab selection logic to reset selection when tabs become empty or when the selected tab is removed.
- Expose setContentTabActionsLeft and setContentTabActionsRight on the DeesAppui programmatic API and update interfaces/appconfig accordingly.
- Update demos to showcase action buttons, add clear-all behavior, and adjust layout/styling for action areas.
## 2026-03-09 - 3.43.4 - fix(media)
remove deprecated dees-pdf and dees-pdf-preview components and bump several dependencies

View File

@@ -1,4 +1,4 @@
Copyright (c) 2020 Lossless GmbH (hello@lossless.com)
Copyright (c) 2020 Task Venture Capital GmbH (hello@task.vc)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -8,10 +8,7 @@ copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software. You agree to being mentioned
as reference by Lossless GmbH. This includes the use of your entity logos
or profile picture by Lossless GmbH on websites and readme's, also on third party
pages like gitlab.com or github.com.
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
@@ -19,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

View File

@@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-catalog",
"version": "3.43.4",
"version": "3.49.1",
"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,8 +16,8 @@
"author": "Lossless GmbH",
"license": "MIT",
"dependencies": {
"@design.estate/dees-domtools": "^2.3.9",
"@design.estate/dees-element": "^2.1.6",
"@design.estate/dees-domtools": "^2.5.4",
"@design.estate/dees-element": "^2.2.4",
"@design.estate/dees-wcctools": "^3.8.0",
"@fortawesome/fontawesome-svg-core": "^7.2.0",
"@fortawesome/free-brands-svg-icons": "^7.2.0",
@@ -33,8 +33,8 @@
"@tiptap/extension-typography": "^2.23.0",
"@tiptap/extension-underline": "^2.23.0",
"@tiptap/starter-kit": "^2.23.0",
"@tsclass/tsclass": "^9.3.0",
"apexcharts": "^5.10.3",
"@tsclass/tsclass": "^9.5.0",
"apexcharts": "^5.10.4",
"highlight.js": "11.11.1",
"ibantools": "^4.5.1",
"lucide": "^0.577.0",
@@ -44,12 +44,12 @@
"xterm-addon-fit": "^0.8.0"
},
"devDependencies": {
"@git.zone/tsbuild": "^4.3.0",
"@git.zone/tsbundle": "^2.9.1",
"@git.zone/tstest": "^3.3.2",
"@git.zone/tswatch": "^3.2.5",
"@push.rocks/projectinfo": "^5.0.2",
"@types/node": "^25.3.5"
"@git.zone/tsbuild": "^4.4.0",
"@git.zone/tsbundle": "^2.10.0",
"@git.zone/tstest": "^3.6.3",
"@git.zone/tswatch": "^3.3.2",
"@push.rocks/projectinfo": "^5.1.0",
"@types/node": "^25.5.0"
},
"files": [
"ts/**/*",
@@ -60,7 +60,7 @@
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
".smartconfig.json",
"readme.md"
],
"browserslist": [

1350
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -58,13 +58,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) |
| **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) |
| **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), [`DeesStorageBrowser`](#deesstorgebrowser) |
| **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) |
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
| **Navigation** | [`DeesStepper`](#deesstepper), [`DeesProgressbar`](#deesprogressbar) |
| **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), [`DeesUpdater`](#deesupdater) |
| **Pre-built Templates** | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) |
| **Shopping** | [`DeesShoppingProductcard`](#deesshoppingproductcard) |
@@ -1780,7 +1780,7 @@ interface ITileFolderItem {
## License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./license) file.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
@@ -1792,9 +1792,11 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
### Company Information
Task Venture Capital GmbH
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

View File

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

View File

@@ -122,7 +122,7 @@ export class DeesAppuiBar extends DeesElement {
>
${menuItem.iconName ? html`<dees-icon .icon="${`lucide:${menuItem.iconName}`}"></dees-icon>` : ''}
${menuItem.name}
${hasSubmenu ? this.renderDropdown(menuItem.submenu, itemId, isActive) : ''}
${hasSubmenu ? this.renderDropdown(menuItem.submenu!, itemId, isActive) : ''}
</div>
`;
}

View File

@@ -75,21 +75,21 @@ export const demoFunc = () => {
// Set up status toggle
const statusButtons = elementArg.querySelectorAll('.status-toggle dees-button');
statusButtons[0].addEventListener('click', () => {
appbar.user = { ...appbar.user, status: 'online' };
appbar.user = { ...appbar.user!, status: 'online' };
});
statusButtons[1].addEventListener('click', () => {
appbar.user = { ...appbar.user, status: 'busy' };
appbar.user = { ...appbar.user!, status: 'busy' };
});
statusButtons[2].addEventListener('click', () => {
appbar.user = { ...appbar.user, status: 'away' };
appbar.user = { ...appbar.user!, status: 'away' };
});
statusButtons[3].addEventListener('click', () => {
appbar.user = { ...appbar.user, status: 'offline' };
appbar.user = { ...appbar.user!, status: 'offline' };
});
// Set up window controls toggle
const windowControlsButton = elementArg.querySelector('.window-controls-toggle dees-button');
windowControlsButton.addEventListener('click', () => {
windowControlsButton!.addEventListener('click', () => {
appbar.showWindowControls = !appbar.showWindowControls;
});

View File

@@ -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>
@@ -157,7 +165,7 @@ export class DeesAppuiMaincontent extends DeesElement {
}
// Tab selection is now handled by the dees-appui-tabs component
// But we need to ensure the tabs component is ready
const tabsComponent = this.shadowRoot.querySelector('dees-appui-tabs') as DeesAppuiTabs;
const tabsComponent = this.shadowRoot!.querySelector('dees-appui-tabs') as DeesAppuiTabs;
if (tabsComponent) {
await tabsComponent.updateComplete;
}

View File

@@ -46,7 +46,7 @@ export class DeesAppuiMainmenu extends DeesElement {
accessor tabs: interfaces.IMenuItem[] = [];
@property()
accessor selectedTab: interfaces.IMenuItem;
accessor selectedTab!: interfaces.IMenuItem;
@property({ type: Boolean, reflect: true })
accessor collapsed: boolean = false;

View File

@@ -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>

View File

@@ -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')) {
@@ -543,21 +663,21 @@ export class DeesAppuiTabs extends DeesElement {
}
private shouldShowIndicator(): boolean {
return this.selectedTab && this.showTabIndicator && this.tabs.includes(this.selectedTab);
return !!this.selectedTab && this.showTabIndicator && this.tabs.includes(this.selectedTab);
}
private getSelectedTabElement(): HTMLElement | null {
const selectedIndex = this.tabs.indexOf(this.selectedTab);
const selectedIndex = this.tabs.indexOf(this.selectedTab!);
const isHorizontal = this.tabStyle === 'horizontal';
const selector = isHorizontal
const selector = isHorizontal
? `.tabs-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`
: `.vertical-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`;
return this.shadowRoot.querySelector(selector);
return this.shadowRoot!.querySelector(selector);
}
private getIndicatorElement(): HTMLElement | null {
return this.shadowRoot.querySelector('.tabIndicator');
return this.shadowRoot!.querySelector('.tabIndicator');
}
private handleInitialTransition(indicator: HTMLElement): void {
@@ -575,7 +695,7 @@ export class DeesAppuiTabs extends DeesElement {
const tabContent = tabElement.querySelector('.tab-content') as HTMLElement;
if (!tabContent) return;
const wrapperRect = indicator.parentElement.getBoundingClientRect();
const wrapperRect = indicator.parentElement!.getBoundingClientRect();
const contentRect = tabContent.getBoundingClientRect();
const contentLeft = contentRect.left - wrapperRect.left;
@@ -587,7 +707,7 @@ export class DeesAppuiTabs extends DeesElement {
}
private updateVerticalIndicator(indicator: HTMLElement, tabElement: HTMLElement): void {
const tabsContainer = this.shadowRoot.querySelector('.vertical-wrapper .tabsContainer') as HTMLElement;
const tabsContainer = this.shadowRoot!.querySelector('.vertical-wrapper .tabsContainer') as HTMLElement;
if (!tabsContainer) return;
indicator.style.top = `${tabElement.offsetTop + tabsContainer.offsetTop}px`;

View File

@@ -12,7 +12,7 @@ class DemoDashboardView extends DeesElement {
@state()
accessor activated: boolean = false;
private ctx: IViewActivationContext;
private ctx!: IViewActivationContext;
private statsTiles: IStatsTile[] = [
{
@@ -267,7 +267,7 @@ class DemoSettingsView extends DeesElement {
@state()
accessor hasChanges: boolean = false;
private appui: DeesAppui;
private appui!: DeesAppui;
onActivate(context: IViewActivationContext) {
this.appui = context.appui as any;

View File

@@ -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;
@@ -213,11 +219,13 @@ export class DeesAppui extends DeesElement {
.maingrid > dees-appui-mainmenu {
position: relative;
z-index: 3;
min-height: 0;
}
.maingrid > dees-appui-secondarymenu {
position: relative;
z-index: 2;
min-height: 0;
}
.maingrid > dees-appui-maincontent {
@@ -306,6 +314,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 +709,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 +877,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);
@@ -1008,7 +1037,7 @@ export class DeesAppui extends DeesElement {
if (!config.mainMenu?.sections) return [];
return config.mainMenu.sections.map((section) => ({
name: section.name,
name: section.name || '',
items: section.views
.map((viewId) => {
const view = this.viewRegistry.get(viewId);

View File

@@ -173,7 +173,7 @@ export class ViewRegistry {
}
// Check for cached instance
let element = shouldCache ? this.instances.get(viewId) : undefined;
let element: HTMLElement | null | undefined = shouldCache ? this.instances.get(viewId) : undefined;
if (element) {
// Reuse cached instance - just show it

View File

@@ -1,5 +1,5 @@
import { html, css } from '@design.estate/dees-element';
import '../00group-button/dees-button/dees-button.js';
import '../../00group-button/dees-button/dees-button.js';
import '../../00group-layout/dees-panel/dees-panel.js';
import '@design.estate/dees-wcctools/demotools';

View File

@@ -301,15 +301,15 @@ export class DeesMobilenavigation extends DeesElement {
`;
}
private windowLayer: DeesWindowLayer;
private windowLayer!: DeesWindowLayer;
/**
* inits the show
*/
public async show() {
const domtools = await this.domtoolsPromise;
const main = this.shadowRoot.querySelector('.main');
const main = this.shadowRoot!.querySelector('.main');
// Create window layer first (it will get its own z-index)
if (!this.windowLayer) {
this.windowLayer = await DeesWindowLayer.createAndShow({
@@ -328,7 +328,7 @@ export class DeesMobilenavigation extends DeesElement {
zIndexRegistry.register(this, this.mobileNavZIndex);
await domtools.convenience.smartdelay.delayFor(10);
main.classList.add('show');
main!.classList.add('show');
}
/**
@@ -336,8 +336,8 @@ export class DeesMobilenavigation extends DeesElement {
*/
public async hide() {
const domtools = await this.domtoolsPromise;
const main = this.shadowRoot.querySelector('.main');
main.classList.remove('show');
const main = this.shadowRoot!.querySelector('.main');
main!.classList.remove('show');
// Unregister from z-index registry
zIndexRegistry.unregister(this);

View File

@@ -288,8 +288,8 @@ export const demoFunc = () => html`
}
if (dataBtn && output) {
dataBtn.addEventListener('clicked', (e: CustomEvent) => {
output.textContent = `Clicked: Secondary button with data: ${e.detail.data}`;
dataBtn.addEventListener('clicked', (e: Event) => {
output.textContent = `Clicked: Secondary button with data: ${(e as CustomEvent).detail.data}`;
});
}
@@ -322,9 +322,9 @@ export const demoFunc = () => html`
const output = elementArg.querySelector('#form-output');
if (form && output) {
form.addEventListener('formData', (e: CustomEvent) => {
output.innerHTML = '<strong>Form submitted with data:</strong><br>' +
JSON.stringify(e.detail.data, null, 2);
form.addEventListener('formData', (e: Event) => {
output.innerHTML = '<strong>Form submitted with data:</strong><br>' +
JSON.stringify((e as CustomEvent).detail.data, null, 2);
});
}

View File

@@ -31,10 +31,10 @@ export class DeesButton extends DeesElement {
return true;
}
})
accessor text: string;
accessor text!: string;
@property()
accessor eventDetailData: string;
accessor eventDetailData!: string;
@property({
type: Boolean,
@@ -69,7 +69,7 @@ export class DeesButton extends DeesElement {
accessor insideForm: boolean = false;
@property({ type: String, reflect: true })
accessor icon: string;
accessor icon!: string;
@property({ type: String, reflect: true })
accessor iconPosition: 'left' | 'right' = 'left';

View File

@@ -37,7 +37,7 @@ export class DeesChartArea extends DeesElement {
// instance
@state()
accessor chart: ApexCharts;
accessor chart!: ApexCharts;
@property()
accessor label: string = 'Untitled Chart';
@@ -68,8 +68,8 @@ export class DeesChartArea extends DeesElement {
@property({ type: Number })
accessor autoScrollInterval: number = 1000; // Auto-scroll interval in milliseconds (0 to disable)
private resizeObserver: ResizeObserver;
private resizeTimeout: number;
private resizeObserver!: ResizeObserver;
private resizeTimeout!: number;
private internalChartData: ApexAxisChartSeries = [];
private autoScrollTimer: number | null = null;
private readonly DEBUG_RESIZE = false; // Set to true to enable resize debugging
@@ -132,7 +132,7 @@ export class DeesChartArea extends DeesElement {
if (this.chart) {
try {
this.chart.destroy();
this.chart = null;
this.chart = null as any;
} catch (error) {
console.error('Error destroying chart:', error);
}
@@ -170,14 +170,14 @@ export class DeesChartArea extends DeesElement {
await new Promise(resolve => requestAnimationFrame(resolve));
// Get actual dimensions of the container
const mainbox: HTMLDivElement = this.shadowRoot.querySelector('.mainbox');
const chartContainer: HTMLDivElement = this.shadowRoot.querySelector('.chartContainer');
const mainbox: HTMLDivElement | null = this.shadowRoot!.querySelector('.mainbox');
const chartContainer: HTMLDivElement | null = this.shadowRoot!.querySelector('.chartContainer');
if (!mainbox || !chartContainer) {
console.error('Chart containers not found');
return;
}
// Calculate initial dimensions
const styleChartContainer = window.getComputedStyle(chartContainer);
const paddingTop = parseInt(styleChartContainer.paddingTop, 10);
@@ -368,7 +368,7 @@ export class DeesChartArea extends DeesElement {
};
try {
this.chart = new ApexChartsLib(this.shadowRoot.querySelector('.chartContainer'), options);
this.chart = new ApexChartsLib(this.shadowRoot!.querySelector('.chartContainer')!, options);
await this.chart.render();
// Give the chart a moment to fully initialize before resizing
@@ -376,7 +376,7 @@ export class DeesChartArea extends DeesElement {
await this.resizeChart();
// Ensure resize observer is watching the mainbox
const mainbox = this.shadowRoot.querySelector('.mainbox');
const mainbox = this.shadowRoot!.querySelector('.mainbox');
if (mainbox && this.resizeObserver) {
// Disconnect any previous observations
this.resizeObserver.disconnect();
@@ -572,9 +572,9 @@ export class DeesChartArea extends DeesElement {
}
try {
const mainbox: HTMLDivElement = this.shadowRoot.querySelector('.mainbox');
const chartContainer: HTMLDivElement = this.shadowRoot.querySelector('.chartContainer');
const mainbox: HTMLDivElement | null = this.shadowRoot!.querySelector('.mainbox');
const chartContainer: HTMLDivElement | null = this.shadowRoot!.querySelector('.chartContainer');
if (!mainbox || !chartContainer) {
return;
}

View File

@@ -44,8 +44,8 @@ export const demoFunc = () => {
// Get the chart elements
const chartElement = elementArg.querySelector('#main-chart') as DeesChartArea;
const connectionsChartElement = elementArg.querySelector('#connections-chart') as DeesChartArea;
let intervalId: number;
let connectionsIntervalId: number;
let intervalId: number | null;
let connectionsIntervalId: number | null;
let currentDataset = 'system';
// Y-axis formatters for different datasets
@@ -71,7 +71,7 @@ export const demoFunc = () => {
// Generate initial data points for time window
const generateInitialData = (baseValue: number, variance: number, interval: number = DATA_POINT_INTERVAL) => {
const data = [];
const data: Array<{ x: string; y: number }> = [];
const now = Date.now();
const pointCount = Math.floor(TIME_WINDOW / interval);
@@ -240,10 +240,10 @@ export const demoFunc = () => {
// Switch dataset
const switchDataset = (name: string) => {
currentDataset = name;
const dataset = datasets[name];
const dataset = (datasets as Record<string, any>)[name];
chartElement.label = dataset.label;
chartElement.series = dataset.series;
chartElement.yAxisFormatter = formatters[name];
chartElement.yAxisFormatter = (formatters as Record<string, any>)[name];
// Set appropriate y-axis scaling
if (name === 'system') {

View File

@@ -8,8 +8,8 @@ export const demoFunc = () => {
// Get the log elements
const structuredLog = elementArg.querySelector('#structured-log') as DeesChartLog;
const rawLog = elementArg.querySelector('#raw-log') as DeesChartLog;
let structuredIntervalId: number;
let rawIntervalId: number;
let structuredIntervalId: number | null;
let rawIntervalId: number | null;
const serverSources = ['Server', 'Database', 'API', 'Auth', 'Cache', 'Queue', 'WebSocket', 'Scheduler'];

View File

@@ -203,7 +203,7 @@ export class DeesDataviewCodebox extends DeesElement {
</style>
<div
class="mainbox"
@contextmenu="${(eventArg) => {
@contextmenu="${(eventArg: MouseEvent) => {
DeesContextmenu.openContextMenuWithOptions(eventArg, [
{
name: 'About',
@@ -241,7 +241,7 @@ export class DeesDataviewCodebox extends DeesElement {
private codeToDisplayStore = '';
private highlightJs: HLJSApi | null = null;
public async updated(_changedProperties) {
public async updated(_changedProperties: Map<string, any>) {
super.updated(_changedProperties);
console.log('highlighting now');
console.log(this.childNodes);
@@ -267,11 +267,11 @@ export class DeesDataviewCodebox extends DeesElement {
this.highlightJs = await DeesServiceLibLoader.getInstance().loadHighlightJs();
}
const localCodeNode = this.shadowRoot.querySelector('code');
const localCodeNode = this.shadowRoot!.querySelector('code');
const highlightedHtml = this.highlightJs.highlight(this.codeToDisplayStore, {
language: this.progLang,
ignoreIllegals: true,
});
localCodeNode.innerHTML = highlightedHtml.value;
localCodeNode!.innerHTML = highlightedHtml.value;
}
}

View File

@@ -50,6 +50,7 @@ export const demoFunc = () => html` <style>
.statusObject=${{
id: '1',
name: 'API Gateway Service',
lastUpdated: Date.now(),
combinedStatus: 'ok',
combinedStatusText: 'All systems operational',
details: [
@@ -89,6 +90,7 @@ export const demoFunc = () => html` <style>
.statusObject=${{
id: '2',
name: 'PostgreSQL Cluster',
lastUpdated: Date.now() - 3600000,
combinedStatus: 'partly_ok',
combinedStatusText: 'Minor issues detected',
details: [
@@ -128,6 +130,7 @@ export const demoFunc = () => html` <style>
.statusObject=${{
id: '3',
name: 'CI/CD Pipeline',
lastUpdated: Date.now() - 86400000,
combinedStatus: 'not_ok',
combinedStatusText: 'Build failure',
details: [

View File

@@ -29,7 +29,7 @@ export class DeesDataviewStatusobject extends DeesElement {
public static demo = demoFunc;
public static demoGroups = ['Data View'];
@property({ type: Object }) accessor statusObject: tsclass.code.IStatusObject;
@property({ type: Object }) accessor statusObject!: tsclass.code.IStatusObject;
public static styles = [
themeDefaultStyles,
@@ -128,7 +128,7 @@ export class DeesDataviewStatusobject extends DeesElement {
grid-template-columns: 48px auto;
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(0 0% 14.9%)')};
transition: background-color 0.15s ease;
padding-right: 16px;
padding: 0 16px;
cursor: context-menu;
}
@@ -148,7 +148,7 @@ export class DeesDataviewStatusobject extends DeesElement {
.detail .detailsText .label {
font-size: 12px;
font-weight: 500;
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')}
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
margin-bottom: 2px;
letter-spacing: -0.01em;
}
@@ -159,6 +159,28 @@ export class DeesDataviewStatusobject extends DeesElement {
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
line-height: 1.5;
}
.bottomBar {
position: relative;
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
height: 28px;
font-size: 12px;
line-height: 28px;
display: flex;
justify-content: flex-end;
align-items: stretch;
overflow: hidden;
flex-shrink: 0;
}
.bottomBar .statusLabel {
padding: 0 16px;
display: flex;
align-items: center;
font-weight: 500;
}
`,
];
@@ -209,6 +231,11 @@ export class DeesDataviewStatusobject extends DeesElement {
</div>
`;
})}
<div class="bottomBar">
<div class="statusLabel">${this.statusObject?.lastUpdated
? `Last updated: ${new Date(this.statusObject.lastUpdated).toLocaleString()}`
: ''}</div>
</div>
</div>
`;
}
@@ -232,7 +259,7 @@ export class DeesDataviewStatusobject extends DeesElement {
await navigator.clipboard.writeText(JSON.stringify(this.statusObject, null, 2));
// Show feedback
const button = this.shadowRoot.querySelector('.copyMain') as HTMLElement;
const button = this.shadowRoot!.querySelector('.copyMain') as HTMLElement;
const originalText = button.textContent;
button.textContent = 'Copied!';

View File

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

View File

@@ -0,0 +1,156 @@
import { html } from '@design.estate/dees-element';
import type { IStorageDataProvider, IStorageObject } from './interfaces.js';
import './dees-storage-browser.js';
// Mock in-memory storage data provider for demo purposes
class MockStorageDataProvider implements IStorageDataProvider {
private objects: Map<string, { content: string; contentType: string; size: number; lastModified: string }> = new Map();
constructor() {
const now = new Date().toISOString();
// Seed with sample data
this.objects.set('documents/readme.md', {
content: btoa('# Welcome\n\nThis is a demo Storage browser.\n'),
contentType: 'text/markdown',
size: 42,
lastModified: now,
});
this.objects.set('documents/config.json', {
content: btoa('{\n "name": "demo",\n "version": "1.0.0"\n}'),
contentType: 'application/json',
size: 48,
lastModified: now,
});
this.objects.set('documents/notes/todo.txt', {
content: btoa('Buy milk\nFix bug #42\nDeploy to production'),
contentType: 'text/plain',
size: 45,
lastModified: now,
});
this.objects.set('images/logo.png', {
content: btoa('fake-png-data'),
contentType: 'image/png',
size: 24500,
lastModified: now,
});
this.objects.set('images/banner.jpg', {
content: btoa('fake-jpg-data'),
contentType: 'image/jpeg',
size: 156000,
lastModified: now,
});
this.objects.set('scripts/deploy.sh', {
content: btoa('#!/bin/bash\necho "Deploying..."\n'),
contentType: 'text/plain',
size: 34,
lastModified: now,
});
this.objects.set('index.html', {
content: btoa('<!DOCTYPE html>\n<html>\n<body>\n <h1>Hello World</h1>\n</body>\n</html>'),
contentType: 'text/html',
size: 72,
lastModified: now,
});
this.objects.set('styles.css', {
content: btoa('body { margin: 0; font-family: sans-serif; }'),
contentType: 'text/css',
size: 44,
lastModified: now,
});
}
async listObjects(bucket: string, prefix?: string, delimiter?: string): Promise<{ objects: IStorageObject[]; prefixes: string[] }> {
const pfx = prefix || '';
const objects: IStorageObject[] = [];
const prefixes = new Set<string>();
for (const [key, data] of this.objects) {
if (!key.startsWith(pfx)) continue;
const rest = key.slice(pfx.length);
if (delimiter) {
const slashIndex = rest.indexOf(delimiter);
if (slashIndex >= 0) {
prefixes.add(pfx + rest.slice(0, slashIndex + 1));
} else {
objects.push({ key, size: data.size, lastModified: data.lastModified });
}
} else {
objects.push({ key, size: data.size, lastModified: data.lastModified });
}
}
return { objects, prefixes: Array.from(prefixes).sort() };
}
async getObject(bucket: string, key: string): Promise<{ content: string; contentType: string; size: number; lastModified: string }> {
const obj = this.objects.get(key);
if (!obj) throw new Error('Not found');
return { ...obj };
}
async putObject(bucket: string, key: string, base64Content: string, contentType: string): Promise<boolean> {
this.objects.set(key, {
content: base64Content,
contentType,
size: atob(base64Content).length,
lastModified: new Date().toISOString(),
});
return true;
}
async deleteObject(bucket: string, key: string): Promise<boolean> {
return this.objects.delete(key);
}
async deletePrefix(bucket: string, prefix: string): Promise<boolean> {
for (const key of this.objects.keys()) {
if (key.startsWith(prefix)) {
this.objects.delete(key);
}
}
return true;
}
async getObjectUrl(bucket: string, key: string): Promise<string> {
const obj = this.objects.get(key);
if (!obj) return '';
const blob = new Blob([Uint8Array.from(atob(obj.content), c => c.charCodeAt(0))], { type: obj.contentType });
return URL.createObjectURL(blob);
}
async moveObject(bucket: string, sourceKey: string, destKey: string): Promise<{ success: boolean; error?: string }> {
const obj = this.objects.get(sourceKey);
if (!obj) return { success: false, error: 'Source not found' };
this.objects.set(destKey, { ...obj, lastModified: new Date().toISOString() });
this.objects.delete(sourceKey);
return { success: true };
}
async movePrefix(bucket: string, sourcePrefix: string, destPrefix: string): Promise<{ success: boolean; movedCount?: number; error?: string }> {
let count = 0;
const toMove = Array.from(this.objects.entries()).filter(([k]) => k.startsWith(sourcePrefix));
for (const [key, data] of toMove) {
const newKey = destPrefix + key.slice(sourcePrefix.length);
this.objects.set(newKey, { ...data, lastModified: new Date().toISOString() });
this.objects.delete(key);
count++;
}
return { success: true, movedCount: count };
}
}
export const demoFunc = () => html`
<style>
.demo-container {
height: 600px;
padding: 16px;
}
</style>
<div class="demo-container">
<dees-storage-browser
.dataProvider=${new MockStorageDataProvider()}
.bucketName=${'demo-bucket'}
></dees-storage-browser>
</div>
`;

View File

@@ -0,0 +1,439 @@
import { customElement, html, css, cssManager, property, state, DeesElement } from '@design.estate/dees-element';
import { themeDefaultStyles } from '../../00theme.js';
import { demoFunc } from './dees-storage-browser.demo.js';
import type { IStorageDataProvider, IStorageChangeEvent } from './interfaces.js';
import './dees-storage-columns.js';
import './dees-storage-keys.js';
import './dees-storage-preview.js';
declare global {
interface HTMLElementTagNameMap {
'dees-storage-browser': DeesStorageBrowser;
}
}
type TViewType = 'columns' | 'keys';
@customElement('dees-storage-browser')
export class DeesStorageBrowser extends DeesElement {
public static demo = demoFunc;
public static demoGroups = ['Data View'];
@property({ type: Object })
public accessor dataProvider: IStorageDataProvider | null = null;
@property({ type: String })
public accessor bucketName: string = '';
/**
* Optional change stream subscription.
* Pass a function that takes a callback and returns an unsubscribe function.
*/
@property({ type: Object })
public accessor onChangeEvent: ((callback: (event: IStorageChangeEvent) => void) => (() => void)) | null = null;
@state()
private accessor viewType: TViewType = 'columns';
@state()
private accessor currentPrefix: string = '';
@state()
private accessor selectedKey: string = '';
@state()
private accessor refreshKey: number = 0;
@state()
private accessor previewWidth: number = 700;
@state()
private accessor isResizingPreview: boolean = false;
@state()
private accessor recentChangeCount: number = 0;
@state()
private accessor isStreamConnected: boolean = false;
private changeUnsubscribe: (() => void) | null = null;
public static styles = [
cssManager.defaultStyles,
themeDefaultStyles,
css`
:host {
display: block;
height: 100%;
}
.browser-container {
display: flex;
flex-direction: column;
height: 100%;
}
.toolbar {
display: flex;
align-items: center;
gap: 12px;
padding: 12px;
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.03)', 'rgba(0, 0, 0, 0.2)')};
border-radius: 8px;
margin-bottom: 16px;
}
.breadcrumb {
display: flex;
align-items: center;
gap: 4px;
flex: 1;
font-size: 14px;
color: ${cssManager.bdTheme('#71717a', '#999')};
}
.breadcrumb-item {
cursor: pointer;
padding: 4px 8px;
border-radius: 4px;
transition: background 0.15s;
}
.breadcrumb-item:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.1)')};
color: ${cssManager.bdTheme('#18181b', '#fff')};
}
.breadcrumb-separator {
color: ${cssManager.bdTheme('#d4d4d8', '#555')};
}
.view-toggle {
display: flex;
gap: 4px;
}
.view-btn {
padding: 6px 12px;
background: transparent;
border: 1px solid ${cssManager.bdTheme('#d4d4d8', '#444')};
color: ${cssManager.bdTheme('#71717a', '#888')};
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: all 0.15s;
}
.view-btn:hover {
border-color: ${cssManager.bdTheme('#a1a1aa', '#666')};
color: ${cssManager.bdTheme('#3f3f46', '#aaa')};
}
.view-btn.active {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.1)')};
border-color: ${cssManager.bdTheme('#a1a1aa', '#404040')};
color: ${cssManager.bdTheme('#18181b', '#e0e0e0')};
}
.content {
flex: 1;
display: grid;
grid-template-columns: 1fr;
gap: 0;
overflow: hidden;
}
.content.has-preview {
grid-template-columns: 1fr 4px var(--preview-width, 700px);
}
.resize-divider {
width: 4px;
background: transparent;
cursor: col-resize;
transition: background 0.2s;
}
.resize-divider:hover,
.resize-divider.active {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.2)')};
}
.main-view {
overflow: auto;
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.02)', 'rgba(0, 0, 0, 0.2)')};
border-radius: 8px;
}
.preview-panel {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.02)', 'rgba(0, 0, 0, 0.2)')};
border-radius: 8px;
overflow: hidden;
margin-left: 12px;
}
@media (max-width: 1024px) {
.content,
.content.has-preview {
grid-template-columns: 1fr;
}
.preview-panel,
.resize-divider {
display: none;
}
}
.stream-status {
display: flex;
align-items: center;
gap: 4px;
font-size: 11px;
color: ${cssManager.bdTheme('#71717a', '#888')};
margin-left: auto;
margin-right: 12px;
}
.stream-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: ${cssManager.bdTheme('#a1a1aa', '#888')};
}
.stream-dot.connected {
background: #22c55e;
}
.change-indicator {
display: flex;
align-items: center;
gap: 6px;
padding: 4px 8px;
background: rgba(245, 158, 11, 0.2);
border-radius: 4px;
font-size: 11px;
color: #f59e0b;
margin-right: 12px;
}
.change-indicator.pulse {
animation: pulse-orange 1s ease-in-out;
}
@keyframes pulse-orange {
0% { background: rgba(245, 158, 11, 0.4); }
100% { background: rgba(245, 158, 11, 0.2); }
}
`,
];
async connectedCallback() {
super.connectedCallback();
this.subscribeToChanges();
}
async disconnectedCallback() {
await super.disconnectedCallback();
this.unsubscribeFromChanges();
}
/**
* Public method to trigger a refresh of child components
*/
public refresh() {
this.refreshKey++;
}
private setViewType(type: TViewType) {
this.viewType = type;
}
private navigateToPrefix(prefix: string) {
this.currentPrefix = prefix;
this.selectedKey = '';
}
private handleKeySelected(e: CustomEvent) {
this.selectedKey = e.detail.key;
}
private handleNavigate(e: CustomEvent) {
this.navigateToPrefix(e.detail.prefix);
}
private handleObjectDeleted(e: CustomEvent) {
this.selectedKey = '';
this.refreshKey++;
}
updated(changedProperties: Map<string, unknown>) {
if (changedProperties.has('bucketName')) {
this.selectedKey = '';
this.currentPrefix = '';
this.recentChangeCount = 0;
this.unsubscribeFromChanges();
this.subscribeToChanges();
}
if (changedProperties.has('onChangeEvent')) {
this.unsubscribeFromChanges();
this.subscribeToChanges();
}
}
private subscribeToChanges() {
if (!this.onChangeEvent) {
this.isStreamConnected = false;
return;
}
try {
this.changeUnsubscribe = this.onChangeEvent((event: IStorageChangeEvent) => {
this.handleChange(event);
});
this.isStreamConnected = true;
} catch (error) {
console.warn('[StorageBrowser] Failed to subscribe to changes:', error);
this.isStreamConnected = false;
}
}
private unsubscribeFromChanges() {
if (this.changeUnsubscribe) {
this.changeUnsubscribe();
this.changeUnsubscribe = null;
}
this.isStreamConnected = false;
}
private handleChange(event: IStorageChangeEvent) {
this.recentChangeCount++;
this.refreshKey++;
}
private startPreviewResize = (e: MouseEvent) => {
e.preventDefault();
this.isResizingPreview = true;
document.addEventListener('mousemove', this.handlePreviewResize);
document.addEventListener('mouseup', this.endPreviewResize);
};
private handlePreviewResize = (e: MouseEvent) => {
if (!this.isResizingPreview) return;
const contentEl = this.shadowRoot?.querySelector('.content');
if (!contentEl) return;
const containerRect = contentEl.getBoundingClientRect();
const newWidth = Math.min(Math.max(containerRect.right - e.clientX, 250), 1000);
this.previewWidth = newWidth;
};
private endPreviewResize = () => {
this.isResizingPreview = false;
document.removeEventListener('mousemove', this.handlePreviewResize);
document.removeEventListener('mouseup', this.endPreviewResize);
};
render() {
const breadcrumbParts = this.currentPrefix
? this.currentPrefix.split('/').filter(Boolean)
: [];
return html`
<div class="browser-container">
<div class="toolbar">
<div class="breadcrumb">
<span
class="breadcrumb-item"
@click=${() => this.navigateToPrefix('')}
>
${this.bucketName}
</span>
${breadcrumbParts.map((part, index) => {
const prefix = breadcrumbParts.slice(0, index + 1).join('/') + '/';
return html`
<span class="breadcrumb-separator">/</span>
<span
class="breadcrumb-item"
@click=${() => this.navigateToPrefix(prefix)}
>
${part}
</span>
`;
})}
</div>
${this.onChangeEvent ? html`
<div class="stream-status">
<span class="stream-dot ${this.isStreamConnected ? 'connected' : ''}"></span>
${this.isStreamConnected ? 'Live' : 'Offline'}
</div>
` : ''}
${this.recentChangeCount > 0
? html`
<div class="change-indicator pulse">
${this.recentChangeCount} change${this.recentChangeCount > 1 ? 's' : ''}
</div>
`
: ''}
<div class="view-toggle">
<button
class="view-btn ${this.viewType === 'columns' ? 'active' : ''}"
@click=${() => this.setViewType('columns')}
>
Columns
</button>
<button
class="view-btn ${this.viewType === 'keys' ? 'active' : ''}"
@click=${() => this.setViewType('keys')}
>
List
</button>
</div>
</div>
<div class="content ${this.selectedKey ? 'has-preview' : ''}" style="--preview-width: ${this.previewWidth}px">
<div class="main-view">
${this.viewType === 'columns'
? html`
<dees-storage-columns
.dataProvider=${this.dataProvider}
.bucketName=${this.bucketName}
.currentPrefix=${this.currentPrefix}
.refreshKey=${this.refreshKey}
@key-selected=${this.handleKeySelected}
@navigate=${this.handleNavigate}
></dees-storage-columns>
`
: html`
<dees-storage-keys
.dataProvider=${this.dataProvider}
.bucketName=${this.bucketName}
.currentPrefix=${this.currentPrefix}
.refreshKey=${this.refreshKey}
@key-selected=${this.handleKeySelected}
@navigate=${this.handleNavigate}
></dees-storage-keys>
`}
</div>
${this.selectedKey
? html`
<div
class="resize-divider ${this.isResizingPreview ? 'active' : ''}"
@mousedown=${this.startPreviewResize}
></div>
<div class="preview-panel">
<dees-storage-preview
.dataProvider=${this.dataProvider}
.bucketName=${this.bucketName}
.objectKey=${this.selectedKey}
@object-deleted=${this.handleObjectDeleted}
></dees-storage-preview>
</div>
`
: ''}
</div>
</div>
`;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,540 @@
import { customElement, html, css, cssManager, property, state, DeesElement } from '@design.estate/dees-element';
import { themeDefaultStyles } from '../../00theme.js';
import type { IStorageDataProvider } from './interfaces.js';
import { formatSize, getFileName } from './utilities.js';
declare global {
interface HTMLElementTagNameMap {
'dees-storage-preview': DeesStoragePreview;
}
}
@customElement('dees-storage-preview')
export class DeesStoragePreview extends DeesElement {
@property({ type: Object })
public accessor dataProvider: IStorageDataProvider | null = null;
@property({ type: String })
public accessor bucketName: string = '';
@property({ type: String })
public accessor objectKey: string = '';
@state()
private accessor loading: boolean = false;
@state()
private accessor saving: boolean = false;
@state()
private accessor content: string = '';
@state()
private accessor originalTextContent: string = '';
@state()
private accessor hasChanges: boolean = false;
@state()
private accessor editing: boolean = false;
@state()
private accessor contentType: string = '';
@state()
private accessor fileSize: number = 0;
@state()
private accessor lastModified: string = '';
@state()
private accessor error: string = '';
public static styles = [
cssManager.defaultStyles,
themeDefaultStyles,
css`
:host {
display: block;
height: 100%;
}
.preview-container {
display: flex;
flex-direction: column;
height: 100%;
}
.preview-header {
padding: 12px;
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#333')};
}
.preview-title {
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
word-break: break-all;
}
.preview-meta {
display: flex;
flex-wrap: wrap;
gap: 16px;
font-size: 12px;
color: ${cssManager.bdTheme('#71717a', '#888')};
}
.meta-item {
display: flex;
align-items: center;
gap: 4px;
}
.preview-content {
flex: 1;
overflow: hidden;
}
.preview-content dees-preview {
width: 100%;
height: 100%;
}
.preview-content.code-editor {
padding: 0;
overflow: hidden;
}
.preview-content.code-editor dees-input-code {
height: 100%;
}
.preview-actions {
padding: 12px;
border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#333')};
display: flex;
gap: 8px;
}
.action-btn {
flex: 1;
padding: 8px 16px;
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.1)')};
border: 1px solid ${cssManager.bdTheme('#d4d4d8', '#404040')};
color: ${cssManager.bdTheme('#3f3f46', '#e0e0e0')};
border-radius: 6px;
cursor: pointer;
font-size: 13px;
transition: all 0.15s;
}
.action-btn:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.15)')};
}
.action-btn.danger {
background: rgba(239, 68, 68, 0.2);
border-color: #ef4444;
color: #f87171;
}
.action-btn.danger:hover {
background: rgba(239, 68, 68, 0.3);
}
.action-btn.primary {
background: rgba(59, 130, 246, 0.3);
border-color: #3b82f6;
color: #60a5fa;
}
.action-btn.primary:hover {
background: rgba(59, 130, 246, 0.4);
}
.action-btn.primary:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.action-btn.secondary {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.03)', 'rgba(255, 255, 255, 0.05)')};
border-color: ${cssManager.bdTheme('#d4d4d8', '#555')};
color: ${cssManager.bdTheme('#71717a', '#aaa')};
}
.action-btn.secondary:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.1)')};
color: ${cssManager.bdTheme('#18181b', '#fff')};
}
.unsaved-indicator {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
background: rgba(251, 191, 36, 0.1);
border: 1px solid rgba(251, 191, 36, 0.3);
border-radius: 4px;
font-size: 12px;
color: #fbbf24;
}
.unsaved-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #fbbf24;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: ${cssManager.bdTheme('#a1a1aa', '#666')};
text-align: center;
padding: 24px;
}
.empty-state svg {
width: 48px;
height: 48px;
margin-bottom: 12px;
opacity: 0.5;
}
.loading-state {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: ${cssManager.bdTheme('#71717a', '#888')};
}
.error-state {
padding: 16px;
color: #f87171;
text-align: center;
}
`,
];
updated(changedProperties: Map<string, unknown>) {
if (changedProperties.has('objectKey') || changedProperties.has('bucketName')) {
if (this.objectKey) {
this.loadObject();
} else {
this.content = '';
this.contentType = '';
this.error = '';
this.originalTextContent = '';
this.hasChanges = false;
this.editing = false;
}
}
}
private async loadObject() {
if (!this.objectKey || !this.bucketName || !this.dataProvider) return;
this.loading = true;
this.error = '';
this.hasChanges = false;
this.editing = false;
try {
const result = await this.dataProvider.getObject(this.bucketName, this.objectKey);
if (!result) {
this.error = 'Object not found';
this.loading = false;
return;
}
this.content = result.content || '';
this.contentType = result.contentType || '';
this.fileSize = result.size || 0;
this.lastModified = result.lastModified || '';
if (this.isText()) {
this.originalTextContent = this.getTextContent();
}
} catch (err) {
console.error('Error loading object:', err);
this.error = 'Failed to load object';
}
this.loading = false;
}
private formatDate(dateStr: string): string {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleString();
}
private isImage(): boolean {
return this.contentType.startsWith('image/');
}
private isText(): boolean {
return (
this.contentType.startsWith('text/') ||
this.contentType === 'application/json' ||
this.contentType === 'application/xml' ||
this.contentType === 'application/javascript'
);
}
private getTextContent(): string {
try {
const binaryString = atob(this.content);
const bytes = new Uint8Array(binaryString.length);
for (let i = 0; i < binaryString.length; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
return new TextDecoder('utf-8').decode(bytes);
} catch {
return 'Unable to decode content';
}
}
private async handleDownload() {
try {
const blob = new Blob([Uint8Array.from(atob(this.content), (c) => c.charCodeAt(0))], {
type: this.contentType,
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = getFileName(this.objectKey);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
} catch (err) {
console.error('Error downloading:', err);
}
}
private async handleDelete() {
if (!this.dataProvider) return;
if (!confirm(`Delete "${getFileName(this.objectKey)}"?`)) return;
try {
await this.dataProvider.deleteObject(this.bucketName, this.objectKey);
this.dispatchEvent(
new CustomEvent('object-deleted', {
detail: { key: this.objectKey },
bubbles: true,
composed: true,
})
);
} catch (err) {
console.error('Error deleting object:', err);
}
}
private getLanguage(): string {
const ext = this.objectKey.split('.').pop()?.toLowerCase() || '';
const languageMap: Record<string, string> = {
ts: 'typescript',
tsx: 'typescript',
js: 'javascript',
jsx: 'javascript',
mjs: 'javascript',
cjs: 'javascript',
json: 'json',
html: 'html',
htm: 'html',
css: 'css',
scss: 'scss',
sass: 'scss',
less: 'less',
md: 'markdown',
markdown: 'markdown',
xml: 'xml',
yaml: 'yaml',
yml: 'yaml',
py: 'python',
rb: 'ruby',
go: 'go',
rs: 'rust',
java: 'java',
c: 'c',
cpp: 'cpp',
h: 'c',
hpp: 'cpp',
cs: 'csharp',
php: 'php',
sh: 'shell',
bash: 'shell',
zsh: 'shell',
sql: 'sql',
graphql: 'graphql',
gql: 'graphql',
dockerfile: 'dockerfile',
txt: 'plaintext',
};
return languageMap[ext] || 'plaintext';
}
private handleContentChange(event: CustomEvent) {
const newValue = event.detail as string;
this.hasChanges = newValue !== this.originalTextContent;
}
private handleEdit() {
this.editing = true;
}
private handleDiscard() {
const codeEditor = this.shadowRoot?.querySelector('dees-input-code') as any;
if (codeEditor) {
codeEditor.value = this.originalTextContent;
}
this.hasChanges = false;
this.editing = false;
}
private async handleSave() {
if (!this.hasChanges || this.saving || !this.dataProvider) return;
this.saving = true;
try {
const codeEditor = this.shadowRoot?.querySelector('dees-input-code') as any;
const currentContent = codeEditor?.value ?? '';
const encoder = new TextEncoder();
const bytes = encoder.encode(currentContent);
const base64Content = btoa(String.fromCharCode(...bytes));
const success = await this.dataProvider.putObject(
this.bucketName,
this.objectKey,
base64Content,
this.contentType
);
if (success) {
this.originalTextContent = currentContent;
this.hasChanges = false;
this.editing = false;
this.content = base64Content;
}
} catch (err) {
console.error('Error saving object:', err);
}
this.saving = false;
}
render() {
if (!this.objectKey) {
return html`
<div class="preview-container">
<div class="empty-state">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
<polyline points="14 2 14 8 20 8" />
</svg>
<p>Select a file to preview</p>
</div>
</div>
`;
}
if (this.loading) {
return html`
<div class="preview-container">
<div class="loading-state">Loading...</div>
</div>
`;
}
if (this.error) {
return html`
<div class="preview-container">
<div class="error-state">${this.error}</div>
</div>
`;
}
return html`
<div class="preview-container">
<div class="preview-header">
<div class="preview-title">${getFileName(this.objectKey)}</div>
<div class="preview-meta">
<span class="meta-item">${this.contentType}</span>
<span class="meta-item">${formatSize(this.fileSize)}</span>
<span class="meta-item">${this.formatDate(this.lastModified)}</span>
${this.hasChanges ? html`
<span class="unsaved-indicator">
<span class="unsaved-dot"></span>
Unsaved changes
</span>
` : ''}
</div>
</div>
<div class="preview-content ${this.editing ? 'code-editor' : ''}">
${this.editing
? html`
<dees-input-code
.value=${this.originalTextContent}
.language=${this.getLanguage()}
height="100%"
@content-change=${(e: CustomEvent) => this.handleContentChange(e)}
></dees-input-code>
`
: this.isText()
? html`
<dees-preview
.textContent=${this.originalTextContent}
.filename=${getFileName(this.objectKey)}
.language=${this.getLanguage()}
.showToolbar=${true}
.showFilename=${false}
></dees-preview>
`
: html`
<dees-preview
.base64=${this.content}
.mimeType=${this.contentType}
.filename=${getFileName(this.objectKey)}
.showToolbar=${true}
.showFilename=${false}
></dees-preview>
`
}
</div>
<div class="preview-actions">
${this.editing
? html`
<button class="action-btn secondary" @click=${this.handleDiscard}>
${this.hasChanges ? 'Discard' : 'Cancel'}
</button>
<button
class="action-btn primary"
@click=${this.handleSave}
?disabled=${this.saving || !this.hasChanges}
>
${this.saving ? 'Saving...' : 'Save'}
</button>
`
: html`
${this.isText()
? html`<button class="action-btn" @click=${this.handleEdit}>Edit</button>`
: ''}
<button class="action-btn" @click=${this.handleDownload}>Download</button>
<button class="action-btn danger" @click=${this.handleDelete}>Delete</button>
`
}
</div>
</div>
`;
}
}

View File

@@ -0,0 +1,6 @@
export * from './dees-storage-browser.js';
export * from './dees-storage-columns.js';
export * from './dees-storage-keys.js';
export * from './dees-storage-preview.js';
export * from './interfaces.js';
export { formatSize, formatCount, getFileName, validateMove, getParentPrefix, getContentType, getDefaultContent, getPathSegments } from './utilities.js';

View File

@@ -0,0 +1,37 @@
/**
* Storage Data Provider interface - implement this to connect the storage browser to your backend
*/
export interface IStorageObject {
key: string;
size?: number;
lastModified?: string;
isPrefix?: boolean;
}
export interface IStorageChangeEvent {
type: 'add' | 'modify' | 'delete';
key: string;
bucket: string;
size?: number;
lastModified?: Date;
}
export interface IStorageDataProvider {
listObjects(bucket: string, prefix?: string, delimiter?: string): Promise<{ objects: IStorageObject[]; prefixes: string[] }>;
getObject(bucket: string, key: string): Promise<{ content: string; contentType: string; size: number; lastModified: string }>;
putObject(bucket: string, key: string, base64Content: string, contentType: string): Promise<boolean>;
deleteObject(bucket: string, key: string): Promise<boolean>;
deletePrefix(bucket: string, prefix: string): Promise<boolean>;
getObjectUrl(bucket: string, key: string): Promise<string>;
moveObject(bucket: string, sourceKey: string, destKey: string): Promise<{ success: boolean; error?: string }>;
movePrefix(bucket: string, sourcePrefix: string, destPrefix: string): Promise<{ success: boolean; movedCount?: number; error?: string }>;
}
export interface IColumn {
prefix: string;
objects: IStorageObject[];
prefixes: string[];
selectedItem: string | null;
width: number;
}

View File

@@ -0,0 +1,120 @@
/**
* Shared utilities for Storage browser components
*/
export interface IMoveValidation {
valid: boolean;
error?: string;
}
/**
* Format a byte size into a human-readable string
*/
export function formatSize(bytes?: number): string {
if (bytes === undefined || bytes === null) return '-';
if (bytes === 0) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(unitIndex > 0 ? 1 : 0)} ${units[unitIndex]}`;
}
/**
* Format a count into a compact human-readable string
*/
export function formatCount(count?: number): string {
if (count === undefined || count === null) return '';
if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;
if (count >= 1000) return `${(count / 1000).toFixed(1)}K`;
return count.toString();
}
/**
* Extract the file name from a path
*/
export function getFileName(path: string): string {
const parts = path.replace(/\/$/, '').split('/');
return parts[parts.length - 1] || path;
}
/**
* Validates if a move operation is allowed
*/
export function validateMove(sourceKey: string, destPrefix: string): IMoveValidation {
if (sourceKey.endsWith('/')) {
if (destPrefix.startsWith(sourceKey)) {
return { valid: false, error: 'Cannot move a folder into itself' };
}
}
const sourceParent = getParentPrefix(sourceKey);
if (sourceParent === destPrefix) {
return { valid: false, error: 'Item is already in this location' };
}
return { valid: true };
}
/**
* Gets the parent prefix (directory) of a given key
*/
export function getParentPrefix(key: string): string {
const trimmed = key.endsWith('/') ? key.slice(0, -1) : key;
const lastSlash = trimmed.lastIndexOf('/');
return lastSlash >= 0 ? trimmed.substring(0, lastSlash + 1) : '';
}
/**
* Get content type from file extension
*/
export function getContentType(ext: string): string {
const contentTypes: Record<string, string> = {
json: 'application/json',
txt: 'text/plain',
html: 'text/html',
css: 'text/css',
js: 'application/javascript',
ts: 'text/typescript',
md: 'text/markdown',
xml: 'application/xml',
yaml: 'text/yaml',
yml: 'text/yaml',
csv: 'text/csv',
};
return contentTypes[ext] || 'application/octet-stream';
}
/**
* Get default content for a new file based on extension
*/
export function getDefaultContent(ext: string): string {
const defaults: Record<string, string> = {
json: '{\n \n}',
html: '<!DOCTYPE html>\n<html>\n<head>\n <title></title>\n</head>\n<body>\n \n</body>\n</html>',
md: '# Title\n\n',
txt: '',
};
return defaults[ext] || '';
}
/**
* Parse a prefix into cumulative path segments
*/
export function getPathSegments(prefix: string): string[] {
if (!prefix) return [];
const parts = prefix.split('/').filter(p => p);
const segments: string[] = [];
let cumulative = '';
for (const part of parts) {
cumulative += part + '/';
segments.push(cumulative);
}
return segments;
}

View File

@@ -52,12 +52,12 @@ export class DeesTable<T> extends DeesElement {
@property({
type: String,
})
accessor key: string;
accessor key!: string;
@property({
type: String,
})
accessor label: string;
accessor label!: string;
@property({
type: Boolean,
@@ -83,7 +83,7 @@ export class DeesTable<T> extends DeesElement {
type: String,
reflect: true,
})
accessor dataName: string;
accessor dataName!: string;
@property({
@@ -127,7 +127,7 @@ export class DeesTable<T> extends DeesElement {
@property({
type: Object,
})
accessor selectedDataRow: T;
accessor selectedDataRow!: T;
@property({
type: Array,
@@ -359,7 +359,7 @@ export class DeesTable<T> extends DeesElement {
if (elementArg.tagName === 'TR') {
return elementArg;
} else {
return getTr(elementArg.parentElement);
return getTr(elementArg.parentElement!);
}
};
return html`
@@ -393,8 +393,8 @@ export class DeesTable<T> extends DeesElement {
}}
@drop=${async (eventArg: DragEvent) => {
eventArg.preventDefault();
const newFiles = [];
for (const file of Array.from(eventArg.dataTransfer.files)) {
const newFiles: File[] = [];
for (const file of Array.from(eventArg.dataTransfer!.files)) {
this.files.push(file);
newFiles.push(file);
this.requestUpdate();
@@ -548,8 +548,8 @@ export class DeesTable<T> extends DeesElement {
type: ['header'],
actionFunc: async () => {
console.log('open search');
const searchGrid = this.shadowRoot.querySelector('.searchGrid');
searchGrid.classList.toggle('hidden');
const searchGrid = this.shadowRoot!.querySelector('.searchGrid');
searchGrid!.classList.toggle('hidden');
}
});
console.log(this.dataActions);
@@ -609,7 +609,7 @@ export class DeesTable<T> extends DeesElement {
const domtools = await this.domtoolsPromise;
await domtools.convenience.smartdelay.delayFor(0);
// Get the table element
const table = this.shadowRoot.querySelector('table');
const table = this.shadowRoot!.querySelector('table');
if (!table) return;
// Get the first row's cells to measure the widths
@@ -718,7 +718,7 @@ export class DeesTable<T> extends DeesElement {
if (!this._rowIdMap.has(key)) {
this._rowIdMap.set(key, String(++this._rowIdCounter));
}
return this._rowIdMap.get(key);
return this._rowIdMap.get(key)!;
}
private isRowSelected(row: T): boolean {
@@ -818,7 +818,7 @@ export class DeesTable<T> extends DeesElement {
input.blur();
}
if (saveArg) {
itemArg[key] = input.value as any; // Convert string to T (you might need better type casting depending on your data structure)
(itemArg as any)[key] = input.value as any; // Convert string to T (you might need better type casting depending on your data structure)
this.changeSubject.next(this);
}
input.remove();

View File

@@ -3,3 +3,4 @@ export * from './dees-dataview-codebox/index.js';
export * from './dees-dataview-statusobject/index.js';
export * from './dees-table/index.js';
export * from './dees-statsgrid/index.js';
export * from './dees-storage-browser/index.js';

View File

@@ -89,7 +89,7 @@ export class DeesProgressbar extends DeesElement {
}
public async updatePercentage() {
const progressBarFill = this.shadowRoot.querySelector('.progressBarFill') as HTMLElement;
const progressBarFill = this.shadowRoot!.querySelector('.progressBarFill') as HTMLElement;
progressBarFill.style.width = `${this.percentage}%`;
}

View File

@@ -30,7 +30,7 @@ export class DeesFormSubmit extends DeesElement {
accessor disabled = false;
@property({ type: String })
accessor text: string;
accessor text!: string;
@property({ type: String })
accessor status: 'normal' | 'pending' | 'success' | 'error' = 'normal';
@@ -42,7 +42,7 @@ export class DeesFormSubmit extends DeesElement {
accessor size: 'sm' | 'default' | 'lg' | 'icon' | 'small' | 'normal' | 'large' = 'default';
@property({ type: String })
accessor icon: string;
accessor icon!: string;
@property({ type: String })
accessor iconPosition: 'left' | 'right' = 'left';

View File

@@ -57,10 +57,10 @@ export const demoFunc = () => html`
const outputDiv = elementArg.querySelector('.form-output');
if (form && outputDiv) {
form.addEventListener('formData', async (eventArg: CustomEvent) => {
form.addEventListener('formData', (async (eventArg: CustomEvent) => {
const data = eventArg.detail.data;
console.log('Form submitted with data:', data);
// Show processing state
form.setStatus('pending', 'Processing your registration...');
outputDiv.innerHTML = `<strong>Submitted Data:</strong>\n${JSON.stringify(data, null, 2)}`;
@@ -75,7 +75,7 @@ export const demoFunc = () => html`
await domtools.plugins.smartdelay.delayFor(2000);
form.reset();
outputDiv.innerHTML = '<em>Form has been reset</em>';
});
}) as unknown as EventListener);
// Track individual field changes
const inputs = form.querySelectorAll('dees-input-text, dees-input-dropdown, dees-input-checkbox');
@@ -158,14 +158,14 @@ export const demoFunc = () => html`
console.log('Horizontal form layout active');
// Monitor filter changes
form.addEventListener('formData', (event: CustomEvent) => {
form.addEventListener('formData', ((event: CustomEvent) => {
const filters = event.detail.data;
console.log('Filter applied:', filters);
// Simulate search
const resultsCount = Math.floor(Math.random() * 100) + 1;
console.log(`Found ${resultsCount} results with filters:`, filters);
});
}) as unknown as EventListener);
// Setup real-time filter updates
const inputs = form.querySelectorAll('[key]');
@@ -224,7 +224,7 @@ export const demoFunc = () => html`
const statusDiv = elementArg.querySelector('#status-display');
if (form) {
form.addEventListener('formData', async (eventArg: CustomEvent) => {
form.addEventListener('formData', (async (eventArg: CustomEvent) => {
const data = eventArg.detail.data;
console.log('Advanced form data:', data);
@@ -252,7 +252,7 @@ export const demoFunc = () => html`
}
console.log('Form data logged:', data);
});
}) as unknown as EventListener);
// Monitor file uploads
const fileUpload = form.querySelector('dees-input-fileupload');

View File

@@ -22,6 +22,10 @@ import { DeesInputMultitoggle } from '../../00group-input/dees-input-multitoggle
import { DeesInputPhone } from '../../00group-input/dees-input-phone/dees-input-phone.js';
import { 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 {
@@ -123,13 +135,14 @@ export class DeesForm extends DeesElement {
}
public getFormElements(): Array<TFormInputElement> {
return Array.from(this.children).filter((child) =>
// Use querySelectorAll('*') to find form inputs nested inside wrapper elements (e.g. <div>)
return Array.from(this.querySelectorAll('*')).filter((child) =>
FORM_INPUT_TYPES.includes(child.constructor as any)
) as unknown as TFormInputElement[];
}
public getSubmitButton(): DeesFormSubmit | undefined {
return Array.from(this.children).find(
return Array.from(this.querySelectorAll('*')).find(
(child) => child instanceof DeesFormSubmit
) as DeesFormSubmit;
}
@@ -143,8 +156,9 @@ export class DeesForm extends DeesElement {
requiredOK = false;
}
}
if (this.getSubmitButton()) {
this.getSubmitButton().disabled = !requiredOK;
const submitButton = this.getSubmitButton();
if (submitButton) {
submitButton.disabled = !requiredOK;
}
}
@@ -187,6 +201,7 @@ export class DeesForm extends DeesElement {
) {
const inputChildren = this.getFormElements();
const submitButton = this.getSubmitButton();
if (!submitButton) return;
switch (visualStateArg) {
case 'normal':
@@ -227,7 +242,6 @@ export class DeesForm extends DeesElement {
*/
reset() {
const inputChildren = this.getFormElements();
const submitButton = this.getSubmitButton();
for (const inputChild of inputChildren) {
inputChild.value = null;

View File

@@ -31,10 +31,10 @@ export abstract class DeesInputBase<T = any> extends DeesElement {
* Common properties for all inputs
*/
@property({ type: String })
accessor key: string;
accessor key!: string;
@property({ type: String })
accessor label: string;
accessor label!: string;
@property({ type: Boolean })
accessor required: boolean = false;
@@ -43,7 +43,7 @@ export abstract class DeesInputBase<T = any> extends DeesElement {
accessor disabled: boolean = false;
@property({ type: String })
accessor description: string;
accessor description!: string;
/**
* Common styles for all input components

View File

@@ -215,7 +215,7 @@ export class DeesInputCheckbox extends DeesInputBase<DeesInputCheckbox> {
}
public focus(): void {
const checkboxDiv = this.shadowRoot.querySelector('.checkbox');
const checkboxDiv = this.shadowRoot!.querySelector('.checkbox');
if (checkboxDiv) {
(checkboxDiv as any).focus();
}

View File

@@ -610,20 +610,20 @@ export class DeesInputCode extends DeesInputBase<string> {
{
name: 'Cancel',
action: async (modalRef) => {
await modalRef.destroy();
await modalRef!.destroy();
},
},
{
name: 'Save & Close',
action: async (modalRef) => {
// Get the editor content from the modal
modalEditorElement = modalRef.shadowRoot?.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
modalEditorElement = modalRef!.shadowRoot?.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
if (modalEditorElement) {
const editor = await modalEditorElement.editorDeferred.promise;
const newValue = editor.getValue();
this.setValue(newValue);
}
await modalRef.destroy();
await modalRef!.destroy();
},
},
],

View File

@@ -47,9 +47,9 @@ export const demoFunc = () => html`
const datePicker = elementArg.querySelector('dees-input-datepicker');
if (datePicker) {
datePicker.addEventListener('change', (event: CustomEvent) => {
datePicker.addEventListener('change', ((event: CustomEvent) => {
console.log('Basic date selected:', (event.target as DeesInputDatepicker).value);
});
}) as EventListener);
}
}}>
<dees-panel .title=${'Basic Date Picker'} .subtitle=${'Simple date selection without time'}>
@@ -66,17 +66,17 @@ export const demoFunc = () => html`
const appointmentPicker = elementArg.querySelector('dees-input-datepicker[label="Appointment"]');
if (dateTimePicker) {
dateTimePicker.addEventListener('change', (event: CustomEvent) => {
dateTimePicker.addEventListener('change', ((event: CustomEvent) => {
const value = (event.target as DeesInputDatepicker).value;
console.log('24h format datetime:', value);
});
}) as EventListener);
}
if (appointmentPicker) {
appointmentPicker.addEventListener('change', (event: CustomEvent) => {
appointmentPicker.addEventListener('change', ((event: CustomEvent) => {
const value = (event.target as DeesInputDatepicker).value;
console.log('12h format datetime:', value);
});
}) as EventListener);
}
}}>
<dees-panel .title=${'Date and Time Selection'} .subtitle=${'Date pickers with time selection in different formats'}>
@@ -102,14 +102,14 @@ export const demoFunc = () => html`
const timezonePickers = elementArg.querySelectorAll('dees-input-datepicker');
timezonePickers.forEach((picker) => {
picker.addEventListener('change', (event: CustomEvent) => {
picker.addEventListener('change', ((event: CustomEvent) => {
const target = event.target as DeesInputDatepicker;
console.log(`${target.label} value:`, target.value);
const input = target.shadowRoot?.querySelector('.date-input') as HTMLInputElement;
if (input) {
console.log(`${target.label} formatted:`, input.value);
}
});
}) as EventListener);
});
}}>
<dees-panel .title=${'Timezone Support'} .subtitle=${'Date and time selection with timezone awareness'}>
@@ -140,7 +140,7 @@ export const demoFunc = () => html`
if (futureDatePicker) {
// Show the min/max constraints in action
futureDatePicker.addEventListener('change', (event: CustomEvent) => {
futureDatePicker.addEventListener('change', ((event: CustomEvent) => {
const value = (event.target as DeesInputDatepicker).value;
if (value) {
const selectedDate = new Date(value);
@@ -148,7 +148,7 @@ export const demoFunc = () => html`
const daysDiff = Math.floor((selectedDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24));
console.log(`Selected date is ${daysDiff} days from today`);
}
});
}) as EventListener);
}
}}>
<dees-panel .title=${'Date Range Constraints'} .subtitle=${'Limit selectable dates with min and max values'}>
@@ -171,14 +171,14 @@ export const demoFunc = () => html`
const datePickers = elementArg.querySelectorAll('dees-input-datepicker');
datePickers.forEach((picker) => {
picker.addEventListener('change', (event: CustomEvent) => {
picker.addEventListener('change', ((event: CustomEvent) => {
const target = event.target as DeesInputDatepicker;
// Log the formatted value that's displayed in the input
const input = target.shadowRoot?.querySelector('.date-input') as HTMLInputElement;
if (input) {
console.log(`${target.label} format:`, input.value);
}
});
}) as EventListener);
});
}}>
<dees-panel .title=${'Date Formats'} .subtitle=${'Different date display formats for various regions'}>
@@ -268,7 +268,7 @@ export const demoFunc = () => html`
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
// Generate weekend dates for the current month
const generateWeekends = () => {
const weekends = [];
const weekends: string[] = [];
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth();
@@ -286,7 +286,7 @@ export const demoFunc = () => html`
const picker = elementArg.querySelector('dees-input-datepicker');
if (picker) {
picker.disabledDates = generateWeekends();
(picker as DeesInputDatepicker).disabledDates = generateWeekends();
console.log('Disabled weekend dates for current month');
}
}}>
@@ -344,7 +344,7 @@ export const demoFunc = () => html`
const picker = elementArg.querySelector('dees-input-datepicker');
if (picker) {
picker.events = sampleEvents;
(picker as DeesInputDatepicker).events = sampleEvents;
console.log('Calendar events loaded:', sampleEvents);
}
}}>
@@ -371,7 +371,7 @@ export const demoFunc = () => html`
const output = elementArg.querySelector('#event-output');
if (picker && output) {
picker.addEventListener('change', (event: CustomEvent) => {
picker.addEventListener('change', ((event: CustomEvent) => {
const target = event.target as DeesInputDatepicker;
const value = target.value;
if (value) {
@@ -379,16 +379,16 @@ export const demoFunc = () => html`
// Get the formatted value from the input element
const input = target.shadowRoot?.querySelector('.date-input') as HTMLInputElement;
const formattedValue = input?.value || 'N/A';
output.innerHTML = `
(output as HTMLElement).innerHTML = `
<strong>Event triggered!</strong><br>
ISO Value: ${value}<br>
Formatted: ${formattedValue}<br>
Date object: ${date.toLocaleString()}
`;
} else {
output.innerHTML = '<em>Date cleared</em>';
(output as HTMLElement).innerHTML = '<em>Date cleared</em>';
}
});
}) as EventListener);
picker.addEventListener('blur', () => {
console.log('Datepicker lost focus');

View File

@@ -56,16 +56,16 @@ export const demoFunc = () => html`
// Log when country changes
if (countryDropdown) {
countryDropdown.addEventListener('selectedOption', (event: CustomEvent) => {
countryDropdown.addEventListener('selectedOption', ((event: CustomEvent) => {
console.log('Country selected:', event.detail);
});
}) as EventListener);
}
// Log when role changes
if (roleDropdown) {
roleDropdown.addEventListener('selectedOption', (event: CustomEvent) => {
roleDropdown.addEventListener('selectedOption', ((event: CustomEvent) => {
console.log('Role selected:', event.detail);
});
}) as EventListener);
}
}}>
<dees-panel .title=${'1. Basic Dropdowns'} .subtitle=${'Standard dropdown with search functionality and various options'}>
@@ -103,9 +103,9 @@ export const demoFunc = () => html`
const priorityDropdown = elementArg.querySelector('dees-input-dropdown');
if (priorityDropdown) {
priorityDropdown.addEventListener('selectedOption', (event: CustomEvent) => {
priorityDropdown.addEventListener('selectedOption', ((event: CustomEvent) => {
console.log(`Priority changed to: ${event.detail.option}`);
});
}) as EventListener);
}
}}>
<dees-panel .title=${'2. Without Search'} .subtitle=${'Dropdown with search functionality disabled for simpler selection'}>
@@ -128,10 +128,10 @@ export const demoFunc = () => html`
// Log all changes from horizontal dropdowns
dropdowns.forEach((dropdown) => {
dropdown.addEventListener('selectedOption', (event: CustomEvent) => {
dropdown.addEventListener('selectedOption', ((event: CustomEvent) => {
const label = dropdown.getAttribute('label');
console.log(`${label}: ${event.detail.option}`);
});
}) as EventListener);
});
}}>
<dees-panel .title=${'3. Horizontal Layout'} .subtitle=${'Multiple dropdowns in a horizontal layout for compact forms'}>
@@ -216,9 +216,9 @@ export const demoFunc = () => html`
const dropdown = elementArg.querySelector('dees-input-dropdown');
if (dropdown) {
dropdown.addEventListener('selectedOption', (event: CustomEvent) => {
dropdown.addEventListener('selectedOption', ((event: CustomEvent) => {
console.log('Bottom dropdown selected:', event.detail);
});
}) as EventListener);
// Note: The dropdown automatically detects available space
// and opens upward when near the bottom of the viewport
@@ -248,16 +248,16 @@ export const demoFunc = () => html`
output.innerHTML = '<em>Select a product to see details...</em>';
// Handle dropdown changes
dropdown.addEventListener('change', (event: CustomEvent) => {
dropdown.addEventListener('change', ((event: CustomEvent) => {
if (event.detail.value) {
output.innerHTML = `
(output as HTMLElement).innerHTML = `
<strong>Selected:</strong> ${event.detail.value.option}<br>
<strong>Key:</strong> ${event.detail.value.key}<br>
<strong>Price:</strong> $${event.detail.value.payload?.price || 'N/A'}<br>
<strong>Features:</strong> ${event.detail.value.payload?.features?.join(', ') || 'N/A'}
`;
}
});
}) as EventListener);
}
}}>
<dees-panel .title=${'6. Event Handling & Payload'} .subtitle=${'Dropdown with payload data and change event handling'}>
@@ -281,20 +281,20 @@ export const demoFunc = () => html`
const frameworkDropdown = elementArg.querySelector('dees-input-dropdown[key="framework"]');
if (form) {
form.addEventListener('formData', (event: CustomEvent) => {
form.addEventListener('formData', ((event: CustomEvent) => {
console.log('Form submitted with data:', event.detail.data);
});
}) as EventListener);
}
if (projectTypeDropdown && frameworkDropdown) {
// Filter frameworks based on project type
projectTypeDropdown.addEventListener('selectedOption', (event: CustomEvent) => {
projectTypeDropdown.addEventListener('selectedOption', ((event: CustomEvent) => {
const selectedType = event.detail.key;
console.log(`Project type changed to: ${selectedType}`);
// In a real app, you could filter the framework options based on project type
// For demo purposes, we just log the change
});
}) as EventListener);
}
}}>
<dees-panel .title=${'7. Form Integration'} .subtitle=${'Dropdown working within a form with validation'}>

View File

@@ -30,14 +30,14 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
accessor options: { option: string; key: string; payload?: any }[] = [];
@property()
accessor selectedOption: { option: string; key: string; payload?: any } = null;
accessor selectedOption: { option: string; key: string; payload?: any } | null = null;
// Add value property for form compatibility
public get value() {
return this.selectedOption;
}
public set value(val: { option: string; key: string; payload?: any }) {
public set value(val: { option: string; key: string; payload?: any } | null) {
this.selectedOption = val;
}
@@ -372,7 +372,7 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
if (this.isOpened) {
// Check available space and set position
const selectedBox = this.shadowRoot.querySelector('.selectedBox') as HTMLElement;
const selectedBox = this.shadowRoot!.querySelector('.selectedBox') as HTMLElement;
const rect = selectedBox.getBoundingClientRect();
const spaceBelow = window.innerHeight - rect.bottom;
const spaceAbove = rect.top;
@@ -382,7 +382,7 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
// Focus search input if present
await this.updateComplete;
const searchInput = this.shadowRoot.querySelector('.search input') as HTMLInputElement;
const searchInput = this.shadowRoot!.querySelector('.search input') as HTMLInputElement;
if (searchInput) {
searchInput.focus();
}
@@ -455,7 +455,7 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
}
}
public getValue(): { option: string; key: string; payload?: any } {
public getValue(): { option: string; key: string; payload?: any } | null {
return this.selectedOption;
}

View File

@@ -48,7 +48,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
accessor maxFiles: number = 0; // 0 means no limit
@property({ type: String, reflect: true })
accessor validationState: 'valid' | 'invalid' | 'warn' | 'pending' = null;
accessor validationState: 'valid' | 'invalid' | 'warn' | 'pending' | null = null;
accessor validationMessage: string = '';
@@ -266,7 +266,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
return;
}
['dragenter', 'dragover', 'dragleave', 'drop'].forEach((eventName) => {
this.dropArea!.addEventListener(eventName, this.handleDragEvent);
this.dropArea!.addEventListener(eventName, this.handleDragEvent as unknown as EventListener);
});
}
@@ -275,7 +275,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
return;
}
['dragenter', 'dragover', 'dragleave', 'drop'].forEach((eventName) => {
this.dropArea!.removeEventListener(eventName, this.handleDragEvent);
this.dropArea!.removeEventListener(eventName, this.handleDragEvent as unknown as EventListener);
});
}

View File

@@ -60,7 +60,7 @@ export class DeesInputIban extends DeesInputBase<DeesInputIban> {
public firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
super.firstUpdated(_changedProperties);
const deesInputText = this.shadowRoot.querySelector('dees-input-text') as any;
const deesInputText = this.shadowRoot!.querySelector('dees-input-text') as any;
if (deesInputText && deesInputText.changeSubject) {
deesInputText.changeSubject.subscribe(() => {
this.changeSubject.next(this);
@@ -81,8 +81,10 @@ export class DeesInputIban extends DeesInputBase<DeesInputIban> {
}
}
this.enteredIbanIsValid = ibantools.isValidIBAN(this.enteredString.replace(/ /g, ''));
const deesInputText = this.shadowRoot.querySelector('dees-input-text');
deesInputText.validationText = `IBAN is valid: ${this.enteredIbanIsValid}`;
const deesInputText = this.shadowRoot!.querySelector('dees-input-text') as any;
if (deesInputText) {
deesInputText.validationText = `IBAN is valid: ${this.enteredIbanIsValid}`;
}
}
public getValue(): string {

View File

@@ -211,9 +211,9 @@ export class DeesInputMultitoggle extends DeesInputBase<DeesInputMultitoggle> {
private indicatorInitialized = false;
public async setIndicator() {
const indicator: HTMLDivElement = this.shadowRoot.querySelector('.indicator');
const indicator: HTMLDivElement | null = this.shadowRoot!.querySelector('.indicator');
const selectedIndex = this.options.indexOf(this.selectedOption);
// If no valid selection, hide indicator
if (selectedIndex === -1 || !indicator) {
if (indicator) {
@@ -221,8 +221,8 @@ export class DeesInputMultitoggle extends DeesInputBase<DeesInputMultitoggle> {
}
return;
}
const option: HTMLDivElement = this.shadowRoot.querySelector(
const option: HTMLDivElement | null = this.shadowRoot!.querySelector(
`.option:nth-child(${selectedIndex + 2})`
);

View File

@@ -67,7 +67,7 @@ export class DeesInputPhone extends DeesInputBase<DeesInputPhone> {
}
// Subscribe to the inner input's changes
const innerInput = this.shadowRoot.querySelector('dees-input-text') as any;
const innerInput = this.shadowRoot!.querySelector('dees-input-text') as any;
if (innerInput && innerInput.changeSubject) {
innerInput.changeSubject.subscribe(() => {
this.changeSubject.next(this);

View File

@@ -35,7 +35,7 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
accessor direction: 'vertical' | 'horizontal' = 'vertical';
@property({ type: String, reflect: true })
accessor validationState: 'valid' | 'invalid' | 'warn' | 'pending' = null;
accessor validationState: 'valid' | 'invalid' | 'warn' | 'pending' | null = null;
// Form compatibility
public get value() {
@@ -346,15 +346,15 @@ export class DeesInputRadiogroup extends DeesInputBase<string | object> {
}
private focusNextOption() {
const radioCircles = Array.from(this.shadowRoot.querySelectorAll('.radio-circle'));
const currentIndex = radioCircles.findIndex(el => el === this.shadowRoot.activeElement);
const radioCircles = Array.from(this.shadowRoot!.querySelectorAll('.radio-circle'));
const currentIndex = radioCircles.findIndex(el => el === this.shadowRoot!.activeElement);
const nextIndex = (currentIndex + 1) % radioCircles.length;
(radioCircles[nextIndex] as HTMLElement).focus();
}
private focusPreviousOption() {
const radioCircles = Array.from(this.shadowRoot.querySelectorAll('.radio-circle'));
const currentIndex = radioCircles.findIndex(el => el === this.shadowRoot.activeElement);
const radioCircles = Array.from(this.shadowRoot!.querySelectorAll('.radio-circle'));
const currentIndex = radioCircles.findIndex(el => el === this.shadowRoot!.activeElement);
const prevIndex = currentIndex <= 0 ? radioCircles.length - 1 : currentIndex - 1;
(radioCircles[prevIndex] as HTMLElement).focus();
}

View File

@@ -58,11 +58,11 @@ export class DeesInputRichtext extends DeesInputBase<string> {
@state()
accessor wordCount: number = 0;
private editorElement: HTMLElement;
private linkInputElement: HTMLInputElement;
private editorElement!: HTMLElement;
private linkInputElement!: HTMLInputElement;
private tiptapBundle: ITiptapBundle | null = null;
public editor: Editor;
public editor!: Editor;
public static styles = richtextStyles;
@@ -235,8 +235,8 @@ export class DeesInputRichtext extends DeesInputBase<string> {
// Load Tiptap from CDN
this.tiptapBundle = await DeesServiceLibLoader.getInstance().loadTiptap();
this.editorElement = this.shadowRoot.querySelector('.editor-content');
this.linkInputElement = this.shadowRoot.querySelector('.link-input input');
this.editorElement = this.shadowRoot!.querySelector('.editor-content')!;
this.linkInputElement = this.shadowRoot!.querySelector('.link-input input')!;
this.initializeEditor();
}
@@ -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 },

View File

@@ -73,9 +73,9 @@ export const demoFunc = () => html`
const inputs = elementArg.querySelectorAll('dees-input-text');
inputs.forEach((input: DeesInputText) => {
input.addEventListener('changeSubject', (event: CustomEvent) => {
input.addEventListener('changeSubject', ((event: CustomEvent) => {
console.log(`Input "${input.label}" changed to:`, input.getValue());
});
}) as EventListener);
input.addEventListener('blur', () => {
console.log(`Input "${input.label}" lost focus`);
@@ -271,7 +271,8 @@ export const demoFunc = () => html`
// Track password visibility toggles
const passwordInputs = elementArg.querySelectorAll('dees-input-text[isPasswordBool]');
passwordInputs.forEach((input: DeesInputText) => {
passwordInputs.forEach((_input) => {
const input = _input as DeesInputText;
// Monitor for toggle button clicks within shadow DOM
const checkToggle = () => {
const inputEl = input.shadowRoot?.querySelector('input');
@@ -316,10 +317,10 @@ export const demoFunc = () => html`
if (dynamicInput && output) {
// Update output on every change
dynamicInput.addEventListener('changeSubject', (event: CustomEvent) => {
dynamicInput.addEventListener('changeSubject', ((event: CustomEvent) => {
const value = (event.detail as DeesInputText).getValue();
output.textContent = `Current value: "${value}"`;
});
}) as EventListener);
// Also track focus/blur events
dynamicInput.addEventListener('focus', () => {

View File

@@ -47,7 +47,7 @@ export class DeesInputText extends DeesInputBase {
type: Boolean,
reflect: true,
})
accessor validationState: 'valid' | 'warn' | 'invalid';
accessor validationState!: 'valid' | 'warn' | 'invalid';
@property({
reflect: true,
@@ -55,7 +55,7 @@ export class DeesInputText extends DeesInputBase {
accessor validationText: string = '';
@property({})
accessor validationFunction: (value: string) => boolean;
accessor validationFunction!: (value: string) => boolean;
public static styles = [
themeDefaultStyles,
@@ -274,12 +274,12 @@ export class DeesInputText extends DeesInputBase {
}
public async focus() {
const textInput = this.shadowRoot.querySelector('input');
textInput.focus();
const textInput = this.shadowRoot!.querySelector('input');
textInput!.focus();
}
public async blur() {
const textInput = this.shadowRoot.querySelector('input');
textInput.blur();
const textInput = this.shadowRoot!.querySelector('input');
textInput!.blur();
}
}

View File

@@ -12,17 +12,19 @@ export const demoFunc = () => html`
if (toggleAllOnBtn && toggleAllOffBtn) {
toggleAllOnBtn.addEventListener('click', () => {
featureToggles.forEach((toggle: DeesInputToggle) => {
if (!toggle.disabled && !toggle.required) {
toggle.value = true;
featureToggles.forEach((toggle) => {
const t = toggle as unknown as DeesInputToggle;
if (!t.disabled && !t.required) {
t.value = true;
}
});
});
toggleAllOffBtn.addEventListener('click', () => {
featureToggles.forEach((toggle: DeesInputToggle) => {
if (!toggle.disabled && !toggle.required) {
toggle.value = false;
featureToggles.forEach((toggle) => {
const t = toggle as unknown as DeesInputToggle;
if (!t.disabled && !t.required) {
t.value = false;
}
});
});
@@ -280,10 +282,10 @@ export const demoFunc = () => html`
<dees-input-toggle
.label=${'Airplane mode'}
.value=${false}
@newValue=${(event: CustomEvent) => {
@newValue=${(event: Event) => {
const output = document.querySelector('#airplane-output');
if (output) {
output.textContent = `Airplane mode: ${event.detail ? 'ON' : 'OFF'}`;
output.textContent = `Airplane mode: ${(event as CustomEvent).detail ? 'ON' : 'OFF'}`;
}
}}
></dees-input-toggle>
@@ -291,10 +293,10 @@ export const demoFunc = () => html`
<dees-input-toggle
.label=${'Do not disturb'}
.value=${false}
@newValue=${(event: CustomEvent) => {
@newValue=${(event: Event) => {
const output = document.querySelector('#dnd-output');
if (output) {
output.textContent = `Do not disturb: ${event.detail ? 'ENABLED' : 'DISABLED'}`;
output.textContent = `Do not disturb: ${(event as CustomEvent).detail ? 'ENABLED' : 'DISABLED'}`;
}
}}
></dees-input-toggle>

View File

@@ -156,7 +156,7 @@ export class DeesInputTypelist extends DeesInputBase<DeesInputTypelist> {
<dees-label .label=${this.label} .description=${this.description}></dees-label>
<div class="mainbox">
<div class="tags" @click=${() => {
this.shadowRoot.querySelector('input').focus();
this.shadowRoot!.querySelector('input')!.focus();
}}>
${this.value.length === 0
? html`<div class="notags">No tags yet</div>`

View File

@@ -200,15 +200,16 @@ export class HeadingBlockHandler extends BaseBlockHandler {
const wysiwygBlock = (headingBlock.getRootNode() as ShadowRoot).host as any;
if (wysiwygBlock) {
const originalDisconnectedCallback = (wysiwygBlock as any).disconnectedCallback;
const self = this;
(wysiwygBlock as any).disconnectedCallback = async function() {
if (this.selectionHandler) {
document.removeEventListener('selectionchange', this.selectionHandler);
this.selectionHandler = null;
if (self.selectionHandler) {
document.removeEventListener('selectionchange', self.selectionHandler);
self.selectionHandler = null;
}
if (originalDisconnectedCallback) {
await originalDisconnectedCallback.call(wysiwygBlock);
}
}.bind(this);
};
}
}

View File

@@ -213,15 +213,16 @@ export class ListBlockHandler extends BaseBlockHandler {
const wysiwygBlock = (listBlock.getRootNode() as ShadowRoot).host as any;
if (wysiwygBlock) {
const originalDisconnectedCallback = (wysiwygBlock as any).disconnectedCallback;
const self = this;
(wysiwygBlock as any).disconnectedCallback = async function() {
if (this.selectionHandler) {
document.removeEventListener('selectionchange', this.selectionHandler);
this.selectionHandler = null;
if (self.selectionHandler) {
document.removeEventListener('selectionchange', self.selectionHandler);
self.selectionHandler = null;
}
if (originalDisconnectedCallback) {
await originalDisconnectedCallback.call(wysiwygBlock);
}
}.bind(this);
};
}
}

View File

@@ -193,15 +193,16 @@ export class ParagraphBlockHandler extends BaseBlockHandler {
const wysiwygBlock = element.closest('dees-wysiwyg-block');
if (wysiwygBlock) {
const originalDisconnectedCallback = (wysiwygBlock as any).disconnectedCallback;
const self = this;
(wysiwygBlock as any).disconnectedCallback = async function() {
if (this.selectionHandler) {
document.removeEventListener('selectionchange', this.selectionHandler);
this.selectionHandler = null;
if (self.selectionHandler) {
document.removeEventListener('selectionchange', self.selectionHandler);
self.selectionHandler = null;
}
if (originalDisconnectedCallback) {
await originalDisconnectedCallback.call(wysiwygBlock);
}
}.bind(this);
};
}
}

View File

@@ -192,15 +192,16 @@ export class QuoteBlockHandler extends BaseBlockHandler {
const wysiwygBlock = (quoteBlock.getRootNode() as ShadowRoot).host as any;
if (wysiwygBlock) {
const originalDisconnectedCallback = (wysiwygBlock as any).disconnectedCallback;
const self = this;
(wysiwygBlock as any).disconnectedCallback = async function() {
if (this.selectionHandler) {
document.removeEventListener('selectionchange', this.selectionHandler);
this.selectionHandler = null;
if (self.selectionHandler) {
document.removeEventListener('selectionchange', self.selectionHandler);
self.selectionHandler = null;
}
if (originalDisconnectedCallback) {
await originalDisconnectedCallback.call(wysiwygBlock);
}
}.bind(this);
};
}
}

View File

@@ -188,36 +188,39 @@ export class DeesFormattingMenu extends DeesElement {
public firstUpdated(): void {
// Set up event delegation for the menu
this.shadowRoot?.addEventListener('mousedown', (e: MouseEvent) => {
this.shadowRoot!.addEventListener('mousedown', (e: Event) => {
const mouseEvent = e as MouseEvent;
const menu = this.shadowRoot?.querySelector('.formatting-menu');
if (menu && menu.contains(e.target as Node)) {
if (menu && menu.contains(mouseEvent.target as Node)) {
// Prevent focus loss
e.preventDefault();
e.stopPropagation();
mouseEvent.preventDefault();
mouseEvent.stopPropagation();
}
});
this.shadowRoot?.addEventListener('click', (e: MouseEvent) => {
const target = e.target as HTMLElement;
this.shadowRoot!.addEventListener('click', (e: Event) => {
const mouseEvent = e as MouseEvent;
const target = mouseEvent.target as HTMLElement;
const button = target.closest('.format-button') as HTMLElement;
if (button) {
e.preventDefault();
e.stopPropagation();
mouseEvent.preventDefault();
mouseEvent.stopPropagation();
const command = button.getAttribute('data-command');
if (command) {
this.applyFormat(command);
}
}
});
this.shadowRoot?.addEventListener('focus', (e: FocusEvent) => {
this.shadowRoot!.addEventListener('focus', (e: Event) => {
const focusEvent = e as FocusEvent;
const menu = this.shadowRoot?.querySelector('.formatting-menu');
if (menu && menu.contains(e.target as Node)) {
if (menu && menu.contains(focusEvent.target as Node)) {
// Prevent menu from taking focus
e.preventDefault();
e.stopPropagation();
focusEvent.preventDefault();
focusEvent.stopPropagation();
}
}, true); // Use capture phase
}

View File

@@ -77,7 +77,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
@state()
accessor selectedText: string = '';
public editorContentRef: HTMLDivElement;
public editorContentRef!: HTMLDivElement;
public isComposing: boolean = false;
// Handler instances
@@ -144,7 +144,7 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
// No global selection listener needed
// Listen for custom selection events from blocks
this.addEventListener('block-text-selected', (e: CustomEvent) => {
this.addEventListener('block-text-selected', ((e: CustomEvent) => {
if (!this.slashMenu.visible && e.detail.hasSelection && e.detail.text.length > 0) {
this.selectedText = e.detail.text;
@@ -164,8 +164,8 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
);
}
}
});
}) as EventListener);
// Hide formatting menu when clicking outside
document.addEventListener('mousedown', (e) => {
// Check if click is on the formatting menu itself
@@ -896,14 +896,14 @@ export class DeesInputWysiwyg extends DeesInputBase<string> {
{
name: 'Cancel',
action: async (modal) => {
modal.destroy();
modal!.destroy();
resolve(null);
}
},
{
name: 'Add Link',
action: async (modal) => {
modal.destroy();
modal!.destroy();
resolve(linkUrl);
}
}

View File

@@ -216,46 +216,50 @@ export class DeesSlashMenu extends DeesElement {
public firstUpdated(): void {
// Set up event delegation
this.shadowRoot?.addEventListener('mousedown', (e: MouseEvent) => {
this.shadowRoot!.addEventListener('mousedown', (e: Event) => {
const mouseEvent = e as MouseEvent;
const menu = this.shadowRoot?.querySelector('.slash-menu');
if (menu && menu.contains(e.target as Node)) {
if (menu && menu.contains(mouseEvent.target as Node)) {
// Prevent focus loss
e.preventDefault();
e.stopPropagation();
mouseEvent.preventDefault();
mouseEvent.stopPropagation();
}
});
this.shadowRoot?.addEventListener('click', (e: MouseEvent) => {
const target = e.target as HTMLElement;
this.shadowRoot!.addEventListener('click', (e: Event) => {
const mouseEvent = e as MouseEvent;
const target = mouseEvent.target as HTMLElement;
const menuItem = target.closest('.slash-menu-item') as HTMLElement;
if (menuItem) {
e.preventDefault();
e.stopPropagation();
mouseEvent.preventDefault();
mouseEvent.stopPropagation();
const itemType = menuItem.getAttribute('data-item-type');
if (itemType) {
this.selectItem(itemType);
}
}
});
this.shadowRoot?.addEventListener('mouseenter', (e: MouseEvent) => {
const target = e.target as HTMLElement;
this.shadowRoot!.addEventListener('mouseenter', (e: Event) => {
const mouseEvent = e as MouseEvent;
const target = mouseEvent.target as HTMLElement;
const menuItem = target.closest('.slash-menu-item') as HTMLElement;
if (menuItem) {
const index = parseInt(menuItem.getAttribute('data-item-index') || '0', 10);
this.selectedIndex = index;
}
}, true); // Use capture phase
this.shadowRoot?.addEventListener('focus', (e: FocusEvent) => {
this.shadowRoot!.addEventListener('focus', (e: Event) => {
const focusEvent = e as FocusEvent;
const menu = this.shadowRoot?.querySelector('.slash-menu');
if (menu && menu.contains(e.target as Node)) {
if (menu && menu.contains(focusEvent.target as Node)) {
// Prevent menu from taking focus
e.preventDefault();
e.stopPropagation();
focusEvent.preventDefault();
focusEvent.stopPropagation();
}
}, true); // Use capture phase
}

View File

@@ -33,13 +33,13 @@ export class DeesWysiwygBlock extends DeesElement {
}
}
@property({ type: Object })
accessor block: IBlock;
accessor block!: IBlock;
@property({ type: Boolean })
accessor isSelected: boolean = false;
@property({ type: Object })
accessor handlers: IBlockEventHandlers;
accessor handlers!: IBlockEventHandlers;
@property({ type: Object })
accessor wysiwygComponent: any; // Reference to parent dees-input-wysiwyg

View File

@@ -105,10 +105,10 @@ export class WysiwygDragDropHandler {
handleDragEnd(): void {
// Clean up visual state
const allBlocks = this.component.editorContentRef.querySelectorAll('.block-wrapper');
allBlocks.forEach((block: HTMLElement) => {
block.classList.remove('dragging', 'move-up', 'move-down');
block.style.removeProperty('--drag-offset');
block.style.removeProperty('transform');
allBlocks.forEach((block) => {
(block as HTMLElement).classList.remove('dragging', 'move-up', 'move-down');
(block as HTMLElement).style.removeProperty('--drag-offset');
(block as HTMLElement).style.removeProperty('transform');
});
// Remove dragging class from editor

View File

@@ -704,17 +704,17 @@ export class WysiwygKeyboardHandler {
const rect = range.getBoundingClientRect();
// Get the container element
let container = range.commonAncestorContainer;
let container: Node = range.commonAncestorContainer;
if (container.nodeType === Node.TEXT_NODE) {
container = container.parentElement;
container = container.parentElement!;
}
// Get the top position of the container
const containerRect = (container as Element).getBoundingClientRect();
// Check if we're near the top (within 5px tolerance for line height variations)
const isNearTop = rect.top - containerRect.top < 5;
// For single-line content, also check if we're at the beginning
if (container.textContent && !container.textContent.includes('\n')) {
const cursorPos = WysiwygSelection.getCursorPositionInElement(container as Element, ...shadowRoots);
@@ -740,11 +740,11 @@ export class WysiwygKeyboardHandler {
const rect = range.getBoundingClientRect();
// Get the container element
let container = range.commonAncestorContainer;
let container: Node = range.commonAncestorContainer;
if (container.nodeType === Node.TEXT_NODE) {
container = container.parentElement;
container = container.parentElement!;
}
// Get the bottom position of the container
const containerRect = (container as Element).getBoundingClientRect();

View File

@@ -72,7 +72,7 @@ export class WysiwygModalManager {
{
name: 'Cancel',
action: async (modal) => {
modal.destroy();
modal!.destroy();
resolve(null);
}
}
@@ -158,7 +158,7 @@ export class WysiwygModalManager {
{
name: 'Done',
action: async (modal) => {
modal.destroy();
modal!.destroy();
}
}
]

View File

@@ -56,10 +56,10 @@ export const demoFunc = () => html`
const roundProfile = elementArg.querySelector('dees-input-profilepicture[shape="round"]');
if (roundProfile) {
roundProfile.addEventListener('change', (event: CustomEvent) => {
roundProfile.addEventListener('change', ((event: CustomEvent) => {
const target = event.target as DeesInputProfilePicture;
console.log('Round profile picture changed:', target.value?.substring(0, 50) + '...');
});
}) as EventListener);
}
}}>
<dees-panel .title=${'Profile Picture Input'} .subtitle=${'Basic usage with round and square shapes'}>
@@ -85,10 +85,10 @@ export const demoFunc = () => html`
// Different sizes demo
const profiles = elementArg.querySelectorAll('dees-input-profilepicture');
profiles.forEach((profile) => {
profile.addEventListener('change', (event: CustomEvent) => {
profile.addEventListener('change', ((event: CustomEvent) => {
const target = event.target as DeesInputProfilePicture;
console.log(`Profile (size ${target.size}) changed`);
});
}) as EventListener);
});
}}>
<dees-panel .title=${'Size Variations'} .subtitle=${'Profile pictures in different sizes'}>
@@ -122,15 +122,15 @@ export const demoFunc = () => html`
if (prefilledProfile) {
prefilledProfile.value = sampleImageUrl;
prefilledProfile.addEventListener('change', (event: CustomEvent) => {
prefilledProfile.addEventListener('change', ((event: CustomEvent) => {
const target = event.target as DeesInputProfilePicture;
const output = elementArg.querySelector('#prefilled-output');
if (output) {
output.textContent = target.value ?
`Image data: ${target.value.substring(0, 80)}...` :
output.textContent = target.value ?
`Image data: ${target.value.substring(0, 80)}...` :
'No image selected';
}
});
}) as EventListener);
}
}}>
<dees-panel .title=${'Pre-filled and Value Binding'} .subtitle=${'Profile picture with initial value and change tracking'}>

View File

@@ -436,10 +436,10 @@ export class DeesInputProfilePicture extends DeesInputBase<DeesInputProfilePictu
this.modalInstance.outputSize = this.outputSize;
this.modalInstance.outputQuality = this.outputQuality;
this.modalInstance.addEventListener('save', (event: CustomEvent) => {
this.modalInstance.addEventListener('save', ((event: CustomEvent) => {
this.value = event.detail.croppedImage;
this.changeSubject.next(this);
});
}) as EventListener);
document.body.appendChild(this.modalInstance);
}

View File

@@ -41,7 +41,7 @@ export class DeesChips extends DeesElement {
accessor selectableChips: Tag[] = [];
@property()
accessor selectedChip: Tag = null;
accessor selectedChip: Tag | null = null;
@property({
type: Array,

View File

@@ -1,5 +1,6 @@
import { html, css, cssManager } from '@design.estate/dees-element';
import type { DeesDashboardgrid } from './dees-dashboardgrid.js';
import type { LayoutDirection } from './types.js';
import '@design.estate/dees-wcctools/demotools';
export const demoFunc = () => {
@@ -160,7 +161,7 @@ export const demoFunc = () => {
});
// Enhanced logging for reflow events
let lastPlaceholderPosition = null;
let lastPlaceholderPosition: Record<string, string> | null = null;
let moveEventCounter = 0;
// Helper function to log grid state
@@ -231,25 +232,28 @@ export const demoFunc = () => {
// Log initial state
logGridState('Initial Grid State');
grid.addEventListener('widget-move', (e: CustomEvent) => {
grid.addEventListener('widget-move', (e: Event) => {
const detail = (e as CustomEvent).detail;
logGridState('Widget Move', {
widget: e.detail.widget,
displaced: e.detail.displaced,
swappedWith: e.detail.swappedWith
widget: detail.widget,
displaced: detail.displaced,
swappedWith: detail.swappedWith
});
});
grid.addEventListener('widget-resize', (e: CustomEvent) => {
grid.addEventListener('widget-resize', (e: Event) => {
const detail = (e as CustomEvent).detail;
logGridState('Widget Resize', {
widget: e.detail.widget,
displaced: e.detail.displaced,
swappedWith: e.detail.swappedWith
widget: detail.widget,
displaced: detail.displaced,
swappedWith: detail.swappedWith
});
});
grid.addEventListener('widget-remove', (e: CustomEvent) => {
grid.addEventListener('widget-remove', (e: Event) => {
const detail = (e as CustomEvent).detail;
logGridState('Widget Remove', {
removedWidget: e.detail.widget
removedWidget: detail.widget
});
updateStatus();
});
@@ -312,7 +316,7 @@ export const demoFunc = () => {
// Log compact operations
const originalCompact = grid.compact.bind(grid);
grid.compact = (direction?: string) => {
grid.compact = (direction?: LayoutDirection) => {
console.group('🗜️ Compacting Grid');
console.log('Direction:', direction || 'vertical');
logGridState('Before Compact');

View File

@@ -32,7 +32,7 @@ export class DeesLabel extends DeesElement {
type: String,
reflect: true,
})
accessor description: string;
accessor description!: string;
@property({
type: Boolean,

View File

@@ -45,7 +45,7 @@ export class DeesStepper extends DeesElement {
@property({
type: Object,
})
accessor selectedStep: IStep;
accessor selectedStep!: IStep;
constructor() {
super();
@@ -214,19 +214,19 @@ export class DeesStepper extends DeesElement {
this.setScrollStatus();
// Remove entrance class after initial animation completes
await this.domtools.convenience.smartdelay.delayFor(350);
this.shadowRoot.querySelector('.step.entrance')?.classList.remove('entrance');
this.shadowRoot!.querySelector('.step.entrance')?.classList.remove('entrance');
}
public async updated() {
this.setScrollStatus();
}
public scroller: typeof domtools.plugins.SweetScroll.prototype;
public scroller!: typeof domtools.plugins.SweetScroll.prototype;
public async setScrollStatus() {
const stepperContainer: HTMLElement = this.shadowRoot.querySelector('.stepperContainer');
const firstStepElement: HTMLElement = this.shadowRoot.querySelector('.step');
const selectedStepElement: HTMLElement = this.shadowRoot.querySelector('.selected');
const stepperContainer = this.shadowRoot!.querySelector('.stepperContainer') as HTMLElement;
const firstStepElement = this.shadowRoot!.querySelector('.step') as HTMLElement;
const selectedStepElement = this.shadowRoot!.querySelector('.selected') as HTMLElement;
if (!selectedStepElement) {
return;
}
@@ -278,7 +278,7 @@ export class DeesStepper extends DeesElement {
this.selectedStep = previousStep;
await this.domtoolsPromise;
await this.domtools.convenience.smartdelay.delayFor(100);
this.selectedStep.onReturnToStepFunc?.(this, this.shadowRoot.querySelector('.selected'));
this.selectedStep.onReturnToStepFunc?.(this, this.shadowRoot!.querySelector('.selected') as HTMLElement);
}
public goNext() {

View File

@@ -21,9 +21,9 @@ export function throttle<T extends (...args: any[]) => any>(
): (...args: Parameters<T>) => void {
let inThrottle: boolean;
return function executedFunction(...args: Parameters<T>) {
return (...args: Parameters<T>) => {
if (!inThrottle) {
func.apply(this, args);
func(...args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -1,9 +1,9 @@
import { property, html, customElement, type TemplateResult, type CSSResult } from '@design.estate/dees-element';
import { property, state, html, customElement, type TemplateResult, type CSSResult } from '@design.estate/dees-element';
import { DeesTileBase } from '../dees-tile-shared/DeesTileBase.js';
import { 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;

View File

@@ -1,4 +1,4 @@
import { html } from '@design.estate/dees-element';
import { html, cssManager } from '@design.estate/dees-element';
export const demo = () => {
const samplePdfs = [
@@ -7,7 +7,7 @@ export const demo = () => {
];
const generateGridItems = (count: number) => {
const items = [];
const items: ReturnType<typeof html>[] = [];
for (let i = 0; i < count; i++) {
const pdfUrl = samplePdfs[i % samplePdfs.length];
items.push(html`
@@ -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>

View File

@@ -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 {

View File

@@ -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();

View File

@@ -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;

View File

@@ -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,

View File

@@ -130,7 +130,7 @@ export class DeesContextmenu extends DeesElement {
type: Array,
})
accessor menuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean; submenu?: (plugins.tsclass.website.IMenuItem & { shortcut?: string; disabled?: boolean } | { divider: true })[]; divider?: never } | { divider: true })[] = [];
windowLayer: DeesWindowLayer;
windowLayer!: DeesWindowLayer;
private submenu: DeesContextmenu | null = null;
private submenuTimeout: any = null;
@@ -278,7 +278,7 @@ export class DeesContextmenu extends DeesElement {
}
private handleKeydown = (event: KeyboardEvent) => {
const menuItems = Array.from(this.shadowRoot.querySelectorAll('.menuitem:not(.disabled)'));
const menuItems = Array.from(this.shadowRoot!.querySelectorAll('.menuitem:not(.disabled)'));
const currentIndex = menuItems.findIndex(item => item.matches(':hover'));
switch (event.key) {
@@ -352,7 +352,7 @@ export class DeesContextmenu extends DeesElement {
if (!menuItem.submenu || menuItem.submenu.length === 0) return;
// Find the menu item element
const menuItems = Array.from(this.shadowRoot.querySelectorAll('.menuitem'));
const menuItems = Array.from(this.shadowRoot!.querySelectorAll('.menuitem'));
const menuItemElement = menuItems.find(el => el.querySelector('.menuitem-text')?.textContent === menuItem.name) as HTMLElement;
if (!menuItemElement) return;

View File

@@ -49,7 +49,7 @@ export const demoFunc = () => html`
heading: 'With Help Button',
showHelpButton: true,
onHelp: async () => {
const helpModal = await DeesModal.createAndShow({
await DeesModal.createAndShow({
heading: 'Help',
width: 'small',
showCloseButton: true,
@@ -60,7 +60,7 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Got it',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
},
@@ -70,7 +70,7 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'OK',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>With Help Button</dees-button>
@@ -85,7 +85,7 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Close',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>No Close Button</dees-button>
@@ -101,7 +101,7 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Done',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Both Buttons</dees-button>
@@ -116,7 +116,7 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Close',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Clean Header</dees-button>
@@ -136,10 +136,10 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Cancel',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}, {
name: 'OK',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Small Modal</dees-button>
@@ -157,10 +157,10 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Cancel',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}, {
name: 'Sign Up',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Medium Modal</dees-button>
@@ -181,10 +181,10 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Cancel',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}, {
name: 'Save',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Large Modal</dees-button>
@@ -207,10 +207,10 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Save',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}, {
name: 'Cancel',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Fullscreen Modal</dees-button>
@@ -230,7 +230,7 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Close',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Custom 700px</dees-button>
@@ -245,7 +245,7 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Got it',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Max Width 600px</dees-button>
@@ -260,7 +260,7 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'OK',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Min Width 400px</dees-button>
@@ -279,13 +279,13 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Delete',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}, {
name: 'Cancel',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}, {
name: 'Save Changes',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Three Buttons</dees-button>
@@ -298,7 +298,7 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Acknowledge',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Single Button</dees-button>
@@ -322,10 +322,10 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Discard All Changes',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}, {
name: 'Save and Continue Editing',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Long Labels</dees-button>
@@ -347,7 +347,7 @@ export const demoFunc = () => html`
`,
menuOptions: [{
name: 'Close',
action: async (modal) => modal.destroy()
action: async (modal) => modal!.destroy()
}],
});
}}>Test Responsive</dees-button>

View File

@@ -85,7 +85,7 @@ export class DeesModal extends DeesElement {
accessor heading = '';
@state({})
accessor content: TemplateResult;
accessor content!: TemplateResult;
@state({})
accessor menuOptions: plugins.tsclass.website.IMenuItem<DeesModal>[] = [];
@@ -94,10 +94,10 @@ export class DeesModal extends DeesElement {
accessor width: 'small' | 'medium' | 'large' | 'fullscreen' | number = 'medium';
@property({ type: Number })
accessor maxWidth: number;
accessor maxWidth!: number;
@property({ type: Number })
accessor minWidth: number;
accessor minWidth!: number;
@property({ type: Boolean })
accessor showCloseButton: boolean = true;
@@ -106,7 +106,7 @@ export class DeesModal extends DeesElement {
accessor showHelpButton: boolean = false;
@property({ attribute: false })
accessor onHelp: () => void | Promise<void>;
accessor onHelp!: () => void | Promise<void>;
@property({ type: Boolean })
accessor mobileFullscreen: boolean = false;
@@ -383,18 +383,18 @@ export class DeesModal extends DeesElement {
`;
}
private windowLayer: DeesWindowLayer;
private windowLayer!: DeesWindowLayer;
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
super.firstUpdated(_changedProperties);
const domtools = await this.domtoolsPromise;
await domtools.convenience.smartdelay.delayFor(30);
const modal = this.shadowRoot.querySelector('.modal');
modal.classList.add('show');
const modal = this.shadowRoot!.querySelector('.modal');
modal!.classList.add('show');
}
public async handleOutsideClick(eventArg: MouseEvent) {
eventArg.stopPropagation();
const modalContainer = this.shadowRoot.querySelector('.modalContainer');
const modalContainer = this.shadowRoot!.querySelector('.modalContainer');
if (eventArg.target === modalContainer) {
await this.destroy();
}
@@ -402,8 +402,8 @@ export class DeesModal extends DeesElement {
public async destroy() {
const domtools = await this.domtoolsPromise;
const modal = this.shadowRoot.querySelector('.modal');
modal.classList.add('predestroy');
const modal = this.shadowRoot!.querySelector('.modal');
modal!.classList.add('predestroy');
await domtools.convenience.smartdelay.delayFor(200);
document.body.removeChild(this);
await this.windowLayer.destroy();

View File

@@ -42,7 +42,7 @@ export class DeesSpeechbubble extends DeesElement {
speechbubble.manifested = true;
windowLayer.appendChild(speechbubble);
windowLayer.style.pointerEvents = 'none';
(windowLayer.shadowRoot.querySelector('.windowOverlay') as HTMLElement).style.pointerEvents = 'none';
(windowLayer.shadowRoot!.querySelector('.windowOverlay') as HTMLElement).style.pointerEvents = 'none';
return speechbubble;
}
@@ -50,13 +50,13 @@ export class DeesSpeechbubble extends DeesElement {
@property({
type: Object,
})
accessor reffedElement: HTMLElement;
accessor reffedElement!: HTMLElement;
@property({
type: String,
reflect: true,
})
accessor text: string;
accessor text!: string;
@property({
type: Boolean,
@@ -73,7 +73,7 @@ export class DeesSpeechbubble extends DeesElement {
})
accessor status: 'normal' | 'pending' | 'success' | 'error' = 'normal';
public windowLayer: DeesWindowLayer;
public windowLayer!: DeesWindowLayer;
constructor() {
super();
@@ -189,7 +189,7 @@ export class DeesSpeechbubble extends DeesElement {
}
if (this.manifested) {
await this.updatePosition();
(this.shadowRoot.querySelector('.maincontainer') as HTMLElement).style.opacity = '1';
(this.shadowRoot!.querySelector('.maincontainer') as HTMLElement).style.opacity = '1';
} else {
// lets make sure we instrument it
let speechbubble: DeesSpeechbubble;
@@ -227,7 +227,7 @@ export class DeesSpeechbubble extends DeesElement {
public async show() {}
public async destroy() {
(this.shadowRoot.querySelector('.maincontainer') as HTMLElement).style.opacity = '0';
(this.shadowRoot!.querySelector('.maincontainer') as HTMLElement).style.opacity = '0';
this.windowLayer.destroy();
}
}

View File

@@ -132,7 +132,7 @@ export class WebContainerEnvironment implements IExecutionEnvironment {
const watcher = this.container!.fs.watch(
path,
{ recursive: options?.recursive ?? false },
callback
callback as (event: 'rename' | 'change', filename: string | Uint8Array) => void
);
return {
stop: () => watcher.close(),

View File

@@ -109,14 +109,14 @@ export const demoFunc = () => html`
<dees-panel .title=${'Interactive Shopping Cart'} .subtitle=${'Product cards with dynamic cart calculation'} .runAfterRender=${async (elementArg: HTMLElement) => {
const products = [
{ id: 'laptop', element: null, data: { name: 'MacBook Pro 14"', category: 'Computers', description: 'M3 Pro chip with 18GB RAM', price: 1999, originalPrice: 2199, iconName: 'lucide:laptop' }},
{ id: 'ipad', element: null, data: { name: 'iPad Air', category: 'Tablets', description: '10.9" Liquid Retina display', price: 599, iconName: 'lucide:tablet' }},
{ id: 'keyboard', element: null, data: { name: 'Magic Keyboard', category: 'Accessories', description: 'Wireless keyboard with Touch ID', price: 149, iconName: 'lucide:keyboard' }}
{ id: 'laptop', element: null as DeesShoppingProductcard | null, data: { name: 'MacBook Pro 14"', category: 'Computers', description: 'M3 Pro chip with 18GB RAM', price: 1999, originalPrice: 2199, iconName: 'lucide:laptop' }},
{ id: 'ipad', element: null as DeesShoppingProductcard | null, data: { name: 'iPad Air', category: 'Tablets', description: '10.9" Liquid Retina display', price: 599, iconName: 'lucide:tablet' }},
{ id: 'keyboard', element: null as DeesShoppingProductcard | null, data: { name: 'Magic Keyboard', category: 'Accessories', description: 'Wireless keyboard with Touch ID', price: 149, iconName: 'lucide:keyboard' }}
];
const updateCartSummary = () => {
let total = 0;
const items = [];
const items: string[] = [];
products.forEach(product => {
const element = elementArg.querySelector(`#${product.id}`) as DeesShoppingProductcard;
@@ -216,8 +216,8 @@ export const demoFunc = () => html`
const output = document.querySelector('#selection-output');
if (output) {
const selectedCards = document.querySelectorAll('dees-shopping-productcard[selectable]');
const selectedProducts = [];
selectedCards.forEach((card: DeesShoppingProductcard) => {
const selectedProducts: string[] = [];
(selectedCards as NodeListOf<DeesShoppingProductcard>).forEach((card) => {
if (card.selected) {
selectedProducts.push(card.productData.name);
}
@@ -243,8 +243,8 @@ export const demoFunc = () => html`
const output = document.querySelector('#selection-output');
if (output) {
const selectedCards = document.querySelectorAll('dees-shopping-productcard[selectable]');
const selectedProducts = [];
selectedCards.forEach((card: DeesShoppingProductcard) => {
const selectedProducts: string[] = [];
(selectedCards as NodeListOf<DeesShoppingProductcard>).forEach((card) => {
if (card.selected) {
selectedProducts.push(card.productData.name);
}
@@ -271,8 +271,8 @@ export const demoFunc = () => html`
const output = document.querySelector('#selection-output');
if (output) {
const selectedCards = document.querySelectorAll('dees-shopping-productcard[selectable]');
const selectedProducts = [];
selectedCards.forEach((card: DeesShoppingProductcard) => {
const selectedProducts: string[] = [];
(selectedCards as NodeListOf<DeesShoppingProductcard>).forEach((card) => {
if (card.selected) {
selectedProducts.push(card.productData.name);
}

View File

@@ -46,7 +46,7 @@ export class DeesSimpleAppDash extends DeesElement {
accessor terminalSetupCommand: string = `echo "Terminal ready"`;
@state()
accessor selectedView: IView;
accessor selectedView!: IView;
public static styles = [
@@ -386,7 +386,7 @@ export class DeesSimpleAppDash extends DeesElement {
`;
}
public async firstUpdated(_changedProperties): Promise<void> {
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>): Promise<void> {
const domtools = await this.domtoolsPromise;
super.firstUpdated(_changedProperties);
if (this.viewTabs && this.viewTabs.length > 0) {
@@ -395,7 +395,7 @@ export class DeesSimpleAppDash extends DeesElement {
}
}
public currentTerminal: DeesWorkspaceTerminal;
public currentTerminal: DeesWorkspaceTerminal | null = null;
public async launchTerminal() {
const domtools = await this.domtoolsPromise;
if (this.currentTerminal) {
@@ -404,7 +404,7 @@ export class DeesSimpleAppDash extends DeesElement {
return;
}
const maincontainer = this.shadowRoot.querySelector('.maincontainer');
const maincontainer = this.shadowRoot!.querySelector('.maincontainer')! as HTMLElement;
const { DeesWorkspaceTerminal } = await import('../../00group-workspace/dees-workspace-terminal/dees-workspace-terminal.js');
const terminal = new DeesWorkspaceTerminal();
terminal.setupCommand = this.terminalSetupCommand;
@@ -444,9 +444,9 @@ export class DeesSimpleAppDash extends DeesElement {
}
private currentView: DeesElement;
private currentView!: DeesElement;
public async loadView(viewArg: IView) {
const appcontent = this.shadowRoot.querySelector('.appcontent');
const appcontent = this.shadowRoot!.querySelector('.appcontent')!;
const view = new viewArg.element();
if (this.currentView) {
this.currentView.remove();

View File

@@ -141,15 +141,15 @@ export class DeesSimpleLogin extends DeesElement {
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>): Promise<void> {
super.firstUpdated(_changedProperties);
const form = this.shadowRoot.querySelector('dees-form') as any;
const form = this.shadowRoot!.querySelector('dees-form') as any;
if (form) {
form.addEventListener('formData', (event: CustomEvent) => {
form.addEventListener('formData', ((event: CustomEvent) => {
this.dispatchEvent(new CustomEvent('login', {
detail: event.detail,
bubbles: true,
composed: true
}));
});
}) as EventListener);
}
}
@@ -158,9 +158,9 @@ export class DeesSimpleLogin extends DeesElement {
*/
public async switchToSlottedContent() {
const domtools = await this.domtoolsPromise;
const loginDiv: HTMLDivElement = this.shadowRoot.querySelector('.login');
const loginContainerDiv: HTMLDivElement = this.shadowRoot.querySelector('.loginContainer');
const slotContainerDiv: HTMLDivElement = this.shadowRoot.querySelector('.slotContainer');
const loginDiv = this.shadowRoot!.querySelector('.login') as HTMLDivElement;
const loginContainerDiv = this.shadowRoot!.querySelector('.loginContainer') as HTMLDivElement;
const slotContainerDiv = this.shadowRoot!.querySelector('.slotContainer') as HTMLDivElement;
loginDiv.style.opacity = '0';
loginDiv.style.transform = 'translateY(20px)';
loginContainerDiv.style.pointerEvents = 'none';

View File

@@ -15,7 +15,7 @@ export const demoFunc = () => {
.filter(key => {
// Skip utility functions and focus on icon components (first letter is uppercase)
const isUppercaseFirst = key[0] === key[0].toUpperCase() && key[0] !== key[0].toLowerCase();
const isFunction = typeof lucideIcons[key] === 'function';
const isFunction = typeof (lucideIcons as any)[key] === 'function';
const notUtility = !['createElement', 'createIcons', 'default'].includes(key);
return isFunction && isUppercaseFirst && notUtility;
})
@@ -63,7 +63,7 @@ export const demoFunc = () => {
const searchIcons = (event: InputEvent) => {
const searchTerm = (event.target as HTMLInputElement).value.toLowerCase().trim();
// Get the demo container first, then search within it
const demoContainer = (event.target as HTMLElement).closest('.demoContainer');
const demoContainer = (event.target as HTMLElement).closest('.demoContainer')!;
const containers = demoContainer.querySelectorAll('.iconContainer');
containers.forEach(container => {
@@ -79,7 +79,7 @@ export const demoFunc = () => {
});
// Update counts - search within demoContainer
demoContainer.querySelectorAll('.section-container').forEach(section => {
demoContainer!.querySelectorAll('.section-container').forEach(section => {
const visibleIcons = section.querySelectorAll('.iconContainer:not(.hidden)').length;
const countElement = section.querySelector('.icon-count');
if (countElement) {

View File

@@ -208,7 +208,7 @@ export class DeesIcon extends DeesElement {
accessor icon: IconWithPrefix | undefined = undefined;
@property({ type: Number })
accessor iconSize: number;
accessor iconSize!: number;
@property({ type: String })
accessor color: string = 'currentColor';
@@ -292,13 +292,13 @@ export class DeesIcon extends DeesElement {
const pascalCaseName = iconName.charAt(0).toUpperCase() + iconName.slice(1);
// Check if the icon exists in lucideIcons
if (!lucideIcons[pascalCaseName]) {
if (!(lucideIcons as any)[pascalCaseName]) {
console.warn(`Lucide icon '${pascalCaseName}' not found in lucideIcons object`);
return '';
}
// Use the exact pattern from Lucide documentation
const svgElement = createElement(lucideIcons[pascalCaseName], {
const svgElement = createElement((lucideIcons as any)[pascalCaseName], {
color: this.color,
size: this.iconSize,
strokeWidth: this.strokeWidth
@@ -404,9 +404,9 @@ export class DeesIcon extends DeesElement {
// Convert to PascalCase
const pascalCaseName = name.charAt(0).toUpperCase() + name.slice(1);
if (lucideIcons[pascalCaseName]) {
if ((lucideIcons as any)[pascalCaseName]) {
// Use the documented pattern from Lucide docs
const svgElement = createElement(lucideIcons[pascalCaseName], {
const svgElement = createElement((lucideIcons as any)[pascalCaseName], {
color: this.color,
size: this.iconSize,
strokeWidth: this.strokeWidth

View File

@@ -33,12 +33,12 @@ export class DeesUpdater extends DeesElement {
@property({
type: String,
})
accessor currentVersion: string;
accessor currentVersion!: string;
@property({
type: String,
})
accessor updatedVersion: string;
accessor updatedVersion!: string;
constructor() {
super();
@@ -107,7 +107,7 @@ export class DeesUpdater extends DeesElement {
}
public async destroy() {
this.parentElement.removeChild(this);
this.parentElement!.removeChild(this);
}
private windowLayerClicked() {}

View File

@@ -447,7 +447,7 @@ export class DeesWorkspaceFiletree extends DeesElement {
e.preventDefault();
e.stopPropagation();
const menuItems = [];
const menuItems: Array<{ name?: string; iconName?: string; action?: () => Promise<void>; divider?: boolean }> = [];
if (node.type === 'directory') {
// Directory-specific options
@@ -503,7 +503,7 @@ export class DeesWorkspaceFiletree extends DeesElement {
}
);
await DeesContextmenu.openContextMenuWithOptions(e, menuItems);
await DeesContextmenu.openContextMenuWithOptions(e, menuItems as any);
}
private async handleEmptySpaceContextMenu(e: MouseEvent) {
@@ -561,14 +561,14 @@ export class DeesWorkspaceFiletree extends DeesElement {
menuOptions: [
{
name: 'Cancel',
action: async (modalRef) => {
action: async (modalRef: any) => {
await modalRef.destroy();
resolve(null);
},
},
{
name: options.buttonName || 'Create',
action: async (modalRef) => {
action: async (modalRef: any) => {
// Query the input element directly and read its value
const contentEl = modalRef.shadowRoot?.querySelector('.modal .content');
const inputElement = contentEl?.querySelector('dees-input-text') as DeesInputText | null;

View File

@@ -129,8 +129,8 @@ export class DeesWorkspaceMarkdown extends DeesElement {
@state()
accessor isDragging: boolean = false;
private resizeHandleElement: HTMLElement;
private containerElement: HTMLElement;
private resizeHandleElement!: HTMLElement;
private containerElement!: HTMLElement;
public render() {
return html`
@@ -173,26 +173,26 @@ const hello = 'yes'
`;
}
public async firstUpdated(_changedPropertiesArg) {
public async firstUpdated(_changedPropertiesArg: Map<string | number | symbol, unknown>) {
await super.firstUpdated(_changedPropertiesArg);
// Initialize current ratio from property
this.currentSplitRatio = this.splitRatio;
// Cache elements
this.containerElement = this.shadowRoot.querySelector('.splitContainer');
this.resizeHandleElement = this.shadowRoot.querySelector('.resizeHandle');
this.containerElement = this.shadowRoot!.querySelector('.splitContainer')!;
this.resizeHandleElement = this.shadowRoot!.querySelector('.resizeHandle')!;
const editor = this.shadowRoot.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
const editor = this.shadowRoot!.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
// Wire up markdown rendering
const markdownOutlet = this.shadowRoot.querySelector('dees-workspace-markdownoutlet');
const markdownOutlet = this.shadowRoot!.querySelector('dees-workspace-markdownoutlet');
const smartmarkdownInstance = new domtools.plugins.smartmarkdown.SmartMarkdown();
const mdParsedResult = await smartmarkdownInstance.getMdParsedResultFromMarkdown('loading...')
editor.contentSubject.subscribe(async contentArg => {
await mdParsedResult.updateFromMarkdownString(contentArg)
const html = mdParsedResult.html;
markdownOutlet.updateHtmlText(html);
markdownOutlet!.updateHtmlText(html);
});
}
@@ -226,7 +226,7 @@ const hello = 'yes'
document.removeEventListener('mouseup', this.handleMouseUp);
// Trigger resize on monaco editor
const editor = this.shadowRoot.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
const editor = this.shadowRoot!.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
if (editor) {
// Monaco needs to be notified of size changes
window.dispatchEvent(new Event('resize'));

View File

@@ -381,7 +381,7 @@ export class DeesWorkspaceMarkdownoutlet extends DeesElement {
];
// INSTANCE
private outlet: HTMLElement;
private outlet!: HTMLElement;
public render(): TemplateResult {
return html`
@@ -394,13 +394,13 @@ export class DeesWorkspaceMarkdownoutlet extends DeesElement {
public async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
await super.firstUpdated(_changedProperties);
this.outlet = this.shadowRoot.querySelector('.outlet');
this.outlet = this.shadowRoot!.querySelector('.outlet')!;
}
public async updateHtmlText(htmlTextArg: string) {
await this.updateComplete;
if (!this.outlet) {
this.outlet = this.shadowRoot.querySelector('.outlet');
this.outlet = this.shadowRoot!.querySelector('.outlet')!;
}
this.outlet.innerHTML = htmlTextArg;
}

Some files were not shown because too many files have changed in this diff Show More