Compare commits
102 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d3a1783fd | |||
| af1f660486 | |||
| b1c8a7446e | |||
| 7e991396e9 | |||
| 25cbf9bfdd | |||
| 4d8ba1fefc | |||
| 42317459ff | |||
| 932db338c6 | |||
| bc4b87b83a | |||
| eb055e7214 | |||
| c55eb948fe | |||
| 35779209ea | |||
| 8c6738ea15 | |||
| e7da1d8b44 | |||
| 358d82e7fa | |||
| 6452e05e1d | |||
| 07b536ea9a | |||
| 3fcb0cbf89 | |||
| 3285cbf0e7 | |||
| a2d750b2f6 | |||
| d4276710e6 | |||
| 66d64bf476 | |||
| 2504251707 | |||
| fed130f291 | |||
| 4f05b5907b | |||
| e517320dcd | |||
| ade5a25b3a | |||
| a396dfea12 | |||
| d0105e1b80 | |||
| 1eeebb35e6 | |||
| 14e8b8c533 | |||
| eaf327ea75 | |||
| 09741e0b37 | |||
| 5cadd1fc7f | |||
| 1795235c6d | |||
| ba7d387acb | |||
| 26ca16a284 | |||
| 3ab3eb5e5e | |||
| da5dbc70e2 | |||
| 19b7981542 | |||
| bad105074e | |||
| f124091784 | |||
| 68790a26ed | |||
| 2abf7e356d | |||
| ecd35683e6 | |||
| 93cb632448 | |||
| 047e42c6a3 | |||
| 59efa8cff0 | |||
| 09f0aa97dd | |||
| 7c62f45d77 | |||
| b123768474 | |||
| f292e7a7f4 | |||
| d82e5603a7 | |||
| 7e2386bcdf | |||
| eba2a03355 | |||
| 06c01f0690 | |||
| 91e03eb9c4 | |||
| b7f3f47c61 | |||
| 0a83f0e136 | |||
| 2b048cf34f | |||
| 7e50b8cb3f | |||
| b97601a876 | |||
| 5ddeb8fe7c | |||
| 25f46162c5 | |||
| b84b0e7ce6 | |||
| 69840de3a6 | |||
| 85badfbd21 | |||
| 264e460365 | |||
| bfda6b75e7 | |||
| 825a74b810 | |||
| f6bf0f8a45 | |||
| 66661e05a9 | |||
| 162688cdb5 | |||
| 8158b791c7 | |||
| ed8167385f | |||
| b472057e9d | |||
| 1bbf853043 | |||
| 8ff52fc562 | |||
| 5dd0367df0 | |||
| 1982c40337 | |||
| d2925871fd | |||
| 13ed06872a | |||
| 909e49dbd7 | |||
| 13923d9feb | |||
| e981ddf2d6 | |||
| b478ae3071 | |||
| d329d0b171 | |||
| 74c39482de | |||
| 51611d76dd | |||
| 496084f870 | |||
| c7bff04ae5 | |||
| e71efd409b | |||
| 43db777f2c | |||
| 9bd1734d09 | |||
| aafdb4af72 | |||
| 67a24ddf26 | |||
| 2a928886b9 | |||
| 4d192654df | |||
| a634c2e237 | |||
| 9b0b448cb1 | |||
| ba4aa912af | |||
| ca4f994b55 |
3
.gitignore
vendored
@@ -16,4 +16,5 @@ node_modules/
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
||||
# custom
|
||||
.playwright-mcp/
|
||||
|
Before Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 53 KiB |
@@ -49,5 +49,18 @@
|
||||
},
|
||||
"@ship.zone/szci": {
|
||||
"npmGlobalTools": []
|
||||
},
|
||||
"@git.zone/tsbundle": {
|
||||
"bundles": [
|
||||
{
|
||||
"from": "./ts_web/index.ts",
|
||||
"to": "./dist_bundle/bundle.js",
|
||||
"outputMode": "bundle",
|
||||
"bundler": "esbuild"
|
||||
}
|
||||
]
|
||||
},
|
||||
"@git.zone/tswatch": {
|
||||
"preset": "element"
|
||||
}
|
||||
}
|
||||
2
.vscode/settings.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/npmextra.json"],
|
||||
"fileMatch": ["/.smartconfig.json"],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
373
changelog.md
@@ -1,5 +1,378 @@
|
||||
# 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
|
||||
|
||||
- Removed deprecated PDF components and related demos/styles: ts_web/elements/00group-media/dees-pdf/* and ts_web/elements/00group-media/dees-pdf-preview/*
|
||||
- Removed exports for dees-pdf and dees-pdf-preview from ts_web/elements/00group-media/index.ts (public API removal)
|
||||
- Dependency upgrades: @design.estate/dees-domtools → ^2.3.9, apexcharts → ^5.10.3, lucide → ^0.577.0, @fortawesome/* → ^7.2.0
|
||||
- DevDependency upgrades: @git.zone/tsbuild → ^4.3.0, @git.zone/tsbundle → ^2.9.1, @git.zone/tstest → ^3.3.2, @git.zone/tswatch → ^3.2.5, @types/node → ^25.3.5
|
||||
- Updated ts_web/services/versions.ts to align CDN/version constants (apexcharts, tiptap → 2.27.2, fontawesome)
|
||||
|
||||
## 2026-02-24 - 3.43.3 - fix(dees-table)
|
||||
use lucide icon identifier for Search action in dees-table
|
||||
|
||||
- Replaced iconName 'magnifyingGlass' with 'lucide:Search' in ts_web/elements/00group-dataview/dees-table/dees-table.ts
|
||||
- Updates the icon identifier for the header 'Search' action; no functional behavior changed
|
||||
|
||||
## 2026-02-21 - 3.43.2 - fix(dees-chart-log)
|
||||
avoid duplicate log entries, optimize incremental updates, enforce maxEntries, and respect filters when writing logs
|
||||
|
||||
- Prevent property-bound logEntries from duplicating entries already present in logBuffer by deduplicating on timestamp|message
|
||||
- Call updateMetrics() when replaying or appending log entries so metrics stay accurate
|
||||
- Skip processing entirely when the incoming logEntries array is unchanged
|
||||
- Optimize append-only updates by writing only the new tail entries instead of full re-render
|
||||
- Enforce maxEntries when appending to the logBuffer to maintain buffer size
|
||||
- Respect filterMode and searchQuery when deciding whether to write appended entries to the terminal
|
||||
|
||||
## 2026-02-21 - 3.43.1 - fix(dees-chart-log)
|
||||
replay buffered log entries when terminal becomes ready and sync logEntries updates to re-render filtered logs
|
||||
|
||||
- Replay entries stored in logBuffer after terminal initialization to avoid losing entries that arrived early
|
||||
- Add updated() lifecycle hook to copy logEntries into logBuffer and call reRenderFilteredLogs when terminalReady
|
||||
- Call super.updated(changedProperties) to preserve base class behavior
|
||||
|
||||
## 2026-02-17 - 3.43.0 - feat(dees-form)
|
||||
add layout styles to dees-form and standardize demo input grouping
|
||||
|
||||
- Add static CSS to dees-form: default column layout with gap and support for [horizontal-layout] (row wrapping, alignment and gap).
|
||||
- Remove inline <style> from dees-form render to centralize styling.
|
||||
- Simplify dees-input-base styles by removing host margins and making spacing container-driven.
|
||||
- Update multiple demo files to wrap related inputs in a new .input-group container and include .input-group CSS for consistent vertical spacing.
|
||||
|
||||
## 2026-02-16 - 3.42.2 - fix(dees-chart-area)
|
||||
add ApexAxisChartSeries type to dees-chart-area component to improve typing for ApexCharts series data
|
||||
|
||||
- Introduces ApexAxisChartSeries type alias with support for number arrays, [x,y] tuples, and object {x,y,...} series entries
|
||||
- Type-only change — no runtime or API behavior modified
|
||||
- File changed: ts_web/elements/00group-chart/dees-chart-area/component.ts
|
||||
|
||||
## 2026-02-16 - 3.42.1 - fix(dees-table)
|
||||
Guard against undefined action.type in dees-table by using optional chaining and update several dependencies
|
||||
|
||||
- Use optional chaining (action.type?.includes(...)) in ts_web/elements/.../dees-table.ts to prevent runtime errors when action.type is undefined
|
||||
- Bump dependency apexcharts from ^5.3.6 to ^5.5.0
|
||||
- Bump dependency lucide from ^0.563.0 to ^0.564.0
|
||||
- Bump devDependency @git.zone/tswatch from ^3.0.1 to ^3.1.0
|
||||
- Bump devDependency @types/node from ^25.0.10 to ^25.2.3
|
||||
|
||||
## 2026-02-02 - 3.42.0 - feat(dees-form-submit)
|
||||
forward button properties to internal dees-button, use property bindings, add demo and styles
|
||||
|
||||
- Added forwarded properties: type, size, icon, iconPosition (with defaults) and preserved text/status/disabled
|
||||
- Changed template to use property bindings (.prop) for dees-button instead of string attributes
|
||||
- Switched internal event handler to listen for dees-button's @clicked event (was @click)
|
||||
- Added component styles (:host display and dees-button width:100%) and improved layout
|
||||
- Expanded demo with multiple usage examples (basic, icons, types, sizes, states, and a form context)
|
||||
|
||||
## 2026-02-02 - 3.41.6 - fix(dees-simple-appdash)
|
||||
respect selectedView when loading initial view, falling back to the first tab
|
||||
|
||||
- firstUpdated now loads this.selectedView if set, otherwise loads the first view tab
|
||||
- Prevents always loading the first tab and preserves a previously selected view on initial render
|
||||
|
||||
## 2026-02-01 - 3.41.5 - fix(dees-service-lib-loader)
|
||||
prevent horizontal scrollbar by offsetting xterm WidthCache measurement container
|
||||
|
||||
- Injects additional CSS into DeesServiceLibLoader to move xterm.js WidthCache measurement div off-screen horizontally (selector: body > div[style*="top: -50000px"][style*="width: 50000px"])
|
||||
- Fixes root cause where xterm creates a large-width measurement container (width: 50000px) on document.body that expands scrollWidth and causes a horizontal scrollbar
|
||||
- Change applied in ts_web/services/DeesServiceLibLoader.ts by concatenating the fix CSS into the injected stylesheet
|
||||
|
||||
## 2026-01-29 - 3.41.4 - fix()
|
||||
no changes
|
||||
|
||||
- No files changed in this commit; no code or documentation modified
|
||||
- No release required
|
||||
|
||||
## 2026-01-29 - 3.41.3 - fix(dees-pdf-viewer)
|
||||
use in-memory PDF data for download and print; add robust print wrapper, cleanup and error handling
|
||||
|
||||
- Download and Print now use pdfDocument.getData() to create Blob URLs so in-memory PDFs (pdf.js) can be saved/printed.
|
||||
- Print flow now opens an HTML wrapper with an iframe to allow onafterprint handling, auto-close, popup-fallback and timed cleanup of Blob URLs.
|
||||
- Added try/catch logging, URL.revokeObjectURL calls and safety timeouts to avoid resource leaks.
|
||||
- Removed context menu items that relied on the raw PDF URL (Open in New Tab, Copy PDF URL); Download/Print actions now await the async handlers.
|
||||
|
||||
## 2026-01-28 - 3.41.2 - fix(dees-pdf-viewer)
|
||||
account for devicePixelRatio when setting canvas dimensions and scale 2D context to render crisp PDF pages and thumbnails on high-DPI displays
|
||||
|
||||
- Multiply canvas.width and canvas.height by window.devicePixelRatio (dpr) and use Math.floor to set the actual pixel buffer size
|
||||
- Call ctx.scale(dpr, dpr) so drawing is rendered at device pixels while keeping CSS display size unchanged
|
||||
- Apply the same high-DPI adjustments to both main page rendering and thumbnail generation
|
||||
- Keep canvas.style.width and canvas.style.height set to viewport dimensions to preserve layout
|
||||
|
||||
## 2026-01-27 - 3.41.1 - fix(dataview-codebox)
|
||||
fix dees-dataview codebox layout to ensure full-height, proper flex behavior and scrolling; bump internal dependencies
|
||||
|
||||
- Updated CSS in ts_web/elements/00group-dataview/dees-dataview-codebox/dees-dataview-codebox.ts: added height:100%, box-sizing, display:flex and flex-direction:column on container, set flex-shrink on header elements, made code grid overflow:auto with flex:1 and min-height:0 to prevent overflow issues.
|
||||
- Bumped dependencies in package.json: @design.estate/dees-domtools from ^2.3.7 to ^2.3.8 and @design.estate/dees-element from ^2.1.5 to ^2.1.6.
|
||||
- Non-breaking visual/layout fix — suitable for a patch release.
|
||||
|
||||
## 2026-01-27 - 3.41.0 - feat(docs)
|
||||
document new media & tile components and expand Workspace/IDE component docs; update component count to 90+
|
||||
|
||||
- Updated README component count from 70+ to 90+.
|
||||
- Added Media & Tile components documentation (DeesTilePdf, DeesTileImage, DeesTileAudio, DeesTileVideo, DeesTileNote, DeesTileFolder, DeesPreview, DeesPdfViewer, DeesImageViewer, DeesAudioViewer, DeesVideoViewer).
|
||||
- Expanded Workspace/IDE documentation and introduced workspace subcomponents (DeesWorkspace, DeesWorkspaceMonaco, DeesWorkspaceDiffEditor, DeesWorkspaceFiletree, DeesWorkspaceTerminal, DeesWorkspaceTerminalPreview, DeesWorkspaceMarkdown, DeesWorkspaceMarkdownoutlet, DeesWorkspaceBottombar).
|
||||
- Enhanced Contextmenu docs to demonstrate nested submenus and programmatic API usage.
|
||||
- Added ITileFolderItem interface example for tile folder items.
|
||||
|
||||
## 2026-01-27 - 3.40.0 - feat(dees-tile)
|
||||
unify tile badge styling and markup; replace component-specific badge classes with shared tile-badge classes and update related imports/tests
|
||||
|
||||
- Add shared badge styles: .tile-badge-corner, .tile-badge-topright, .tile-badge and layout rules in dees-tile-shared/styles.ts
|
||||
- Update tile components (audio, video, image, folder, note, pdf) to use new badge markup and remove duplicated badge CSS
|
||||
- Shift badge positioning when a tile label is present (.tile-container:has(.tile-label))
|
||||
- Remove older per-component badge rules (duration-badge, item-count-badge, dimension-badge, note-language/note-line-indicator, preview-page-indicator) and replace with unified classes
|
||||
- Update tests to import dees-contextmenu and dees-dashboardgrid from new 00group-overlay / 00group-layout paths to reflect folder reorganization
|
||||
|
||||
## 2026-01-27 - 3.39.1 - fix(dees-tile-note)
|
||||
use horizontal pointer position to scroll note body by computing percentage from clientX and element width instead of clientY and height
|
||||
|
||||
- Changed ts_web/elements/00group-media/dees-tile-note/component.ts: use x = e.clientX - rect.left and percentage = x / rect.width to drive scrollTop calculation instead of using vertical coordinates
|
||||
- Fixes incorrect scroll mapping where vertical mouse position was used for horizontal scrolling interaction
|
||||
|
||||
## 2026-01-27 - 3.39.0 - feat(components)
|
||||
add large set of new UI components and demos, reorganize groups, and bump a few dependencies
|
||||
|
||||
- Add Media viewers: dees-image-viewer, dees-audio-viewer, dees-video-viewer, DeesPdf/DeesPdfViewer and related PDF utilities (CanvasPool, PdfManager) and demos
|
||||
- Introduce dees-preview composite that auto-detects content and delegates to appropriate viewers
|
||||
- New Tile system (DeesTileBase) and tile components: dees-tile-pdf, dees-tile-image, dees-tile-audio, dees-tile-video, dees-tile-note, dees-tile-folder plus demos
|
||||
- Add dashboard/grid system: dees-dashboardgrid with drag/resize, collision resolution, layout helpers, demos and utilities
|
||||
- Data view additions: dees-table with lucene-like query parser, column utilities, and dees-statsgrid enhancements (including cpuCores visualization)
|
||||
- New feedback & overlay components: dees-toast, dees-actionbar, dees-progressbar, dees-spinner, dees-badge and supporting demos/interfaces
|
||||
- Many layout and utility components added or improved: dees-panel, dees-chips, dees-heading, dees-label, dees-pagination, dees-stepper, and associated demos
|
||||
- Refactor project structure: components organized into 00group-* directories and demo metadata migrated from demoGroup to demoGroups (string[]); many import path updates to match new grouping
|
||||
- Deprecations/notes: dees-pdf-preview and legacy dees-pdf marked as deprecated in favor of new viewers/tiles
|
||||
- Dependency bumps in package.json: @design.estate/dees-wcctools -> ^3.8.0, @git.zone/tstest -> ^3.1.8
|
||||
|
||||
## 2026-01-26 - 3.38.0 - feat(appui)
|
||||
add app shell and bottom bar APIs, new input components, and update README component listings and docs
|
||||
|
||||
- README: updated components count (80+ → 70+), reorganized categories (added App Shell / Pre-built Templates, renamed Workspace/IDE) and improved typography
|
||||
- Document listing: added DeesActionbar, DeesInputToggle, DeesInputCode, DeesAppuiBottombar and other component entries
|
||||
- Menu item interface: added closeable and onClose properties for dismissible menu items
|
||||
- IViewDefinition: expanded content type to accept element constructors and async content, added badgeVariant and cache flags
|
||||
- New interfaces added: IBottomBarWidget, IBottomBarAction, IViewActivationContext to support bottom bar widgets/actions and view activation context
|
||||
|
||||
## 2026-01-25 - 3.37.1 - fix(editor)
|
||||
fix monaco/editor layout, update dev deps, simplify watch script and remove Playwright snapshots
|
||||
|
||||
- Make dees-input-code host and inner wrappers flex so Monaco editor grows to fill available height (set flex, min-height and height:100% where needed).
|
||||
- Set dees-workspace-monaco and editor-wrapper to stretch so the embedded Monaco editor fills the component.
|
||||
- Bump dev dependencies: @git.zone/tsbuild -> ^4.1.2, @git.zone/tsbundle -> ^2.8.3, @git.zone/tstest -> ^3.1.7, @git.zone/tswatch -> ^3.0.1; also bump @types/node -> ^25.0.10 and lucide -> ^0.563.0.
|
||||
- Change npm script: watch from "tswatch element" to "tswatch" and add @git.zone/tswatch preset in npmextra.json.
|
||||
- Add .playwright-mcp/ to .gitignore and remove many Playwright screenshot/test artifact PNGs to keep repo tidy.
|
||||
|
||||
## 2026-01-13 - 3.37.0 - feat(dees-button,dees-statsgrid)
|
||||
add unified icon property and icon-position support to dees-button; add partition and disk tile types to dees-statsgrid
|
||||
|
||||
- dees-button: introduce icon (string) and iconPosition ('left'|'right') properties; extractLightDom() migrates legacy <dees-icon> slotted usage into properties and supports icon-only buttons
|
||||
- dees-button: render left/right icon, hide text for icon-only size; preserve backward compatibility for old type mappings
|
||||
- Demo updates: convert iconFA attributes to new icon syntax (e.g. 'fa:plus', 'lucide:Search') and add new icon-via-property examples and event logging
|
||||
- dees-statsgrid: add IPartitionData and IDiskData interfaces and new tile types 'partition' and 'disk' with rendering, styles and thresholds (usage, filesystem, mountpoint, capacity, iops, health)
|
||||
- dees-statsgrid.demo: add Disk & Storage demo panel with sample partition and disk tiles and configuration notes
|
||||
- misc: added/updated many demo images and metadata to reflect new icon/tile features
|
||||
|
||||
## 2026-01-12 - 3.36.0 - feat(dees-chart-log)
|
||||
add xterm search addon support and enhance chart log demo with structured/raw (Docker-like) logs and themeable styles
|
||||
|
||||
- Add IXtermSearchAddon and IXtermSearchAddonBundle types and integrate xterm-addon-search loading in DeesServiceLibLoader with caching and preload support
|
||||
- Expose new types in services index and add xtermAddonSearch version to CDN_VERSIONS
|
||||
- Enhance dees-chart-log demo: separate structured and raw (docker) log panels, add Docker/ANSI log templates, start/stop controls for each simulation, and raw log writing
|
||||
- Switch demo styling to cssManager theme-aware CSS, and import css helpers from dees-element
|
||||
- Add many .playwright-mcp PNG assets used by demos/tests
|
||||
|
||||
## 2026-01-12 - 3.35.1 - fix(dees-statsgrid)
|
||||
center CPU core bars when they occupy less than ~66% of the tile and switch bar fills to absolute positioning for correct alignment and smoother transitions
|
||||
|
||||
- Add .cpu-cores-bars.centered CSS rule to horizontally center the bars when appropriate.
|
||||
- Compute shouldCenter by estimating max bars width (cores * 24px + gaps) and comparing to estimated tile content width (accounts for columnSpan, minTileWidth, gap and ~32px padding), using a 66.6% threshold.
|
||||
- Change .cpu-core-bar-container to position: relative and .cpu-core-bar-fill to position: absolute (bottom:0; left:0; right:0) to ensure correct vertical alignment and smoother height transitions.
|
||||
|
||||
## 2026-01-12 - 3.35.0 - feat(dees-statsgrid)
|
||||
add cpuCores tile type with column spanning, rendering, demo and docs
|
||||
|
||||
- Introduce ICpuCore and coresData on IStatsTile to represent per-core usage
|
||||
- Implement renderCpuCores with responsive vertical bars, color thresholds (low/medium/high) and average usage header
|
||||
- Add columnSpan support for tiles and apply it to grid items (style grid-column: span N)
|
||||
- Add demo entries (8/16/32 core examples), Randomize grid action and updated demo layout
|
||||
- Document StatsGrid enhancements in readme.hints.md (usage examples and available tile types)
|
||||
- Bump devDependencies (@git.zone/tsbuild, @git.zone/tsbundle, @types/node), simplify build script to use tsbundle, and add @git.zone/tsbundle config in npmextra.json
|
||||
|
||||
## 2026-01-12 - 3.34.1 - fix(deps)
|
||||
move @design.estate/dees-wcctools from devDependencies to dependencies
|
||||
|
||||
- Promoted @design.estate/dees-wcctools@^3.7.1 to runtime dependencies so it will be installed in production builds.
|
||||
|
||||
## 2026-01-07 - 3.34.0 - feat(dees-input-toggle)
|
||||
Add DeesInputToggle component (toggle switch) with demo and exports; integrate into inputs and DeesForm
|
||||
|
||||
- New UI input component: ts_web/elements/00group-input/dees-input-toggle/dees-input-toggle.ts — toggle switch with pointer drag, keyboard support, value syncing and DeesInputBase integration.
|
||||
- Interactive demo added: ts_web/elements/00group-input/dees-input-toggle/dees-input-toggle.demo.ts demonstrating usage, batch operations and event handling.
|
||||
- Module exports updated: added ts_web/elements/00group-input/dees-input-toggle/index.ts and exported from ts_web/elements/00group-input/index.ts.
|
||||
- DeesForm integration: imported DeesInputToggle and added it to the form components array and input union types in ts_web/elements/00group-form/dees-form/dees-form.ts
|
||||
|
||||
## 2026-01-06 - 3.33.0 - feat(dees-statsgrid)
|
||||
add multiPercentage tile type to stats grid
|
||||
|
||||
- Add new 'multiPercentage' type to IStatsTile (percentages: [{label, value, color?}])
|
||||
- Implement renderMultiPercentage() to render up to 3 percentage items with label, value and colored progress bars
|
||||
- Add CSS styles for multi-percentage layout, bars, labels and values
|
||||
- Update demo to replace 'Error Rate' tile with a 'Resource Usage' multiPercentage example (CPU, Memory, Disk)
|
||||
- Change is additive and backward-compatible with existing tile types
|
||||
|
||||
## 2026-01-04 - 3.32.0 - feat(demo)
|
||||
add demoGroup metadata to components and update related dependencies
|
||||
|
||||
- Add public static demoGroup properties to many components to categorize demos (groups added: App UI, Button, Chart, Data View, Form, Input, PDF, Simple, Workspace).
|
||||
- Bump package deps/devDeps: @design.estate/dees-domtools -> ^2.3.7, @design.estate/dees-element -> ^2.1.5, @design.estate/dees-wcctools -> ^3.7.1.
|
||||
- Clean up package.json deps (removed duplicate entries and removed 'lit' dependency).
|
||||
- Refactor dees-pdf-viewer to use consolidated directives import (directives.keyed and directives.repeat) instead of separate keyed/repeat imports.
|
||||
|
||||
## 2026-01-04 - 3.31.0 - feat(dees-input-list)
|
||||
enhance drag-and-drop reordering for dees-input-list and migrate tests to chromium runner
|
||||
|
||||
- Add rich drag state to dees-input-list: dragStartY, dragCurrentY, targetIndex, itemHeight and originalItemRects for accurate hit detection.
|
||||
- Introduce bound global drag handlers and centralized global drag end/cleanup logic (handleGlobalDragOver / handleGlobalDragEnd).
|
||||
- Improve drag visuals and animations: 'dragging', 'move-up', 'move-down' transforms, box-shadow, and smoother transitions; prevent hover styling while dragging.
|
||||
- Move reorder logic away from per-item drop to global drag end to avoid race/positioning issues and ensure consistent reflow and cleanup.
|
||||
- Migrate many browser test files to chromium-specific variants (added *.chromium.ts) and remove duplicate browser test counterparts.
|
||||
|
||||
## 2026-01-04 - 3.30.1 - fix(dees-statsgrid)
|
||||
refine spacing, sizing, and colors in dees-statsgrid for a tighter, more compact appearance
|
||||
|
||||
- Reduce global spacing and sizing variables (grid-gap 16→12, tile-padding 24→16, header-spacing 16→12, content-min-height 48→40, description-spacing 12→8, border-radius 8→6).
|
||||
- Adjust typographic scale (value-font-size 30→26, unit-font-size 16→14, label-font-size 13→12, title-font-size 14→13).
|
||||
- Switch color tokens to neutral hex values and tighten hover/box-shadow (tile border and backgrounds updated from HSL to hex, hover bg to #fafafa/#0d0d0d, border-color and shadow reduced).
|
||||
- Downsize graphical elements: gauge and SVG dimensions (width 140→120, height 80→70), stroke-widths 8→6, radius 48→40.
|
||||
- Slim down percentage bar and trend visuals (percentage bar height 8→6, border-radius 4→3, trend stroke-width 2→1.5, trend fill moved to RGBA).
|
||||
- No functional or API changes — purely visual/CSS and SVG adjustments.
|
||||
|
||||
## 2026-01-03 - 3.30.0 - feat(appui)
|
||||
add dees-appui-bottombar component with config, programmatic API, demo and docs
|
||||
|
||||
- Adds a new dees-appui-bottombar web component (ts_web/elements/00group-appui/dees-appui-bottombar/) implementing widget and action management (add/update/remove/get/clear).
|
||||
- Introduces bottom bar types and API in ts_web/elements/interfaces/appconfig.ts (IBottomBarWidget, IBottomBarAction, IBottomBarConfig, IBottomBarAPI) and extends the app config/type to include bottomBar and bottomBar APIs.
|
||||
- Integrates the bottom bar into dees-appui: imports and registers component, renders conditionally, exposes bottomBar proxy API, visibility controls (set/getBottomBarVisible), and wires initial config to populate widgets/actions.
|
||||
- Updates layout/styles (reduces main grid height to account for 24px fixed bottom bar and adds bottombar-hidden attribute handling) and exports component from the appui index.
|
||||
- Adds interactive demos (dees-appui-bottombar.demo.ts and integration demo) and documents usage and API in readme.hints.md.
|
||||
|
||||
## 2026-01-03 - 3.29.3 - fix(elements/appui)
|
||||
prevent scroll chaining on app UI components by adding overscroll-behavior: contain
|
||||
|
||||
- Added CSS overscroll-behavior: contain to activity log, main menu, secondary menu, profile dropdown, and tabs components to prevent scroll chaining and unintended body scrolling on touch/trackpad.
|
||||
- Styling-only change; no public API or behavioral changes beyond scroll handling.
|
||||
- Bump patch version from 3.29.2 to 3.29.3.
|
||||
|
||||
## 2026-01-03 - 3.29.2 - fix(dees-appui)
|
||||
set min-height: 0 on .maingrid > dees-appui-maincontent to prevent layout overflow in flex container
|
||||
|
||||
|
||||
9
license
@@ -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.
|
||||
|
||||
43
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@design.estate/dees-catalog",
|
||||
"version": "3.29.2",
|
||||
"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",
|
||||
@@ -8,49 +8,48 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "tstest test/ --web --verbose --timeout 30 --logfile",
|
||||
"build": "tsbuild tsfolders --allowimplicitany && tsbundle element --production --bundler esbuild",
|
||||
"watch": "tswatch element",
|
||||
"build": "tsbuild tsfolders --allowimplicitany && tsbundle",
|
||||
"watch": "tswatch",
|
||||
"buildDocs": "tsdoc",
|
||||
"postinstall": "node scripts/update-monaco-version.cjs"
|
||||
},
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@design.estate/dees-domtools": "^2.3.6",
|
||||
"@design.estate/dees-element": "^2.1.3",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
||||
"@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",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
||||
"@push.rocks/smarti18n": "^1.0.4",
|
||||
"@push.rocks/smartpromise": "^4.2.0",
|
||||
"@push.rocks/smartstring": "^4.1.0",
|
||||
"@tempfix/webcontainer__api": "1.6.1",
|
||||
"@tiptap/core": "^2.23.0",
|
||||
"@tiptap/extension-link": "^2.23.0",
|
||||
"@tiptap/extension-text-align": "^2.23.0",
|
||||
"@tiptap/extension-typography": "^2.23.0",
|
||||
"@tiptap/extension-underline": "^2.23.0",
|
||||
"@tiptap/starter-kit": "^2.23.0",
|
||||
"@tsclass/tsclass": "^9.3.0",
|
||||
"@tempfix/webcontainer__api": "1.6.1",
|
||||
"apexcharts": "^5.3.6",
|
||||
"@tsclass/tsclass": "^9.5.0",
|
||||
"apexcharts": "^5.10.4",
|
||||
"highlight.js": "11.11.1",
|
||||
"ibantools": "^4.5.1",
|
||||
"lit": "^3.3.1",
|
||||
"lucide": "^0.562.0",
|
||||
"lucide": "^0.577.0",
|
||||
"monaco-editor": "0.55.1",
|
||||
"pdfjs-dist": "^4.10.38",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-fit": "^0.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@design.estate/dees-wcctools": "^3.4.0",
|
||||
"@git.zone/tsbuild": "^4.0.2",
|
||||
"@git.zone/tsbundle": "^2.6.3",
|
||||
"@git.zone/tstest": "^3.1.4",
|
||||
"@git.zone/tswatch": "^2.3.13",
|
||||
"@push.rocks/projectinfo": "^5.0.2",
|
||||
"@types/node": "^25.0.3"
|
||||
"@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/**/*",
|
||||
@@ -61,7 +60,7 @@
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
".smartconfig.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
|
||||
6682
pnpm-lock.yaml
generated
309
readme.hints.md
@@ -800,4 +800,311 @@ html`
|
||||
- **External Router Support**: Integrate with Angular Router or other frameworks
|
||||
- **State Persistence**: Save/restore collapsed menus, selections, and current view
|
||||
- **View-specific Menus**: Each view can define its own secondary menu and tabs
|
||||
- **Full Backward Compatibility**: Existing code continues to work
|
||||
- **Full Backward Compatibility**: Existing code continues to work
|
||||
|
||||
## AppUI Bottom Bar (2026-01-03)
|
||||
|
||||
Added a new `dees-appui-bottombar` component similar to `dees-workspace-bottombar`, providing a 24px fixed-height status bar at the bottom of the app shell.
|
||||
|
||||
### Features:
|
||||
- **Generic status widgets**: Configurable widgets with icon, label, status colors, loading spinner
|
||||
- **App-specific actions**: Quick action buttons with icons and tooltips
|
||||
- **Always visible**: Fixed 24px height at the bottom of the app
|
||||
- **Status colors**: idle, active (blue), success (green), warning (yellow), error (red)
|
||||
- **Context menus**: Widgets can have right-click context menus
|
||||
|
||||
### New Interfaces (in `interfaces/appconfig.ts`):
|
||||
|
||||
```typescript
|
||||
interface IBottomBarWidget {
|
||||
id: string;
|
||||
iconName?: string;
|
||||
label?: string;
|
||||
status?: 'idle' | 'active' | 'success' | 'warning' | 'error';
|
||||
tooltip?: string;
|
||||
loading?: boolean;
|
||||
onClick?: () => void;
|
||||
contextMenuItems?: IBottomBarContextMenuItem[];
|
||||
position?: 'left' | 'right';
|
||||
order?: number;
|
||||
}
|
||||
|
||||
interface IBottomBarAction {
|
||||
id: string;
|
||||
iconName: string;
|
||||
tooltip?: string;
|
||||
onClick: () => void | Promise<void>;
|
||||
disabled?: boolean;
|
||||
position?: 'left' | 'right';
|
||||
}
|
||||
|
||||
interface IBottomBarConfig {
|
||||
visible?: boolean;
|
||||
widgets?: IBottomBarWidget[];
|
||||
actions?: IBottomBarAction[];
|
||||
}
|
||||
```
|
||||
|
||||
### Usage via configure():
|
||||
|
||||
```typescript
|
||||
const config: IAppConfig = {
|
||||
// ... other config
|
||||
bottomBar: {
|
||||
visible: true,
|
||||
widgets: [
|
||||
{
|
||||
id: 'status',
|
||||
iconName: 'lucide:activity',
|
||||
label: 'System Online',
|
||||
status: 'success',
|
||||
tooltip: 'All systems operational',
|
||||
onClick: () => console.log('Status clicked'),
|
||||
},
|
||||
{
|
||||
id: 'notifications',
|
||||
iconName: 'lucide:bell',
|
||||
label: '3 notifications',
|
||||
status: 'warning',
|
||||
position: 'left',
|
||||
},
|
||||
{
|
||||
id: 'version',
|
||||
iconName: 'lucide:gitBranch',
|
||||
label: 'v1.2.3',
|
||||
position: 'right',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
id: 'terminal',
|
||||
iconName: 'lucide:terminal',
|
||||
tooltip: 'Open Terminal',
|
||||
position: 'right',
|
||||
onClick: () => console.log('Terminal clicked'),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
### Programmatic API:
|
||||
|
||||
```typescript
|
||||
// Add/update/remove widgets
|
||||
appui.bottomBar.addWidget({ id: 'status', ... });
|
||||
appui.bottomBar.updateWidget('status', { status: 'error', label: 'Error!' });
|
||||
appui.bottomBar.removeWidget('status');
|
||||
appui.bottomBar.clearWidgets();
|
||||
|
||||
// Add/remove actions
|
||||
appui.bottomBar.addAction({ id: 'refresh', iconName: 'lucide:refreshCw', ... });
|
||||
appui.bottomBar.removeAction('refresh');
|
||||
appui.bottomBar.clearActions();
|
||||
|
||||
// Visibility control
|
||||
appui.setBottomBarVisible(false);
|
||||
appui.getBottomBarVisible();
|
||||
```
|
||||
|
||||
### Files:
|
||||
- `ts_web/elements/00group-appui/dees-appui-bottombar/dees-appui-bottombar.ts` - Main component
|
||||
- `ts_web/elements/00group-appui/dees-appui-bottombar/dees-appui-bottombar.demo.ts` - Demo
|
||||
- `ts_web/elements/interfaces/appconfig.ts` - New interfaces added
|
||||
|
||||
## Media Components (2026-01-26)
|
||||
|
||||
New media viewer components and a unified preview composite component.
|
||||
|
||||
### Directory: `ts_web/elements/00group-media/`
|
||||
|
||||
#### dees-image-viewer
|
||||
- Image display with zoom, pan, fit, and download controls
|
||||
- Properties: `src`, `alt`, `fit` ('contain'|'cover'|'actual'), `showToolbar`
|
||||
- Features: mouse wheel zoom, click-drag pan, double-click toggle, checkerboard transparency background
|
||||
- Toolbar matches PDF viewer pattern (48px height, 32px buttons, 16px icons, 6px border-radius)
|
||||
|
||||
#### dees-audio-viewer
|
||||
- Audio player with waveform visualization via Web Audio API
|
||||
- Properties: `src`, `title`, `artist`, `showWaveform`, `autoplay`, `loop`
|
||||
- Features: canvas waveform rendering, play/pause, seek, volume control, mute toggle, loop toggle
|
||||
- Uses `HTMLAudioElement` for playback, `AudioContext.decodeAudioData` for waveform data
|
||||
|
||||
#### dees-video-viewer
|
||||
- Video player with custom overlay controls
|
||||
- Properties: `src`, `poster`, `showControls`, `autoplay`, `loop`, `muted`
|
||||
- Features: custom controls bar with gradient, seekbar, volume slider, fullscreen toggle, auto-hide controls, 16:9 aspect ratio
|
||||
|
||||
### dees-preview (Composite Component)
|
||||
- Auto-detects content type and delegates to the appropriate viewer
|
||||
- Directory: `ts_web/elements/dees-preview/`
|
||||
- Properties: `url`, `file` (File object), `base64`, `textContent`, `contentType` (override), `language`, `mimeType`, `filename`, `showToolbar`, `showFilename`
|
||||
- Content type detection priority: explicit override → MIME type → file extension → fallback
|
||||
- Renders: image→DeesImageViewer, pdf→DeesPdfViewer, code→DeesDataviewCodebox, audio→DeesAudioViewer, video→DeesVideoViewer, text→pre, unknown→placeholder
|
||||
- Header bar with file type icon, filename, and type badge
|
||||
|
||||
### dees-dataview-codebox modification
|
||||
- Removed `<dees-windowcontrols>` elements from the appbar (Step 1 of the plan)
|
||||
- Now shows clean centered filename title bar without fake window buttons
|
||||
|
||||
### Icon Sizing Convention
|
||||
- All `dees-icon` elements in buttons need explicit `font-size: 16px` CSS rule
|
||||
- Toolbar buttons: 32px × 32px, border-radius: 6px
|
||||
- Placeholder/error icons: `font-size: 32px`
|
||||
- Pattern: `.button-class dees-icon { font-size: 16px; }`
|
||||
|
||||
## Tile Component System (2026-01-27)
|
||||
|
||||
A family of 200×260px content preview cards with a shared abstract base class. All tiles support lazy loading (IntersectionObserver with 200px margin), hover lift effect, click events, loading/error states, and three sizes (small: 150×195, default: 200×260, large: 250×325).
|
||||
|
||||
### Architecture
|
||||
|
||||
- **DeesTileBase** (`dees-tile-shared/DeesTileBase.ts`) — Abstract base class extending DeesElement
|
||||
- Common properties: `clickable`, `loading`, `error`, `size`, `label`
|
||||
- IntersectionObserver lazy loading via `onBecameVisible()` hook
|
||||
- Click dispatch via `tile-click` CustomEvent (detail from `getTileClickDetail()`)
|
||||
- Subclasses implement `renderTileContent(): TemplateResult`
|
||||
|
||||
### Components
|
||||
|
||||
| Tag | Class | Description |
|
||||
|-----|-------|-------------|
|
||||
| `dees-tile-pdf` | `DeesTilePdf` | PDF page thumbnail with hover-to-browse pages. Canvas-rendered via PDF.js/CanvasPool. |
|
||||
| `dees-tile-image` | `DeesTileImage` | Image thumbnail with `object-fit: cover`, dimension detection on load |
|
||||
| `dees-tile-audio` | `DeesTileAudio` | Music icon + mini waveform (AudioContext decode), duration badge |
|
||||
| `dees-tile-video` | `DeesTileVideo` | Auto-captured first frame, duration badge, hover muted auto-preview |
|
||||
| `dees-tile-note` | `DeesTileNote` | First ~12 lines of text in monospace, gradient fade, optional language badge |
|
||||
| `dees-tile-folder` | `DeesTileFolder` | 2×2 grid of mini-previews (thumbnails or type icons), item count badge |
|
||||
|
||||
### Deprecations
|
||||
|
||||
- `dees-pdf-preview` → Use `dees-tile-pdf` instead. Old tag still works as a thin wrapper with console warning.
|
||||
- `dees-pdf` deprecation comment updated to reference `DeesTilePdf`.
|
||||
|
||||
### File Structure
|
||||
|
||||
All tile components live in `ts_web/elements/00group-media/dees-tile-*/`:
|
||||
- `component.ts` — Main component class
|
||||
- `demo.ts` — Demo function
|
||||
- `index.ts` — Re-export
|
||||
- `styles.ts` — (PDF tile only) Component-specific styles
|
||||
- Shared base: `dees-tile-shared/{DeesTileBase,styles,index}.ts`
|
||||
|
||||
### Interface: ITileFolderItem
|
||||
```typescript
|
||||
interface ITileFolderItem {
|
||||
type: 'pdf' | 'image' | 'audio' | 'video' | 'note' | 'folder' | 'unknown';
|
||||
thumbnailSrc?: string;
|
||||
name: string;
|
||||
}
|
||||
```
|
||||
|
||||
## StatsGrid Enhancements (2026-01-12)
|
||||
|
||||
### Column Spanning
|
||||
|
||||
Tiles can now span multiple columns using the `columnSpan` property. This is useful for wider visualizations like the CPU cores tile.
|
||||
|
||||
```typescript
|
||||
const tile: IStatsTile = {
|
||||
id: 'wide-tile',
|
||||
title: 'Wide Tile',
|
||||
value: 100,
|
||||
type: 'cpuCores',
|
||||
columnSpan: 2, // Spans 2 columns
|
||||
coresData: [...]
|
||||
};
|
||||
```
|
||||
|
||||
Note: On smaller screens where only 1 column fits, tiles will automatically fall back to single column width.
|
||||
|
||||
### CPU Cores Tile Type
|
||||
|
||||
New tile type `cpuCores` for visualizing multi-core CPU usage with vertical bars:
|
||||
|
||||
```typescript
|
||||
interface ICpuCore {
|
||||
id: string | number;
|
||||
usage: number; // 0-100
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const cpuTile: IStatsTile = {
|
||||
id: 'cpu-cores',
|
||||
title: 'CPU Cores',
|
||||
value: 0, // Not used, avg is calculated from coresData
|
||||
type: 'cpuCores',
|
||||
icon: 'lucide:cpu',
|
||||
columnSpan: 2, // Recommended for 8+ cores
|
||||
coresData: [
|
||||
{ id: 0, usage: 45, label: '0' },
|
||||
{ id: 1, usage: 72, label: '1' },
|
||||
// ... more cores
|
||||
],
|
||||
description: 'Intel i7 - 8 cores'
|
||||
};
|
||||
```
|
||||
|
||||
Features:
|
||||
- Vertical bars showing individual core usage
|
||||
- Color-coded: green (<50%), yellow (50-80%), red (>80%)
|
||||
- Shows average usage in header
|
||||
- Core labels shown for 16 or fewer cores
|
||||
- Tooltips show exact usage per core
|
||||
- Responsive: bars flex to fill available width
|
||||
|
||||
### Available Tile Types:
|
||||
- `number` - Simple numeric display
|
||||
- `gauge` - Semi-circular gauge with thresholds
|
||||
- `percentage` - Progress bar (0-100%)
|
||||
- `trend` - Sparkline with recent data
|
||||
- `text` - Text value display
|
||||
- `multiPercentage` - Multiple progress bars
|
||||
- `cpuCores` - Vertical bar visualization for CPU cores
|
||||
|
||||
## Component Group Taxonomy (2026-01-27)
|
||||
|
||||
All components are organized into `00group-*` directories with 14 groups visible in the wcctools sidebar. The `demoGroups` property (plural, `string[]`) replaces the old `demoGroup` (singular). Components can belong to multiple groups.
|
||||
|
||||
### Group Directories
|
||||
| Directory | Group Name | Count |
|
||||
|-----------|-----------|-------|
|
||||
| `00group-appui` | App UI | 10 |
|
||||
| `00group-button` | Button | 3 |
|
||||
| `00group-chart` | Chart | 2 |
|
||||
| `00group-dataview` | Data View | 4 |
|
||||
| `00group-feedback` | Feedback | 6 |
|
||||
| `00group-form` | Form | 2 |
|
||||
| `00group-input` | Input | 18 |
|
||||
| `00group-layout` | Layout | 7 |
|
||||
| `00group-media` | Media | 14 (viewers + PDF + tiles) |
|
||||
| `00group-overlay` | Overlay | 4 |
|
||||
| `00group-simple` | Simple | 3 |
|
||||
| `00group-utility` | Utility | 5 |
|
||||
| `00group-workspace` | Workspace | 9 |
|
||||
| `00group-runtime` | (internal) | - |
|
||||
|
||||
### Multi-Group Components
|
||||
Some components appear in multiple groups via `demoGroups = ['Primary', 'Secondary']`:
|
||||
- `dees-chart-log`: Chart, Workspace
|
||||
- `dees-dataview-codebox`: Data View, Workspace
|
||||
- `dees-input-code`: Input, Workspace
|
||||
- `dees-input-wysiwyg`: Input, Workspace
|
||||
- `dees-form-submit`: Form, Button
|
||||
- `dees-preview`: Media, Data View
|
||||
- `dees-pdf*` / `dees-tile-pdf`: Media, PDF
|
||||
- `dees-stepper`: Layout, Form
|
||||
- `dees-label`: Layout, Input
|
||||
- `dees-toast`: Feedback, Overlay
|
||||
- `dees-actionbar`: Feedback, Overlay
|
||||
|
||||
### Import Conventions
|
||||
- Within same group: `import '../sibling-component/file.js'`
|
||||
- Cross-group (from depth-2): `import '../../00group-X/component/file.js'`
|
||||
- Shared utilities: `import '../../00plugins.js'`, `import '../../00theme.js'`, etc.
|
||||
|
||||
### Key Notes
|
||||
- The old `demoGroup` property (singular, string) is fully removed
|
||||
- All 79 components with demos use `demoGroups` (plural, string[])
|
||||
- `00group-pdf` no longer exists; PDF components are in `00group-media`
|
||||
- `dees-search` and `dees-tooltip` remain standalone (no demos)
|
||||
670
readme.md
@@ -1,6 +1,6 @@
|
||||
# @design.estate/dees-catalog
|
||||
|
||||
A comprehensive web components library built with TypeScript and LitElement, providing **80+ production-ready UI components** for building modern web applications with consistent design and behavior. 🚀
|
||||
A comprehensive web components library built with TypeScript and LitElement, providing **90+ production-ready UI components** for building modern web applications with consistent design and behavior. 🚀
|
||||
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://lit.dev/)
|
||||
@@ -11,12 +11,15 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🎨 **Consistent Design System** - Beautiful, cohesive components following modern UI/UX principles
|
||||
- 🌙 **Dark/Light Theme Support** - All components automatically adapt to your theme
|
||||
- ⌨️ **Keyboard Accessible** - Full keyboard navigation and ARIA support
|
||||
- 📱 **Responsive** - Mobile-first design that works across all screen sizes
|
||||
- 🔧 **TypeScript-First** - Fully typed APIs with excellent IDE support
|
||||
- 🧩 **Modular** - Use only what you need, tree-shakeable architecture
|
||||
- 🎨 **Consistent Design System** — Beautiful, cohesive components following modern UI/UX principles
|
||||
- 🌙 **Dark/Light Theme Support** — All components automatically adapt to your theme
|
||||
- ⌨️ **Keyboard Accessible** — Full keyboard navigation and ARIA support
|
||||
- 📱 **Responsive** — Mobile-first design that works across all screen sizes
|
||||
- 🔧 **TypeScript-First** — Fully typed APIs with excellent IDE support
|
||||
- 🧩 **Modular** — Use only what you need, tree-shakeable architecture
|
||||
- 🏗️ **Full App Shell** — `dees-appui` provides a complete application framework with menus, routing, activity log, and bottom bar
|
||||
- 🎬 **Media Components** — Rich tile-based previews for PDFs, images, audio, video, notes, and folders
|
||||
- 💻 **IDE Workspace** — Full workspace component with Monaco editor, file tree, terminal, and diff viewer
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
@@ -52,16 +55,17 @@ For developers working on this library, please refer to the [UI Components Playb
|
||||
|
||||
| Category | Components |
|
||||
|----------|------------|
|
||||
| **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols) |
|
||||
| **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputList`](#deesinputlist), [`DeesInputProfilepicture`](#deesinputprofilepicture), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesFormSubmit`](#deesformsubmit) |
|
||||
| **Layout** | [`DeesAppui`](#deesappui), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
|
||||
| **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination) |
|
||||
| **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), [`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) |
|
||||
| **Development** | [`DeesEditor`](#deeseditor), [`DeesEditorMarkdown`](#deeseditormarkdown), [`DeesEditorMarkdownoutlet`](#deeseditormarkdownoutlet), [`DeesTerminal`](#deesterminal), [`DeesUpdater`](#deesupdater) |
|
||||
| **Theming** | [`DeesTheme`](#deestheme) |
|
||||
| **Auth & Utilities** | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) |
|
||||
| **Workspace / IDE** | [`DeesWorkspace`](#deesworkspace), [`DeesWorkspaceMonaco`](#deesworkspacemonaco), [`DeesWorkspaceDiffEditor`](#deesworkspacediffeditor), [`DeesWorkspaceFiletree`](#deesworkspacefiletree), [`DeesWorkspaceTerminal`](#deesworkspaceterminal), [`DeesWorkspaceTerminalPreview`](#deesworkspaceterminalpreview), [`DeesWorkspaceMarkdown`](#deesworkspacemarkdown), [`DeesWorkspaceMarkdownoutlet`](#deesworkspacemarkdownoutlet), [`DeesWorkspaceBottombar`](#deesworkspacebottombar) |
|
||||
| **Theming** | [`DeesTheme`](#deestheme), [`DeesUpdater`](#deesupdater) |
|
||||
| **Pre-built Templates** | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) |
|
||||
| **Shopping** | [`DeesShoppingProductcard`](#deesshoppingproductcard) |
|
||||
|
||||
---
|
||||
@@ -117,14 +121,14 @@ Interactive chips/tags with selection capabilities.
|
||||
Display icons from FontAwesome and Lucide icon libraries with library prefixes.
|
||||
|
||||
```typescript
|
||||
// FontAwesome icons - use 'fa:' prefix
|
||||
// FontAwesome icons — use 'fa:' prefix
|
||||
<dees-icon
|
||||
icon="fa:check" // FontAwesome icon with fa: prefix
|
||||
iconSize="24" // Size in pixels
|
||||
color="#22c55e" // Optional: custom color
|
||||
></dees-icon>
|
||||
|
||||
// Lucide icons - use 'lucide:' prefix
|
||||
// Lucide icons — use 'lucide:' prefix
|
||||
<dees-icon
|
||||
icon="lucide:menu" // Lucide icon with lucide: prefix
|
||||
iconSize="24" // Size in pixels
|
||||
@@ -134,7 +138,7 @@ Display icons from FontAwesome and Lucide icon libraries with library prefixes.
|
||||
|
||||
// Legacy API (deprecated but still supported)
|
||||
<dees-icon
|
||||
iconFA="check" // Without prefix - assumes FontAwesome
|
||||
iconFA="check" // Without prefix — assumes FontAwesome
|
||||
></dees-icon>
|
||||
```
|
||||
|
||||
@@ -287,6 +291,18 @@ Window control buttons (minimize, maximize, close) for desktop-like applications
|
||||
></dees-windowcontrols>
|
||||
```
|
||||
|
||||
#### `DeesActionbar`
|
||||
Floating action bar for contextual actions.
|
||||
|
||||
```typescript
|
||||
<dees-actionbar
|
||||
.actions=${[
|
||||
{ icon: 'lucide:save', label: 'Save', action: () => handleSave() },
|
||||
{ icon: 'lucide:trash', label: 'Delete', action: () => handleDelete() }
|
||||
]}
|
||||
></dees-actionbar>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Form Components
|
||||
@@ -331,6 +347,18 @@ Checkbox input component for boolean values.
|
||||
></dees-input-checkbox>
|
||||
```
|
||||
|
||||
#### `DeesInputToggle`
|
||||
Toggle switch component for boolean on/off states.
|
||||
|
||||
```typescript
|
||||
<dees-input-toggle
|
||||
key="darkMode"
|
||||
label="Enable Dark Mode"
|
||||
.value=${true}
|
||||
@change=${handleToggle}
|
||||
></dees-input-toggle>
|
||||
```
|
||||
|
||||
#### `DeesInputDropdown`
|
||||
Dropdown selection component with search and filtering capabilities.
|
||||
|
||||
@@ -658,6 +686,19 @@ Advanced block-based editor with slash commands and rich content blocks.
|
||||
- Collaborative editing ready
|
||||
- Extensible block types
|
||||
|
||||
#### `DeesInputCode`
|
||||
Code input component for editing source code with syntax highlighting.
|
||||
|
||||
```typescript
|
||||
<dees-input-code
|
||||
key="snippet"
|
||||
label="Code Snippet"
|
||||
.value=${codeString}
|
||||
language="typescript"
|
||||
@change=${handleCodeChange}
|
||||
></dees-input-code>
|
||||
```
|
||||
|
||||
#### `DeesFormSubmit`
|
||||
Submit button component specifically designed for `DeesForm`.
|
||||
|
||||
@@ -670,10 +711,10 @@ Submit button component specifically designed for `DeesForm`.
|
||||
|
||||
---
|
||||
|
||||
### Layout Components
|
||||
### App Shell (Layout) Components
|
||||
|
||||
#### `DeesAppui`
|
||||
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management.
|
||||
#### `DeesAppui` 🏗️
|
||||
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, bottom bar, and view management.
|
||||
|
||||
> **Full API Documentation**: See [ts_web/elements/00group-appui/dees-appui/readme.md](./ts_web/elements/00group-appui/dees-appui/readme.md) for complete documentation including all programmatic APIs, view lifecycle hooks, and TypeScript interfaces.
|
||||
|
||||
@@ -690,7 +731,6 @@ class MyApp extends DeesElement {
|
||||
async firstUpdated() {
|
||||
this.appui = this.shadowRoot.querySelector('dees-appui');
|
||||
|
||||
// Configure with views and menu
|
||||
this.appui.configure({
|
||||
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
|
||||
views: [
|
||||
@@ -700,7 +740,13 @@ class MyApp extends DeesElement {
|
||||
mainMenu: {
|
||||
sections: [{ name: 'Main', views: ['dashboard', 'settings'] }]
|
||||
},
|
||||
defaultView: 'dashboard'
|
||||
defaultView: 'dashboard',
|
||||
bottomBar: {
|
||||
visible: true,
|
||||
widgets: [
|
||||
{ id: 'status', iconName: 'lucide:activity', label: 'Online', status: 'success' }
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -710,43 +756,123 @@ class MyApp extends DeesElement {
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **Configure API**: Single `configure()` method for complete app setup
|
||||
- **View Management**: Automatic view caching, lazy loading, and lifecycle hooks
|
||||
- **Programmatic APIs**: Full control over AppBar, Main Menu, Secondary Menu, Content Tabs, and Activity Log
|
||||
- **View Lifecycle Hooks**: `onActivate()`, `onDeactivate()`, and `canDeactivate()` for view components
|
||||
- **Hash-based Routing**: Automatic URL synchronization with view navigation
|
||||
- **RxJS Observables**: `viewChanged$` and `viewLifecycle$` for reactive programming
|
||||
- **TypeScript-first**: Typed `IViewActivationContext` passed to views on activation
|
||||
- **Activity Log Toggle**: Built-in appbar button to show/hide activity panel with entry count badge
|
||||
**Architecture Overview:**
|
||||
|
||||
**Programmatic APIs include:**
|
||||
- `navigateToView(viewId, params?)` - Navigate between views
|
||||
- `setAppBarMenus()`, `setBreadcrumbs()`, `setUser()` - Control the app bar
|
||||
- `setMainMenu()`, `setMainMenuSelection()`, `setMainMenuBadge()` - Control main navigation
|
||||
- `setMainMenuCollapsed()`, `setMainMenuVisible()` - Control main menu visibility
|
||||
- `setSecondaryMenu()`, `setSecondaryMenuCollapsed()`, `setSecondaryMenuVisible()` - Control secondary menu
|
||||
- `setContentTabs()`, `setContentTabsVisible()` - Control view-specific UI
|
||||
- `activityLog.add()`, `activityLog.addMany()`, `activityLog.clear()` - Manage activity entries
|
||||
- `setActivityLogVisible()`, `toggleActivityLog()`, `getActivityLogVisible()` - Control activity panel
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ AppBar (dees-appui-appbar) │
|
||||
│ ├── Menus (File, Edit, View...) │
|
||||
│ ├── Breadcrumbs │
|
||||
│ ├── User Profile + Dropdown │
|
||||
│ └── Activity Log Toggle │
|
||||
├─────────────┬───────────────────────────────────┬───────────────────┤
|
||||
│ Main Menu │ Content Area │ Activity Log │
|
||||
│ (collapsed/ │ ├── Content Tabs │ (slide panel) │
|
||||
│ expanded) │ │ (closable, from tables/lists)│ │
|
||||
│ │ └── View Container │ │
|
||||
│ ┌─────────┐ │ └── Active View │ │
|
||||
│ │ 🏠 Home │ ├─────────────────────────────────┐ │ │
|
||||
│ │ 📁 Files│ │ Secondary Menu │ │ │
|
||||
│ │ ⚙ Set.. │ │ ├── Collapsible Groups │ │ │
|
||||
│ │ │ │ │ ├── Tabs / Actions │ │ │
|
||||
│ └─────────┘ │ │ ├── Filters / Links │ │ │
|
||||
│ │ │ └── Dividers / Headers │ │ │
|
||||
├─────────────┴──┴───────────────────────────────┴───────────────────┤
|
||||
│ Bottom Bar (dees-appui-bottombar) — 24px status bar │
|
||||
│ ├── Status widgets (left/right) │
|
||||
│ └── Action buttons (left/right) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Configuration (`IAppConfig`):**
|
||||
|
||||
**View Visibility Control:**
|
||||
```typescript
|
||||
// In your view's onActivate hook
|
||||
onActivate(context: IViewActivationContext) {
|
||||
// Hide secondary menu for a fullscreen view
|
||||
context.appui.setSecondaryMenuVisible(false);
|
||||
|
||||
// Hide content tabs
|
||||
context.appui.setContentTabsVisible(false);
|
||||
|
||||
// Collapse main menu to give more space
|
||||
context.appui.setMainMenuCollapsed(true);
|
||||
interface IAppConfig {
|
||||
branding?: { logoIcon?: string; logoText?: string };
|
||||
appBar?: IAppBarConfig;
|
||||
views: IViewDefinition[];
|
||||
mainMenu?: IMainMenuConfig;
|
||||
defaultView?: string;
|
||||
activityLog?: IActivityLogConfig;
|
||||
bottomBar?: IBottomBarConfig;
|
||||
onViewChange?: (viewId: string, view: IViewDefinition) => void;
|
||||
onSearch?: (query: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- 🔧 **Configure API** — Single `configure()` method for complete app setup
|
||||
- 📄 **View Management** — Automatic view caching, lazy loading, and lifecycle hooks (`onActivate`, `onDeactivate`, `canDeactivate`)
|
||||
- 🧭 **Hash-based Routing** — Automatic URL synchronization with view navigation and parameterized routes
|
||||
- 📊 **Activity Log** — Slide-out panel with stacked entries, date grouping, search, and filtering
|
||||
- 📌 **Bottom Status Bar** — Configurable widgets and actions with status colors and loading states
|
||||
- 🎯 **RxJS Observables** — `viewChanged$` and `viewLifecycle$` for reactive programming
|
||||
- 🏷️ **TypeScript-first** — Typed `IViewActivationContext` passed to views on activation
|
||||
|
||||
**Programmatic APIs:**
|
||||
|
||||
| Area | Methods |
|
||||
|------|---------|
|
||||
| **Navigation** | `navigateToView(viewId, params?)`, `getCurrentView()`, `getViewRegistry()` |
|
||||
| **App Bar** | `setAppBarMenus()`, `updateAppBarMenu()`, `setBreadcrumbs()`, `setUser()`, `setProfileMenuItems()`, `setSearchVisible()`, `onSearch()`, `setWindowControlsVisible()` |
|
||||
| **Main Menu** | `setMainMenu()`, `updateMainMenuGroup()`, `addMainMenuItem()`, `removeMainMenuItem()`, `setMainMenuSelection()`, `setMainMenuCollapsed()`, `setMainMenuVisible()`, `setMainMenuBadge()`, `clearMainMenuBadge()` |
|
||||
| **Secondary Menu** | `setSecondaryMenu()`, `updateSecondaryMenuGroup()`, `addSecondaryMenuItem()`, `setSecondaryMenuSelection()`, `setSecondaryMenuCollapsed()`, `setSecondaryMenuVisible()`, `clearSecondaryMenu()` |
|
||||
| **Content Tabs** | `setContentTabs()`, `addContentTab()`, `removeContentTab()`, `selectContentTab()`, `getSelectedContentTab()`, `setContentTabsVisible()`, `setContentTabsAutoHide()` |
|
||||
| **Activity Log** | `activityLog.add()`, `activityLog.addMany()`, `activityLog.clear()`, `activityLog.getEntries()`, `activityLog.filter()`, `activityLog.search()`, `setActivityLogVisible()`, `toggleActivityLog()`, `getActivityLogVisible()` |
|
||||
| **Bottom Bar** | `bottomBar.addWidget()`, `bottomBar.updateWidget()`, `bottomBar.removeWidget()`, `bottomBar.getWidget()`, `bottomBar.clearWidgets()`, `bottomBar.addAction()`, `bottomBar.removeAction()`, `bottomBar.clearActions()`, `setBottomBarVisible()`, `getBottomBarVisible()` |
|
||||
| **Observables** | `viewChanged$`, `viewLifecycle$` |
|
||||
|
||||
**View Lifecycle Hooks:**
|
||||
|
||||
```typescript
|
||||
import { DeesElement, customElement } from '@design.estate/dees-element';
|
||||
import type { IViewActivationContext, IViewLifecycle } from '@design.estate/dees-catalog';
|
||||
|
||||
@customElement('my-settings-view')
|
||||
class MySettingsView extends DeesElement implements IViewLifecycle {
|
||||
// Called when view becomes visible
|
||||
async onActivate(context: IViewActivationContext) {
|
||||
const { appui, viewId, params } = context;
|
||||
|
||||
// Set view-specific secondary menu
|
||||
appui.setSecondaryMenu({
|
||||
heading: 'Settings',
|
||||
groups: [{ name: 'Options', items: [...] }]
|
||||
});
|
||||
|
||||
// Control visibility of other shell parts
|
||||
appui.setContentTabsVisible(false);
|
||||
appui.setSecondaryMenuVisible(true);
|
||||
}
|
||||
|
||||
// Called when navigating away
|
||||
onDeactivate() { /* cleanup */ }
|
||||
|
||||
// Return false or a message string to block navigation
|
||||
canDeactivate(): boolean | string {
|
||||
if (this.hasUnsavedChanges) return 'You have unsaved changes. Leave anyway?';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Secondary Menu Item Types:**
|
||||
|
||||
The secondary menu supports **8 distinct item types** for building rich contextual sidebars:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| **Tab** (default) | Selectable item that stays highlighted |
|
||||
| **Action** | Executes on click without staying selected (blue styling) |
|
||||
| **Filter** | Checkbox toggle for filtering |
|
||||
| **MultiFilter** | Collapsible multi-select filter box |
|
||||
| **Divider** | Visual separator line |
|
||||
| **Header** | Non-interactive section label |
|
||||
| **Link** | Opens an external URL |
|
||||
| **Danger Action** | Red-styled action with optional confirmation |
|
||||
|
||||
#### `DeesAppuiMainmenu`
|
||||
Main navigation menu component for application-wide navigation.
|
||||
Main navigation menu component for application-wide navigation. Supports collapsed (icon-only) mode.
|
||||
|
||||
```typescript
|
||||
<dees-appui-mainmenu
|
||||
@@ -759,12 +885,12 @@ Main navigation menu component for application-wide navigation.
|
||||
]
|
||||
}
|
||||
]}
|
||||
collapsed // Optional: show collapsed version
|
||||
collapsed // Optional: show collapsed icon-only version
|
||||
></dees-appui-mainmenu>
|
||||
```
|
||||
|
||||
#### `DeesAppuiSecondarymenu`
|
||||
Secondary navigation component for sub-section selection with collapsible groups and badges.
|
||||
Secondary navigation component for sub-section selection with collapsible groups, badges, and 8 item types.
|
||||
|
||||
```typescript
|
||||
<dees-appui-secondarymenu
|
||||
@@ -806,28 +932,12 @@ Professional application bar component with hierarchical menus, breadcrumb navig
|
||||
.menuItems=${[
|
||||
{
|
||||
name: 'File',
|
||||
action: async () => {}, // No-op for parent menu items
|
||||
action: async () => {},
|
||||
submenu: [
|
||||
{
|
||||
name: 'New File',
|
||||
shortcut: 'Cmd+N',
|
||||
iconName: 'file-plus',
|
||||
action: async () => handleNewFile()
|
||||
},
|
||||
{
|
||||
name: 'Open...',
|
||||
shortcut: 'Cmd+O',
|
||||
iconName: 'folder-open',
|
||||
action: async () => handleOpen()
|
||||
},
|
||||
{ divider: true }, // Menu separator
|
||||
{
|
||||
name: 'Save',
|
||||
shortcut: 'Cmd+S',
|
||||
iconName: 'save',
|
||||
action: async () => handleSave(),
|
||||
disabled: true // Disabled state
|
||||
}
|
||||
{ name: 'New File', shortcut: 'Cmd+N', iconName: 'file-plus', action: async () => handleNewFile() },
|
||||
{ name: 'Open...', shortcut: 'Cmd+O', iconName: 'folder-open', action: async () => handleOpen() },
|
||||
{ divider: true },
|
||||
{ name: 'Save', shortcut: 'Cmd+S', iconName: 'save', action: async () => handleSave(), disabled: true }
|
||||
]
|
||||
}
|
||||
]}
|
||||
@@ -849,12 +959,12 @@ Professional application bar component with hierarchical menus, breadcrumb navig
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **Hierarchical Menu System** - Top-level menus with dropdown submenus, icons, and keyboard shortcuts
|
||||
- **Keyboard Navigation** - Full keyboard support (Tab, Arrow keys, Enter, Escape)
|
||||
- **Breadcrumb Navigation** - Customizable breadcrumb trail with click events
|
||||
- **User Account Section** - Avatar with status indicator and profile dropdown
|
||||
- **Activity Log Toggle** - Button with badge count to show/hide activity panel
|
||||
- **Accessibility** - Full ARIA support with menubar roles
|
||||
- **Hierarchical Menu System** — Top-level menus with dropdown submenus, icons, and keyboard shortcuts
|
||||
- **Keyboard Navigation** — Full keyboard support (Tab, Arrow keys, Enter, Escape)
|
||||
- **Breadcrumb Navigation** — Customizable breadcrumb trail with click events
|
||||
- **User Account Section** — Avatar with status indicator and profile dropdown
|
||||
- **Activity Log Toggle** — Button with badge count to show/hide activity panel
|
||||
- **Accessibility** — Full ARIA support with menubar roles
|
||||
|
||||
#### `DeesAppuiActivitylog`
|
||||
Real-time activity log panel for displaying user actions and system events.
|
||||
@@ -886,6 +996,61 @@ activityLog.search('settings'); // Search by message
|
||||
- Animated slide-in/out panel
|
||||
- Theme-aware styling
|
||||
|
||||
#### `DeesAppuiBottombar`
|
||||
A 24px fixed-height status bar at the bottom of the application shell. Supports status widgets and action buttons positioned left or right.
|
||||
|
||||
```typescript
|
||||
// Configure via DeesAppui
|
||||
appui.configure({
|
||||
bottomBar: {
|
||||
visible: true,
|
||||
widgets: [
|
||||
{
|
||||
id: 'status',
|
||||
iconName: 'lucide:activity',
|
||||
label: 'System Online',
|
||||
status: 'success', // 'idle' | 'active' | 'success' | 'warning' | 'error'
|
||||
tooltip: 'All systems operational',
|
||||
onClick: () => console.log('Status clicked'),
|
||||
},
|
||||
{
|
||||
id: 'version',
|
||||
iconName: 'lucide:gitBranch',
|
||||
label: 'v1.2.3',
|
||||
position: 'right',
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
id: 'terminal',
|
||||
iconName: 'lucide:terminal',
|
||||
tooltip: 'Open Terminal',
|
||||
position: 'right',
|
||||
onClick: () => console.log('Terminal clicked'),
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Programmatic updates
|
||||
appui.bottomBar.addWidget({ id: 'build', iconName: 'lucide:hammer', label: 'Building...', loading: true, status: 'active' });
|
||||
appui.bottomBar.updateWidget('build', { label: 'Build complete', loading: false, status: 'success' });
|
||||
appui.bottomBar.removeWidget('build');
|
||||
|
||||
appui.bottomBar.addAction({ id: 'refresh', iconName: 'lucide:refreshCw', onClick: () => location.reload() });
|
||||
appui.bottomBar.removeAction('refresh');
|
||||
|
||||
appui.setBottomBarVisible(false);
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Configurable status widgets with icons, labels, and colored status indicators
|
||||
- Loading spinner state for widgets
|
||||
- Contextual actions with icon buttons
|
||||
- Left/right positioning for both widgets and actions
|
||||
- Tooltips on hover
|
||||
- Context menu support per widget
|
||||
|
||||
#### `DeesAppuiTabs`
|
||||
Reusable tab component with horizontal/vertical layout support.
|
||||
|
||||
@@ -1007,7 +1172,7 @@ A responsive grid component for displaying statistical data with various visuali
|
||||
value: 125420,
|
||||
unit: '$',
|
||||
type: 'number',
|
||||
icon: 'faDollarSign',
|
||||
icon: 'lucide:dollarSign',
|
||||
description: '+12.5% from last month',
|
||||
color: '#22c55e'
|
||||
},
|
||||
@@ -1016,7 +1181,7 @@ A responsive grid component for displaying statistical data with various visuali
|
||||
title: 'CPU Usage',
|
||||
value: 73,
|
||||
type: 'gauge',
|
||||
icon: 'faMicrochip',
|
||||
icon: 'lucide:cpu',
|
||||
gaugeOptions: {
|
||||
min: 0, max: 100,
|
||||
thresholds: [
|
||||
@@ -1032,8 +1197,22 @@ A responsive grid component for displaying statistical data with various visuali
|
||||
value: '1.2k',
|
||||
unit: '/min',
|
||||
type: 'trend',
|
||||
icon: 'faServer',
|
||||
icon: 'lucide:server',
|
||||
trendData: [45, 52, 38, 65, 72, 68, 75, 82, 79, 85, 88, 92]
|
||||
},
|
||||
{
|
||||
id: 'cores',
|
||||
title: 'CPU Cores',
|
||||
value: 0,
|
||||
type: 'cpuCores',
|
||||
icon: 'lucide:cpu',
|
||||
columnSpan: 2,
|
||||
coresData: [
|
||||
{ id: 0, usage: 45, label: '0' },
|
||||
{ id: 1, usage: 72, label: '1' },
|
||||
{ id: 2, usage: 30, label: '2' },
|
||||
{ id: 3, usage: 88, label: '3' }
|
||||
]
|
||||
}
|
||||
]}
|
||||
.minTileWidth=${250}
|
||||
@@ -1041,6 +1220,8 @@ A responsive grid component for displaying statistical data with various visuali
|
||||
></dees-statsgrid>
|
||||
```
|
||||
|
||||
**Tile Types:** `number`, `gauge`, `percentage`, `trend`, `text`, `multiPercentage`, `cpuCores`
|
||||
|
||||
#### `DeesPagination`
|
||||
Pagination component for navigating through large datasets.
|
||||
|
||||
@@ -1056,6 +1237,146 @@ Pagination component for navigating through large datasets.
|
||||
|
||||
---
|
||||
|
||||
### Media & Tile Components 🎬
|
||||
|
||||
A rich collection of tile-based preview components for displaying media files in grids. All tiles share a consistent base class (`DeesTileBase`) with lazy loading via `IntersectionObserver`, hover interactions, click events, context menus, and three size variants (`small`, `default`, `large`).
|
||||
|
||||
All tile badges use a unified styling system with label-awareness — when a `label` is set, bottom badges automatically shift up to avoid overlapping.
|
||||
|
||||
#### `DeesTilePdf`
|
||||
PDF document tile with live page preview on hover.
|
||||
|
||||
```typescript
|
||||
<dees-tile-pdf
|
||||
pdfUrl="/documents/report.pdf"
|
||||
label="Annual Report"
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-pdf>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Renders first page as canvas preview
|
||||
- Hover to scrub through pages (mouse X position maps to page number)
|
||||
- Shows page count badge, hover page indicator
|
||||
- Detects A4/Letter vs non-standard aspect ratios
|
||||
|
||||
#### `DeesTileImage`
|
||||
Image tile with lazy loading and dimension display.
|
||||
|
||||
```typescript
|
||||
<dees-tile-image
|
||||
src="/photos/landscape.jpg"
|
||||
alt="Mountain landscape"
|
||||
label="landscape.jpg"
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-image>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Lazy loads image on scroll into view
|
||||
- Shows image dimensions on hover (e.g. "1920 × 1080")
|
||||
- Checkerboard background for transparent images
|
||||
|
||||
#### `DeesTileAudio`
|
||||
Audio file tile with waveform visualization.
|
||||
|
||||
```typescript
|
||||
<dees-tile-audio
|
||||
src="/music/track.mp3"
|
||||
title="Summer Vibes"
|
||||
artist="DJ Example"
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-audio>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Generates waveform visualization from audio data
|
||||
- Shows duration badge (e.g. "3:42")
|
||||
- Displays title and artist metadata
|
||||
- Play overlay on hover
|
||||
|
||||
#### `DeesTileVideo`
|
||||
Video tile with thumbnail capture and hover preview.
|
||||
|
||||
```typescript
|
||||
<dees-tile-video
|
||||
src="/videos/intro.mp4"
|
||||
poster="/thumbs/intro.jpg"
|
||||
label="Introduction"
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-video>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Auto-captures first frame as thumbnail (or uses provided `poster`)
|
||||
- Plays video preview on hover
|
||||
- Shows duration badge
|
||||
- Play button overlay
|
||||
|
||||
#### `DeesTileNote`
|
||||
Code/text snippet tile with syntax-aware display.
|
||||
|
||||
```typescript
|
||||
<dees-tile-note
|
||||
title="config.ts"
|
||||
language="TypeScript"
|
||||
.content=${codeString}
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-note>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Monospace font with line numbers
|
||||
- Language badge (top-right)
|
||||
- Scrollable content on hover (mouse X position controls scroll)
|
||||
- Line indicator badge while scrolling
|
||||
- Gradient fade at bottom
|
||||
|
||||
#### `DeesTileFolder`
|
||||
Folder tile with 2×2 content preview grid.
|
||||
|
||||
```typescript
|
||||
<dees-tile-folder
|
||||
name="Project Assets"
|
||||
.items=${[
|
||||
{ type: 'image', name: 'logo.png', thumbnailSrc: '/thumbs/logo.png' },
|
||||
{ type: 'pdf', name: 'spec.pdf' },
|
||||
{ type: 'audio', name: 'jingle.mp3' },
|
||||
{ type: 'video', name: 'demo.mp4' },
|
||||
]}
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-folder>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- 2×2 preview grid showing first 4 items (thumbnails or type icons)
|
||||
- Item count badge (e.g. "12 items")
|
||||
- Folder icon header with name
|
||||
- Supports: `pdf`, `image`, `audio`, `video`, `note`, `folder`, `unknown` types
|
||||
|
||||
#### `DeesPreview`
|
||||
Unified preview component that auto-selects the right tile type based on content.
|
||||
|
||||
#### `DeesPdfViewer` / `DeesPdfPreview`
|
||||
Full PDF viewing components with navigation controls.
|
||||
|
||||
#### `DeesImageViewer`
|
||||
Full-screen image viewer with zoom and pan.
|
||||
|
||||
#### `DeesAudioViewer`
|
||||
Audio playback component with waveform and controls.
|
||||
|
||||
#### `DeesVideoViewer`
|
||||
Video playback component with standard controls.
|
||||
|
||||
---
|
||||
|
||||
### Visualization Components
|
||||
|
||||
#### `DeesChartArea`
|
||||
@@ -1116,16 +1437,41 @@ DeesModal.createAndShow({
|
||||
```
|
||||
|
||||
#### `DeesContextmenu`
|
||||
Context menu component for right-click actions.
|
||||
Context menu component for right-click actions with nested submenu support.
|
||||
|
||||
```typescript
|
||||
<dees-contextmenu
|
||||
.items=${[
|
||||
{ label: 'Edit', icon: 'edit', action: () => handleEdit() },
|
||||
{ label: 'Delete', icon: 'trash', action: () => handleDelete() }
|
||||
]}
|
||||
position="right"
|
||||
></dees-contextmenu>
|
||||
// Programmatic usage
|
||||
DeesContextmenu.openContextMenuWithOptions(mouseEvent, [
|
||||
{
|
||||
name: 'Edit',
|
||||
iconName: 'lucide:edit',
|
||||
action: async () => handleEdit()
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
name: 'More Options',
|
||||
iconName: 'lucide:moreHorizontal',
|
||||
submenu: [
|
||||
{ name: 'Duplicate', iconName: 'lucide:copy', action: async () => handleDuplicate() },
|
||||
{ name: 'Archive', iconName: 'lucide:archive', action: async () => handleArchive() },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
iconName: 'lucide:trash2',
|
||||
action: async () => handleDelete()
|
||||
}
|
||||
]);
|
||||
|
||||
// Component-based (implement getContextMenuItems on any element)
|
||||
class MyComponent extends DeesElement {
|
||||
getContextMenuItems() {
|
||||
return [
|
||||
{ name: 'View Details', iconName: 'lucide:eye', action: async () => { ... } },
|
||||
{ name: 'Edit', iconName: 'lucide:edit', action: async () => { ... } },
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `DeesSpeechbubble`
|
||||
@@ -1189,7 +1535,7 @@ Progress indicator component for tracking completion status.
|
||||
Theme provider component that wraps children and provides CSS custom properties for consistent theming.
|
||||
|
||||
```typescript
|
||||
// Basic usage - wrap your app
|
||||
// Basic usage — wrap your app
|
||||
<dees-theme>
|
||||
<my-app></my-app>
|
||||
</dees-theme>
|
||||
@@ -1217,76 +1563,71 @@ Theme provider component that wraps children and provides CSS custom properties
|
||||
|
||||
---
|
||||
|
||||
### Development Components
|
||||
### Workspace / IDE Components 💻
|
||||
|
||||
#### `DeesEditor`
|
||||
Code editor component with syntax highlighting and code completion, powered by Monaco Editor.
|
||||
A full-featured IDE workspace component suite for building browser-based code editors, terminal interfaces, and documentation viewers.
|
||||
|
||||
#### `DeesWorkspace`
|
||||
Top-level workspace shell that composes editor, file tree, terminal, and bottom bar into an IDE-like layout.
|
||||
|
||||
```typescript
|
||||
<dees-editor
|
||||
<dees-workspace></dees-workspace>
|
||||
```
|
||||
|
||||
#### `DeesWorkspaceMonaco`
|
||||
Monaco Editor integration for code editing with full IntelliSense, syntax highlighting, and language support.
|
||||
|
||||
```typescript
|
||||
<dees-workspace-monaco
|
||||
.value=${code}
|
||||
@change=${handleCodeChange}
|
||||
.language=${'typescript'}
|
||||
.theme=${'vs-dark'}
|
||||
.options=${{
|
||||
lineNumbers: true,
|
||||
minimap: { enabled: true },
|
||||
autoClosingBrackets: 'always'
|
||||
}}
|
||||
></dees-editor>
|
||||
@change=${handleCodeChange}
|
||||
></dees-workspace-monaco>
|
||||
```
|
||||
|
||||
#### `DeesEditorMarkdown`
|
||||
Markdown editor component with live preview.
|
||||
#### `DeesWorkspaceDiffEditor`
|
||||
Side-by-side diff editor powered by Monaco for comparing file versions.
|
||||
|
||||
```typescript
|
||||
<dees-editor-markdown
|
||||
.value=${markdown}
|
||||
@change=${handleMarkdownChange}
|
||||
.options=${{ preview: true, toolbar: true, spellcheck: true }}
|
||||
></dees-editor-markdown>
|
||||
<dees-workspace-diff-editor
|
||||
.originalValue=${originalCode}
|
||||
.modifiedValue=${modifiedCode}
|
||||
.language=${'typescript'}
|
||||
></dees-workspace-diff-editor>
|
||||
```
|
||||
|
||||
#### `DeesEditorMarkdownoutlet`
|
||||
Markdown preview component for rendering markdown content.
|
||||
#### `DeesWorkspaceFiletree`
|
||||
File tree navigation component with expand/collapse, file icons, and selection.
|
||||
|
||||
```typescript
|
||||
<dees-editor-markdownoutlet
|
||||
.markdown=${markdownContent}
|
||||
.theme=${'github'}
|
||||
allowHtml={false}
|
||||
></dees-editor-markdownoutlet>
|
||||
<dees-workspace-filetree
|
||||
.files=${fileTreeData}
|
||||
@file-select=${handleFileSelect}
|
||||
></dees-workspace-filetree>
|
||||
```
|
||||
|
||||
#### `DeesTerminal`
|
||||
Terminal emulator component for command-line interface.
|
||||
#### `DeesWorkspaceTerminal`
|
||||
Terminal emulator component powered by xterm.js.
|
||||
|
||||
```typescript
|
||||
<dees-terminal
|
||||
.commands=${{
|
||||
'echo': (args) => `Echo: ${args.join(' ')}`,
|
||||
'help': () => 'Available commands: echo, help'
|
||||
}}
|
||||
.prompt=${'$'}
|
||||
.welcomeMessage=${'Welcome! Type "help" for available commands.'}
|
||||
></dees-terminal>
|
||||
<dees-workspace-terminal></dees-workspace-terminal>
|
||||
```
|
||||
|
||||
#### `DeesUpdater`
|
||||
Component for managing application updates and version control.
|
||||
#### `DeesWorkspaceTerminalPreview`
|
||||
Terminal with integrated preview pane for output visualization.
|
||||
|
||||
```typescript
|
||||
<dees-updater
|
||||
.currentVersion=${'1.5.2'}
|
||||
.checkInterval=${3600000}
|
||||
.autoUpdate=${false}
|
||||
@update-available=${handleUpdateAvailable}
|
||||
></dees-updater>
|
||||
```
|
||||
#### `DeesWorkspaceMarkdown`
|
||||
Markdown editor with live preview.
|
||||
|
||||
#### `DeesWorkspaceMarkdownoutlet`
|
||||
Read-only markdown renderer for documentation display.
|
||||
|
||||
#### `DeesWorkspaceBottombar`
|
||||
IDE-style bottom status bar for the workspace.
|
||||
|
||||
---
|
||||
|
||||
### Auth & Utilities Components
|
||||
### Pre-built Templates
|
||||
|
||||
#### `DeesSimpleAppdash`
|
||||
Simple application dashboard component for quick prototyping.
|
||||
@@ -1360,6 +1701,8 @@ interface IMenuItem {
|
||||
action: () => void;
|
||||
badge?: string | number;
|
||||
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||
closeable?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
// Menu group interface for organized menus
|
||||
@@ -1375,11 +1718,13 @@ interface IViewDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
iconName?: string;
|
||||
content: string | HTMLElement | (() => TemplateResult);
|
||||
content: string | (new () => HTMLElement) | (() => TemplateResult) | (() => Promise<any>);
|
||||
secondaryMenu?: ISecondaryMenuGroup[];
|
||||
contentTabs?: IMenuItem[];
|
||||
route?: string;
|
||||
badge?: string | number;
|
||||
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||
cache?: boolean;
|
||||
}
|
||||
|
||||
// Activity log entry
|
||||
@@ -1392,13 +1737,50 @@ interface IActivityEntry {
|
||||
iconName?: string;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// Bottom bar widget
|
||||
interface IBottomBarWidget {
|
||||
id: string;
|
||||
iconName?: string;
|
||||
label?: string;
|
||||
status?: 'idle' | 'active' | 'success' | 'warning' | 'error';
|
||||
tooltip?: string;
|
||||
loading?: boolean;
|
||||
onClick?: () => void;
|
||||
position?: 'left' | 'right';
|
||||
order?: number;
|
||||
}
|
||||
|
||||
// Bottom bar action button
|
||||
interface IBottomBarAction {
|
||||
id: string;
|
||||
iconName: string;
|
||||
tooltip?: string;
|
||||
onClick: () => void | Promise<void>;
|
||||
disabled?: boolean;
|
||||
position?: 'left' | 'right';
|
||||
}
|
||||
|
||||
// View activation context (passed to onActivate)
|
||||
interface IViewActivationContext {
|
||||
appui: DeesAppui;
|
||||
viewId: string;
|
||||
params?: Record<string, string>;
|
||||
}
|
||||
|
||||
// Tile folder item (for DeesTileFolder)
|
||||
interface ITileFolderItem {
|
||||
type: 'pdf' | 'image' | 'audio' | 'video' | 'note' | 'folder' | 'unknown';
|
||||
thumbnailSrc?: string;
|
||||
name: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -1410,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.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { demoFunc } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.demo.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import { demoFunc } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.demo.js';
|
||||
|
||||
tap.test('should render context menu demo', async () => {
|
||||
// Create demo container
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
|
||||
tap.test('should close all parent menus when clicking action in nested submenu', async () => {
|
||||
let actionCalled = false;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesElement, customElement, html } from '@design.estate/dees-element';
|
||||
|
||||
// Create a test element with shadow DOM
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
|
||||
tap.test('should show context menu with nested submenu', async () => {
|
||||
// Create a test element with context menu items
|
||||
@@ -3,8 +3,8 @@ import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import {
|
||||
resolveWidgetPlacement,
|
||||
collectCollisions,
|
||||
} from '../ts_web/elements/dees-dashboardgrid/layout.ts';
|
||||
import type { DashboardWidget } from '../ts_web/elements/dees-dashboardgrid/types.ts';
|
||||
} from '../ts_web/elements/00group-layout/dees-dashboardgrid/layout.ts';
|
||||
import type { DashboardWidget } from '../ts_web/elements/00group-layout/dees-dashboardgrid/types.ts';
|
||||
|
||||
tap.test('dashboardgrid does not overlap widgets after swap attempt', async () => {
|
||||
const widgets: DashboardWidget[] = [
|
||||
|
||||
79
test/test.pdf-text-selection.chromium.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||
import * as deesCatalog from '../ts_web/index.js';
|
||||
|
||||
tap.test('PDF viewer should render text layer', async () => {
|
||||
const viewer = await webhelpers.fixture(
|
||||
webhelpers.html`
|
||||
<dees-pdf-viewer
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
|
||||
initialZoom="page-fit"
|
||||
style="height: 600px; width: 100%;"
|
||||
></dees-pdf-viewer>
|
||||
`
|
||||
) as deesCatalog.DeesPdfViewer;
|
||||
|
||||
// Wait for PDF to load and render
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
await viewer.updateComplete;
|
||||
|
||||
expect(viewer.totalPages).toBeGreaterThan(0);
|
||||
|
||||
const textLayer = viewer.shadowRoot?.querySelector('.text-layer[data-page="1"]');
|
||||
expect(textLayer).toBeTruthy();
|
||||
|
||||
const textSpans = textLayer?.querySelectorAll('span');
|
||||
expect(textSpans?.length).toBeGreaterThan(0);
|
||||
console.log(`Text layer has ${textSpans?.length} spans`);
|
||||
});
|
||||
|
||||
tap.test('Text should be selectable', async () => {
|
||||
const viewer = await webhelpers.fixture(
|
||||
webhelpers.html`
|
||||
<dees-pdf-viewer
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
|
||||
initialZoom="page-fit"
|
||||
style="height: 600px; width: 100%;"
|
||||
></dees-pdf-viewer>
|
||||
`
|
||||
) as deesCatalog.DeesPdfViewer;
|
||||
|
||||
// Wait for PDF to load and render
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
const textLayer = viewer.shadowRoot?.querySelector('.text-layer[data-page="1"]');
|
||||
const firstSpan = textLayer?.querySelector('span') as HTMLElement;
|
||||
|
||||
if (firstSpan?.textContent) {
|
||||
const range = document.createRange();
|
||||
const textNode = firstSpan.firstChild;
|
||||
if (textNode) {
|
||||
range.setStart(textNode, 0);
|
||||
range.setEnd(textNode, Math.min(5, textNode.textContent?.length || 0));
|
||||
const selection = window.getSelection();
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
expect(selection?.toString().length).toBeGreaterThan(0);
|
||||
console.log('Selected text:', selection?.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('endOfContent element exists for selection boundary', async () => {
|
||||
const viewer = await webhelpers.fixture(
|
||||
webhelpers.html`
|
||||
<dees-pdf-viewer
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
|
||||
initialZoom="page-fit"
|
||||
style="height: 600px; width: 100%;"
|
||||
></dees-pdf-viewer>
|
||||
`
|
||||
) as deesCatalog.DeesPdfViewer;
|
||||
|
||||
// Wait for PDF to load and render
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
const endOfContent = viewer.shadowRoot?.querySelector('.text-layer[data-page="1"] .endOfContent');
|
||||
expect(endOfContent).toBeTruthy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
|
||||
tap.test('should change block type via context menu', async () => {
|
||||
// Create WYSIWYG editor with a paragraph
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
|
||||
tap.test('should show context menu on WYSIWYG blocks', async () => {
|
||||
// Create WYSIWYG editor
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@design.estate/dees-catalog',
|
||||
version: '3.29.2',
|
||||
version: '3.49.1',
|
||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||
import '../../dees-icon/dees-icon.js';
|
||||
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import type { IActivityEntry, IActivityLogAPI } from '../../interfaces/appconfig.js';
|
||||
import { demoFunc } from './dees-appui-activitylog.demo.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
@@ -20,6 +20,7 @@ import { themeDefaultStyles } from '../../00theme.js';
|
||||
export class DeesAppuiActivitylog extends DeesElement implements IActivityLogAPI {
|
||||
// STATIC
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE PROPERTIES
|
||||
@state()
|
||||
@@ -122,6 +123,7 @@ export class DeesAppuiActivitylog extends DeesElement implements IActivityLogAPI
|
||||
width: 100%;
|
||||
padding: 8px 0;
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: ${cssManager.bdTheme('#d4d4d4', '#333333')} transparent;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import { appuiAppbarStyles } from './styles.js';
|
||||
import { renderAppuiAppbar } from './template.js';
|
||||
|
||||
// Import required components
|
||||
import '../../dees-icon/dees-icon.js';
|
||||
import '../../dees-windowcontrols/dees-windowcontrols.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import '../../00group-utility/dees-windowcontrols/dees-windowcontrols.js';
|
||||
import '../dees-appui-profiledropdown/dees-appui-profiledropdown.js';
|
||||
|
||||
declare global {
|
||||
@@ -28,6 +28,7 @@ declare global {
|
||||
@customElement('dees-appui-appbar')
|
||||
export class DeesAppuiBar extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE PROPERTIES
|
||||
@property({ type: Array })
|
||||
@@ -121,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>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
import type { DeesAppuiBottombar } from './dees-appui-bottombar.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
|
||||
export const demoFunc = () => {
|
||||
return html`
|
||||
<dees-demowrapper>
|
||||
<style>
|
||||
.demo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
padding: 24px;
|
||||
background: #1a1a1a;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.demo-label {
|
||||
font-size: 12px;
|
||||
color: #737373;
|
||||
font-family: 'Geist Sans', sans-serif;
|
||||
}
|
||||
|
||||
.demo-bottombar-wrapper {
|
||||
border: 1px solid hsl(0 0% 20%);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<div class="demo-container">
|
||||
<div class="demo-section">
|
||||
<div class="demo-label">Bottom bar with status widgets and actions</div>
|
||||
<div class="demo-bottombar-wrapper">
|
||||
<dees-appui-bottombar
|
||||
id="demo-bottombar"
|
||||
></dees-appui-bottombar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<div class="demo-label">Controls</div>
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||||
<button onclick="addSuccessWidget()">Add Success Widget</button>
|
||||
<button onclick="addWarningWidget()">Add Warning Widget</button>
|
||||
<button onclick="addErrorWidget()">Add Error Widget</button>
|
||||
<button onclick="addLoadingWidget()">Add Loading Widget</button>
|
||||
<button onclick="addRightWidget()">Add Right Widget</button>
|
||||
<button onclick="addAction()">Add Action</button>
|
||||
<button onclick="clearAll()">Clear All</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module">
|
||||
const bottombar = document.getElementById('demo-bottombar');
|
||||
|
||||
// Wait for component to initialize
|
||||
await bottombar.updateComplete;
|
||||
|
||||
// Add initial widgets
|
||||
bottombar.addWidget({
|
||||
id: 'status',
|
||||
iconName: 'lucide:activity',
|
||||
label: 'System Online',
|
||||
status: 'success',
|
||||
tooltip: 'All systems operational',
|
||||
onClick: () => console.log('Status clicked'),
|
||||
contextMenuItems: [
|
||||
{ name: 'View Details', iconName: 'lucide:info', action: () => alert('System details') },
|
||||
{ divider: true },
|
||||
{ name: 'Refresh Status', iconName: 'lucide:refreshCw', action: () => alert('Refreshing...') },
|
||||
],
|
||||
});
|
||||
|
||||
bottombar.addWidget({
|
||||
id: 'notifications',
|
||||
iconName: 'lucide:bell',
|
||||
label: '3 notifications',
|
||||
status: 'warning',
|
||||
tooltip: 'You have unread notifications',
|
||||
onClick: () => console.log('Notifications clicked'),
|
||||
});
|
||||
|
||||
bottombar.addWidget({
|
||||
id: 'version',
|
||||
iconName: 'lucide:gitBranch',
|
||||
label: 'v1.2.3',
|
||||
tooltip: 'Current version',
|
||||
position: 'right',
|
||||
onClick: () => console.log('Version clicked'),
|
||||
});
|
||||
|
||||
// Add initial actions
|
||||
bottombar.addAction({
|
||||
id: 'settings',
|
||||
iconName: 'lucide:settings',
|
||||
tooltip: 'Settings',
|
||||
position: 'right',
|
||||
onClick: () => alert('Settings clicked'),
|
||||
});
|
||||
|
||||
bottombar.addAction({
|
||||
id: 'help',
|
||||
iconName: 'lucide:helpCircle',
|
||||
tooltip: 'Help',
|
||||
position: 'right',
|
||||
onClick: () => alert('Help clicked'),
|
||||
});
|
||||
|
||||
// Demo control functions
|
||||
let widgetCounter = 0;
|
||||
let actionCounter = 0;
|
||||
|
||||
window.addSuccessWidget = () => {
|
||||
widgetCounter++;
|
||||
bottombar.addWidget({
|
||||
id: 'success-' + widgetCounter,
|
||||
iconName: 'lucide:checkCircle',
|
||||
label: 'Success ' + widgetCounter,
|
||||
status: 'success',
|
||||
tooltip: 'Success widget',
|
||||
onClick: () => bottombar.removeWidget('success-' + widgetCounter),
|
||||
});
|
||||
};
|
||||
|
||||
window.addWarningWidget = () => {
|
||||
widgetCounter++;
|
||||
bottombar.addWidget({
|
||||
id: 'warning-' + widgetCounter,
|
||||
iconName: 'lucide:alertTriangle',
|
||||
label: 'Warning ' + widgetCounter,
|
||||
status: 'warning',
|
||||
tooltip: 'Warning widget',
|
||||
onClick: () => bottombar.removeWidget('warning-' + widgetCounter),
|
||||
});
|
||||
};
|
||||
|
||||
window.addErrorWidget = () => {
|
||||
widgetCounter++;
|
||||
bottombar.addWidget({
|
||||
id: 'error-' + widgetCounter,
|
||||
iconName: 'lucide:xCircle',
|
||||
label: 'Error ' + widgetCounter,
|
||||
status: 'error',
|
||||
tooltip: 'Error widget',
|
||||
onClick: () => bottombar.removeWidget('error-' + widgetCounter),
|
||||
});
|
||||
};
|
||||
|
||||
window.addLoadingWidget = () => {
|
||||
widgetCounter++;
|
||||
const id = 'loading-' + widgetCounter;
|
||||
bottombar.addWidget({
|
||||
id: id,
|
||||
iconName: 'lucide:loader2',
|
||||
label: 'Loading...',
|
||||
status: 'active',
|
||||
loading: true,
|
||||
tooltip: 'Loading in progress',
|
||||
});
|
||||
|
||||
// Simulate completion after 3 seconds
|
||||
setTimeout(() => {
|
||||
bottombar.updateWidget(id, {
|
||||
iconName: 'lucide:check',
|
||||
label: 'Done!',
|
||||
status: 'success',
|
||||
loading: false,
|
||||
});
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
window.addRightWidget = () => {
|
||||
widgetCounter++;
|
||||
bottombar.addWidget({
|
||||
id: 'right-' + widgetCounter,
|
||||
iconName: 'lucide:info',
|
||||
label: 'Right ' + widgetCounter,
|
||||
position: 'right',
|
||||
onClick: () => bottombar.removeWidget('right-' + widgetCounter),
|
||||
});
|
||||
};
|
||||
|
||||
window.addAction = () => {
|
||||
actionCounter++;
|
||||
bottombar.addAction({
|
||||
id: 'action-' + actionCounter,
|
||||
iconName: 'lucide:zap',
|
||||
tooltip: 'Action ' + actionCounter,
|
||||
onClick: () => {
|
||||
alert('Action ' + actionCounter + ' clicked');
|
||||
bottombar.removeAction('action-' + actionCounter);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
window.clearAll = () => {
|
||||
bottombar.clearWidgets();
|
||||
bottombar.clearActions();
|
||||
widgetCounter = 0;
|
||||
actionCounter = 0;
|
||||
};
|
||||
</script>
|
||||
</dees-demowrapper>
|
||||
`;
|
||||
};
|
||||
@@ -0,0 +1,315 @@
|
||||
import {
|
||||
DeesElement,
|
||||
type TemplateResult,
|
||||
customElement,
|
||||
html,
|
||||
css,
|
||||
cssManager,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import type {
|
||||
IBottomBarWidget,
|
||||
IBottomBarAction,
|
||||
IBottomBarAPI,
|
||||
} from '../../interfaces/appconfig.js';
|
||||
import { demoFunc } from './dees-appui-bottombar.demo.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-appui-bottombar': DeesAppuiBottombar;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-appui-bottombar')
|
||||
export class DeesAppuiBottombar extends DeesElement implements IBottomBarAPI {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE PROPERTIES
|
||||
@state()
|
||||
accessor widgets: IBottomBarWidget[] = [];
|
||||
|
||||
@state()
|
||||
accessor actions: IBottomBarAction[] = [];
|
||||
|
||||
public static styles = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 24px;
|
||||
flex-shrink: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 8px;
|
||||
gap: 4px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 94%)', 'hsl(0 0% 6%)')};
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 15%)')};
|
||||
font-size: 11px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
|
||||
}
|
||||
|
||||
.widget {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.widget:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 88%)', 'hsl(0 0% 12%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')};
|
||||
}
|
||||
|
||||
.widget dees-icon {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.widget-separator {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 80%)', 'hsl(0 0% 20%)')};
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
/* Status colors matching dees-workspace-bottombar */
|
||||
.widget.active {
|
||||
color: ${cssManager.bdTheme('hsl(210 100% 45%)', 'hsl(210 100% 60%)')};
|
||||
}
|
||||
|
||||
.widget.success {
|
||||
color: ${cssManager.bdTheme('hsl(142 70% 35%)', 'hsl(142 70% 50%)')};
|
||||
}
|
||||
|
||||
.widget.warning {
|
||||
color: ${cssManager.bdTheme('hsl(38 92% 45%)', 'hsl(38 92% 55%)')};
|
||||
}
|
||||
|
||||
.widget.error {
|
||||
color: ${cssManager.bdTheme('hsl(0 70% 50%)', 'hsl(0 70% 60%)')};
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.spinning {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 88%)', 'hsl(0 0% 12%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')};
|
||||
}
|
||||
|
||||
.action-button.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.action-button.disabled:hover {
|
||||
background: transparent;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
const leftWidgets = this.widgets
|
||||
.filter(w => w.position !== 'right')
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0));
|
||||
|
||||
const rightWidgets = this.widgets
|
||||
.filter(w => w.position === 'right')
|
||||
.sort((a, b) => (a.order || 0) - (b.order || 0));
|
||||
|
||||
const leftActions = this.actions.filter(a => a.position === 'left');
|
||||
const rightActions = this.actions.filter(a => a.position !== 'left');
|
||||
|
||||
return html`
|
||||
<div class="bottom-bar">
|
||||
<!-- Left actions -->
|
||||
${leftActions.map(action => this.renderAction(action))}
|
||||
|
||||
<!-- Left widgets -->
|
||||
${leftWidgets.map((widget, index) => html`
|
||||
${index > 0 || leftActions.length > 0 ? html`<div class="widget-separator"></div>` : ''}
|
||||
${this.renderWidget(widget)}
|
||||
`)}
|
||||
|
||||
<div class="spacer"></div>
|
||||
|
||||
<!-- Right widgets -->
|
||||
${rightWidgets.map((widget, index) => html`
|
||||
${this.renderWidget(widget)}
|
||||
${index < rightWidgets.length - 1 || rightActions.length > 0 ? html`<div class="widget-separator"></div>` : ''}
|
||||
`)}
|
||||
|
||||
<!-- Right actions -->
|
||||
${rightActions.map(action => this.renderAction(action))}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderWidget(widget: IBottomBarWidget): TemplateResult {
|
||||
const statusClass = widget.status && widget.status !== 'idle' ? widget.status : '';
|
||||
const iconName = widget.iconName
|
||||
? (widget.iconName.startsWith('lucide:') ? widget.iconName : `lucide:${widget.iconName}`)
|
||||
: '';
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="widget ${statusClass}"
|
||||
title="${widget.tooltip || ''}"
|
||||
@click=${() => widget.onClick?.()}
|
||||
@contextmenu=${(e: MouseEvent) => this.handleWidgetContextMenu(e, widget)}
|
||||
>
|
||||
${iconName ? html`
|
||||
<dees-icon
|
||||
.icon=${iconName}
|
||||
iconSize="12"
|
||||
class="${widget.loading ? 'spinning' : ''}"
|
||||
></dees-icon>
|
||||
` : ''}
|
||||
${widget.label ? html`<span>${widget.label}</span>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderAction(action: IBottomBarAction): TemplateResult {
|
||||
const iconName = action.iconName.startsWith('lucide:')
|
||||
? action.iconName
|
||||
: `lucide:${action.iconName}`;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="action-button ${action.disabled ? 'disabled' : ''}"
|
||||
title="${action.tooltip || ''}"
|
||||
@click=${() => !action.disabled && action.onClick?.()}
|
||||
>
|
||||
<dees-icon
|
||||
.icon=${iconName}
|
||||
iconSize="12"
|
||||
></dees-icon>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private async handleWidgetContextMenu(e: MouseEvent, widget: IBottomBarWidget): Promise<void> {
|
||||
if (!widget.contextMenuItems || widget.contextMenuItems.length === 0) return;
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
const menuItems: Parameters<typeof DeesContextmenu.openContextMenuWithOptions>[1] = [];
|
||||
|
||||
for (const item of widget.contextMenuItems) {
|
||||
if (item.divider) {
|
||||
menuItems.push({ divider: true });
|
||||
} else {
|
||||
menuItems.push({
|
||||
name: item.name,
|
||||
iconName: item.iconName,
|
||||
action: async () => { await item.action(); },
|
||||
disabled: item.disabled,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
await DeesContextmenu.openContextMenuWithOptions(e, menuItems);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// API METHODS (implements IBottomBarAPI)
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Add a widget to the bottom bar
|
||||
*/
|
||||
public addWidget(widget: IBottomBarWidget): void {
|
||||
// Remove existing widget with same ID if present
|
||||
this.widgets = this.widgets.filter(w => w.id !== widget.id);
|
||||
this.widgets = [...this.widgets, widget];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing widget by ID
|
||||
*/
|
||||
public updateWidget(id: string, update: Partial<IBottomBarWidget>): void {
|
||||
this.widgets = this.widgets.map(w =>
|
||||
w.id === id ? { ...w, ...update } : w
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a widget by ID
|
||||
*/
|
||||
public removeWidget(id: string): void {
|
||||
this.widgets = this.widgets.filter(w => w.id !== id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a widget by ID
|
||||
*/
|
||||
public getWidget(id: string): IBottomBarWidget | undefined {
|
||||
return this.widgets.find(w => w.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all widgets
|
||||
*/
|
||||
public clearWidgets(): void {
|
||||
this.widgets = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an action button
|
||||
*/
|
||||
public addAction(action: IBottomBarAction): void {
|
||||
this.actions = this.actions.filter(a => a.id !== action.id);
|
||||
this.actions = [...this.actions, action];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an action by ID
|
||||
*/
|
||||
public removeAction(id: string): void {
|
||||
this.actions = this.actions.filter(a => a.id !== id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all actions
|
||||
*/
|
||||
public clearActions(): void {
|
||||
this.actions = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './dees-appui-bottombar.js';
|
||||
@@ -31,6 +31,7 @@ export class DeesAppuiMaincontent extends DeesElement {
|
||||
</div>
|
||||
</dees-appui-maincontent>
|
||||
`;
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE
|
||||
@property({
|
||||
@@ -52,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,
|
||||
@@ -105,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>
|
||||
@@ -156,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;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import { demoFunc } from './dees-appui-mainmenu.demo.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
@@ -22,6 +22,7 @@ import { themeDefaultStyles } from '../../00theme.js';
|
||||
@customElement('dees-appui-mainmenu')
|
||||
export class DeesAppuiMainmenu extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE
|
||||
|
||||
@@ -45,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;
|
||||
@@ -164,6 +165,7 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overscroll-behavior: contain;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export class DeesAppuiProfileDropdown extends DeesElement {
|
||||
.isOpen=${true}
|
||||
></dees-appui-profiledropdown>
|
||||
`;
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
@property({ type: Object })
|
||||
accessor user: {
|
||||
@@ -285,6 +286,7 @@ export class DeesAppuiProfileDropdown extends DeesElement {
|
||||
max-width: calc(100vw - 32px);
|
||||
max-height: calc(100vh - 32px);
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
:host([isopen]) .dropdown {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as plugins from '../../00plugins.js';
|
||||
import * as interfaces from '../../interfaces/index.js';
|
||||
|
||||
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||
import '../../dees-icon/dees-icon.js';
|
||||
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@@ -33,6 +33,7 @@ import { themeDefaultStyles } from '../../00theme.js';
|
||||
@customElement('dees-appui-secondarymenu')
|
||||
export class DeesAppuiSecondarymenu extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE
|
||||
|
||||
@@ -201,6 +202,7 @@ export class DeesAppuiSecondarymenu extends DeesElement {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overscroll-behavior: contain;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { themeDefaultStyles } from '../../00theme.js';
|
||||
@customElement('dees-appui-tabs')
|
||||
export class DeesAppuiTabs extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE
|
||||
@property({
|
||||
@@ -40,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;
|
||||
@@ -72,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 */
|
||||
@@ -104,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;
|
||||
@@ -116,15 +191,18 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
font-size: 14px;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
overscroll-behavior: contain;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -142,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)')};
|
||||
}
|
||||
|
||||
@@ -329,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>
|
||||
`;
|
||||
@@ -351,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' : ''}`;
|
||||
@@ -404,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
|
||||
|
||||
@@ -421,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();
|
||||
});
|
||||
@@ -501,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')) {
|
||||
@@ -541,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 {
|
||||
@@ -573,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;
|
||||
@@ -585,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`;
|
||||
|
||||
@@ -3,6 +3,8 @@ import type { DeesAppui } from './dees-appui.js';
|
||||
import type { IAppConfig, IViewActivationContext } from '../../interfaces/appconfig.js';
|
||||
import type * as interfaces from '../../interfaces/index.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import '../../00group-dataview/dees-statsgrid/dees-statsgrid.js';
|
||||
import type { IStatsTile } from '../../00group-dataview/dees-statsgrid/dees-statsgrid.js';
|
||||
|
||||
// Demo view component with lifecycle hooks
|
||||
@customElement('demo-dashboard-view')
|
||||
@@ -10,7 +12,80 @@ class DemoDashboardView extends DeesElement {
|
||||
@state()
|
||||
accessor activated: boolean = false;
|
||||
|
||||
private ctx: IViewActivationContext;
|
||||
private ctx!: IViewActivationContext;
|
||||
|
||||
private statsTiles: IStatsTile[] = [
|
||||
{
|
||||
id: 'users',
|
||||
title: 'Active Users',
|
||||
value: 1234,
|
||||
type: 'number',
|
||||
icon: 'lucide:users',
|
||||
description: 'Online now',
|
||||
color: '#22c55e'
|
||||
},
|
||||
{
|
||||
id: 'api-calls',
|
||||
title: 'API Calls',
|
||||
value: 45200,
|
||||
type: 'trend',
|
||||
icon: 'lucide:activity',
|
||||
description: '+12% from last hour',
|
||||
color: '#3b82f6',
|
||||
trendData: [32000, 35000, 38000, 41000, 39000, 42000, 45200]
|
||||
},
|
||||
{
|
||||
id: 'health',
|
||||
title: 'System Health',
|
||||
value: 99.9,
|
||||
unit: '%',
|
||||
type: 'gauge',
|
||||
icon: 'lucide:heart-pulse',
|
||||
description: 'All systems operational',
|
||||
color: '#10b981',
|
||||
gaugeOptions: {
|
||||
min: 0,
|
||||
max: 100,
|
||||
thresholds: [
|
||||
{ value: 80, color: '#ef4444' },
|
||||
{ value: 95, color: '#f59e0b' },
|
||||
{ value: 100, color: '#10b981' }
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 'response',
|
||||
title: 'Avg Response',
|
||||
value: 127,
|
||||
unit: 'ms',
|
||||
type: 'number',
|
||||
icon: 'lucide:timer',
|
||||
description: '-15ms from yesterday',
|
||||
color: '#8b5cf6'
|
||||
},
|
||||
{
|
||||
id: 'resources',
|
||||
title: 'Resource Usage',
|
||||
value: '',
|
||||
type: 'multiPercentage',
|
||||
icon: 'lucide:server',
|
||||
percentages: [
|
||||
{ label: 'CPU', value: 67, color: '#3b82f6' },
|
||||
{ label: 'Memory', value: 84, color: '#8b5cf6' },
|
||||
{ label: 'Disk', value: 45, color: '#10b981' }
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'requests',
|
||||
title: 'Requests/sec',
|
||||
value: 1850,
|
||||
type: 'trend',
|
||||
icon: 'lucide:zap',
|
||||
description: 'Current throughput',
|
||||
color: '#06b6d4',
|
||||
trendData: [1200, 1400, 1350, 1600, 1750, 1680, 1850]
|
||||
}
|
||||
];
|
||||
|
||||
onActivate(context: IViewActivationContext) {
|
||||
this.ctx = context;
|
||||
@@ -83,21 +158,9 @@ class DemoDashboardView extends DeesElement {
|
||||
}
|
||||
h1 { color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 8px; }
|
||||
p { color: #737373; margin-bottom: 32px; }
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
dees-statsgrid {
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
.card {
|
||||
background: rgba(255,255,255,0.03);
|
||||
border: 1px solid rgba(255,255,255,0.08);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
}
|
||||
.card h3 { color: #fafafa; font-size: 14px; font-weight: 600; margin-bottom: 8px; }
|
||||
.metric { font-size: 32px; font-weight: 700; color: #fafafa; }
|
||||
.status { display: inline-block; padding: 2px 8px; border-radius: 9px; font-size: 12px; }
|
||||
.status.active { background: #14532d; color: #4ade80; }
|
||||
|
||||
.ctx-actions {
|
||||
margin-top: 32px;
|
||||
@@ -147,23 +210,10 @@ class DemoDashboardView extends DeesElement {
|
||||
</style>
|
||||
<h1>Dashboard</h1>
|
||||
<p>Welcome back! Here's an overview of your system.</p>
|
||||
<div class="grid">
|
||||
<div class="card">
|
||||
<h3>Active Users</h3>
|
||||
<div class="metric">1,234</div>
|
||||
<span class="status active">Online</span>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>API Calls</h3>
|
||||
<div class="metric">45.2K</div>
|
||||
<p style="color: #4ade80; font-size: 12px; margin: 0;">+12% from last hour</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>System Health</h3>
|
||||
<div class="metric">99.9%</div>
|
||||
<p style="color: #737373; font-size: 12px; margin: 0;">All systems operational</p>
|
||||
</div>
|
||||
</div>
|
||||
<dees-statsgrid
|
||||
.tiles=${this.statsTiles}
|
||||
@tile-action=${(e: CustomEvent) => console.log('Tile action:', e.detail)}
|
||||
></dees-statsgrid>
|
||||
|
||||
<div class="ctx-actions">
|
||||
<h2>Context Actions (ctx.appui)</h2>
|
||||
@@ -217,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;
|
||||
@@ -663,6 +713,44 @@ export const demoFunc = () => {
|
||||
|
||||
defaultView: 'dashboard',
|
||||
|
||||
bottomBar: {
|
||||
visible: true,
|
||||
widgets: [
|
||||
{
|
||||
id: 'status',
|
||||
iconName: 'lucide:activity',
|
||||
label: 'System Online',
|
||||
status: 'success',
|
||||
tooltip: 'All systems operational',
|
||||
onClick: () => console.log('Status clicked'),
|
||||
},
|
||||
{
|
||||
id: 'notifications',
|
||||
iconName: 'lucide:bell',
|
||||
label: '3 notifications',
|
||||
status: 'warning',
|
||||
tooltip: 'You have unread notifications',
|
||||
onClick: () => console.log('Notifications clicked'),
|
||||
},
|
||||
{
|
||||
id: 'version',
|
||||
iconName: 'lucide:gitBranch',
|
||||
label: 'v1.2.3',
|
||||
position: 'right',
|
||||
tooltip: 'Current version',
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
id: 'terminal',
|
||||
iconName: 'lucide:terminal',
|
||||
tooltip: 'Open Terminal',
|
||||
position: 'right',
|
||||
onClick: () => console.log('Terminal clicked'),
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
onViewChange: (viewId, view) => {
|
||||
console.log(`View changed to: ${viewId} (${view.name})`);
|
||||
},
|
||||
|
||||
@@ -15,6 +15,7 @@ import type { DeesAppuiMainmenu } from '../dees-appui-mainmenu/dees-appui-mainme
|
||||
import type { DeesAppuiSecondarymenu } from '../dees-appui-secondarymenu/dees-appui-secondarymenu.js';
|
||||
import type { DeesAppuiMaincontent } from '../dees-appui-maincontent/dees-appui-maincontent.js';
|
||||
import type { DeesAppuiActivitylog } from '../dees-appui-activitylog/dees-appui-activitylog.js';
|
||||
import type { DeesAppuiBottombar } from '../dees-appui-bottombar/dees-appui-bottombar.js';
|
||||
import { demoFunc } from './dees-appui.demo.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
@@ -23,6 +24,7 @@ import { ViewRegistry } from './view.registry.js';
|
||||
|
||||
// Import child components
|
||||
import '../dees-appui-appbar/index.js';
|
||||
import '../dees-appui-bottombar/dees-appui-bottombar.js';
|
||||
import '../dees-appui-mainmenu/dees-appui-mainmenu.js';
|
||||
import '../dees-appui-secondarymenu/dees-appui-secondarymenu.js';
|
||||
import '../dees-appui-maincontent/dees-appui-maincontent.js';
|
||||
@@ -37,6 +39,7 @@ declare global {
|
||||
@customElement('dees-appui')
|
||||
export class DeesAppui extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// ==========================================
|
||||
// REACTIVE OBSERVABLES (RxJS Subjects)
|
||||
@@ -140,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;
|
||||
@@ -156,6 +165,12 @@ export class DeesAppui extends DeesElement {
|
||||
@state()
|
||||
accessor activitylogElement: DeesAppuiActivitylog | undefined = undefined;
|
||||
|
||||
@state()
|
||||
accessor bottombarElement: DeesAppuiBottombar | undefined = undefined;
|
||||
|
||||
@state()
|
||||
accessor bottombarVisible: boolean = true;
|
||||
|
||||
// Current view state
|
||||
@state()
|
||||
accessor currentView: interfaces.IViewDefinition | undefined = undefined;
|
||||
@@ -179,24 +194,38 @@ export class DeesAppui extends DeesElement {
|
||||
.maingrid {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
height: calc(100% - 40px);
|
||||
height: calc(100% - 40px - 24px);
|
||||
width: 100%;
|
||||
display: grid;
|
||||
/* grid-template-columns set dynamically in template */
|
||||
grid-template-rows: 1fr;
|
||||
transition: grid-template-columns 0.3s ease;
|
||||
transition: grid-template-columns 0.3s ease, height 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:host([bottombar-hidden]) .maingrid {
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
|
||||
dees-appui-bottombar {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
/* Z-index layering for proper stacking */
|
||||
.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 {
|
||||
@@ -285,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)}
|
||||
>
|
||||
@@ -295,6 +326,9 @@ export class DeesAppui extends DeesElement {
|
||||
class="${this.activityLogVisible ? 'visible' : 'hidden'}"
|
||||
></dees-appui-activitylog>
|
||||
</div>
|
||||
${this.bottombarVisible ? html`
|
||||
<dees-appui-bottombar></dees-appui-bottombar>
|
||||
` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -305,6 +339,7 @@ export class DeesAppui extends DeesElement {
|
||||
this.secondarymenu = this.shadowRoot!.querySelector('dees-appui-secondarymenu') as DeesAppuiSecondarymenu;
|
||||
this.maincontent = this.shadowRoot!.querySelector('dees-appui-maincontent') as DeesAppuiMaincontent;
|
||||
this.activitylogElement = this.shadowRoot!.querySelector('dees-appui-activitylog') as DeesAppuiActivitylog;
|
||||
this.bottombarElement = this.shadowRoot!.querySelector('dees-appui-bottombar') as DeesAppuiBottombar;
|
||||
|
||||
// Subscribe to activity log entry changes for badge count
|
||||
if (this.activitylogElement) {
|
||||
@@ -674,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
|
||||
// ==========================================
|
||||
@@ -730,6 +779,72 @@ export class DeesAppui extends DeesElement {
|
||||
return this.activityLogVisible;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// PROGRAMMATIC API: BOTTOM BAR
|
||||
// ==========================================
|
||||
|
||||
/**
|
||||
* Get the bottom bar API for widget/action management
|
||||
*/
|
||||
public get bottomBar(): interfaces.IBottomBarAPI {
|
||||
if (!this.bottombarElement) {
|
||||
// Return a deferred API that will work after firstUpdated
|
||||
return {
|
||||
addWidget: (widget) => {
|
||||
this.updateComplete.then(() => this.bottombarElement?.addWidget(widget));
|
||||
},
|
||||
updateWidget: (id, update) => {
|
||||
this.updateComplete.then(() => this.bottombarElement?.updateWidget(id, update));
|
||||
},
|
||||
removeWidget: (id) => {
|
||||
this.updateComplete.then(() => this.bottombarElement?.removeWidget(id));
|
||||
},
|
||||
getWidget: (id) => this.bottombarElement?.getWidget(id),
|
||||
clearWidgets: () => {
|
||||
this.updateComplete.then(() => this.bottombarElement?.clearWidgets());
|
||||
},
|
||||
addAction: (action) => {
|
||||
this.updateComplete.then(() => this.bottombarElement?.addAction(action));
|
||||
},
|
||||
removeAction: (id) => {
|
||||
this.updateComplete.then(() => this.bottombarElement?.removeAction(id));
|
||||
},
|
||||
clearActions: () => {
|
||||
this.updateComplete.then(() => this.bottombarElement?.clearActions());
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
addWidget: (widget) => this.bottombarElement!.addWidget(widget),
|
||||
updateWidget: (id, update) => this.bottombarElement!.updateWidget(id, update),
|
||||
removeWidget: (id) => this.bottombarElement!.removeWidget(id),
|
||||
getWidget: (id) => this.bottombarElement!.getWidget(id),
|
||||
clearWidgets: () => this.bottombarElement!.clearWidgets(),
|
||||
addAction: (action) => this.bottombarElement!.addAction(action),
|
||||
removeAction: (id) => this.bottombarElement!.removeAction(id),
|
||||
clearActions: () => this.bottombarElement!.clearActions(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set bottom bar visibility
|
||||
*/
|
||||
public setBottomBarVisible(visible: boolean): void {
|
||||
this.bottombarVisible = visible;
|
||||
if (!visible) {
|
||||
this.setAttribute('bottombar-hidden', '');
|
||||
} else {
|
||||
this.removeAttribute('bottombar-hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bottom bar visibility state
|
||||
*/
|
||||
public getBottomBarVisible(): boolean {
|
||||
return this.bottombarVisible;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// PROGRAMMATIC API: NAVIGATION
|
||||
// ==========================================
|
||||
@@ -762,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);
|
||||
@@ -842,6 +962,23 @@ export class DeesAppui extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
// Apply bottom bar config
|
||||
if (config.bottomBar) {
|
||||
this.setBottomBarVisible(config.bottomBar.visible ?? true);
|
||||
|
||||
if (config.bottomBar.widgets) {
|
||||
config.bottomBar.widgets.forEach(widget => {
|
||||
this.bottomBar.addWidget(widget);
|
||||
});
|
||||
}
|
||||
|
||||
if (config.bottomBar.actions) {
|
||||
config.bottomBar.actions.forEach(action => {
|
||||
this.bottomBar.addAction(action);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Setup domtools.router integration
|
||||
this.setupRouterIntegration(config);
|
||||
|
||||
@@ -900,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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { html, css } from '@design.estate/dees-element';
|
||||
import '../00group-button/dees-button/dees-button.js';
|
||||
import '../dees-panel/dees-panel.js';
|
||||
import '../../00group-button/dees-button/dees-button.js';
|
||||
import '../../00group-layout/dees-panel/dees-panel.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
|
||||
export const demoFunc = () => html`
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as plugins from '../00plugins.js';
|
||||
import { zIndexRegistry } from '../00zindex.js';
|
||||
import { cssGeistFontFamily } from '../00fonts.js';
|
||||
import * as plugins from '../../00plugins.js';
|
||||
import { zIndexRegistry } from '../../00zindex.js';
|
||||
import { cssGeistFontFamily } from '../../00fonts.js';
|
||||
import {
|
||||
cssManager,
|
||||
css,
|
||||
@@ -12,13 +12,14 @@ import {
|
||||
property,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import { DeesWindowLayer } from '../dees-windowlayer/dees-windowlayer.js';
|
||||
import '../dees-icon/dees-icon.js';
|
||||
import { themeDefaultStyles } from '../00theme.js';
|
||||
import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
@customElement('dees-mobilenavigation')
|
||||
export class DeesMobilenavigation extends DeesElement {
|
||||
// STATIC
|
||||
public static demoGroups = ['App UI'];
|
||||
public static demo = () => html`
|
||||
<dees-button @click=${() => {
|
||||
DeesMobilenavigation.createAndShow([
|
||||
@@ -300,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({
|
||||
@@ -327,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');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,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);
|
||||
@@ -1,9 +1,11 @@
|
||||
// App UI Components
|
||||
export * from './dees-appui-activitylog/index.js';
|
||||
export * from './dees-appui-appbar/index.js';
|
||||
export * from './dees-appui-bottombar/index.js';
|
||||
export * from './dees-appui/index.js';
|
||||
export * from './dees-appui-maincontent/index.js';
|
||||
export * from './dees-appui-mainmenu/index.js';
|
||||
export * from './dees-appui-secondarymenu/index.js';
|
||||
export * from './dees-appui-profiledropdown/index.js';
|
||||
export * from './dees-appui-tabs/index.js';
|
||||
export * from './dees-mobilenavigation/index.js';
|
||||
|
||||
@@ -16,6 +16,7 @@ export class DeesButtonExit extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<dees-button-exit></dees-button-exit>
|
||||
`;
|
||||
public static demoGroups = ['Button'];
|
||||
|
||||
// INSTANCE
|
||||
@property({
|
||||
|
||||
@@ -21,6 +21,7 @@ declare global {
|
||||
@customElement('dees-button-group')
|
||||
export class DeesButtonGroup extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['Button'];
|
||||
|
||||
@property()
|
||||
accessor label: string = '';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { html, css, cssManager, domtools } from '@design.estate/dees-element';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import '../../dees-panel/dees-panel.js';
|
||||
import '../../00group-layout/dees-panel/dees-panel.js';
|
||||
import '../../00group-form/dees-form/dees-form.js';
|
||||
import '../../00group-form/dees-form-submit/dees-form-submit.js';
|
||||
import '../../00group-input/dees-input-text/dees-input-text.js';
|
||||
import '../../dees-icon/dees-icon.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import type { DeesButton } from '../dees-button/dees-button.js';
|
||||
|
||||
export const demoFunc = () => html`
|
||||
@@ -142,54 +142,95 @@ export const demoFunc = () => html`
|
||||
<dees-panel .title=${'3. Buttons with Icons'} .subtitle=${'Combining icons with text for enhanced visual communication'}>
|
||||
<div class="icon-row">
|
||||
<dees-button>
|
||||
<dees-icon iconFA="faPlus"></dees-icon>
|
||||
<dees-icon icon="fa:plus"></dees-icon>
|
||||
Add Item
|
||||
</dees-button>
|
||||
<dees-button type="destructive">
|
||||
<dees-icon iconFA="faTrash"></dees-icon>
|
||||
<dees-icon icon="fa:trash"></dees-icon>
|
||||
Delete
|
||||
</dees-button>
|
||||
<dees-button type="outline">
|
||||
<dees-icon iconFA="faDownload"></dees-icon>
|
||||
<dees-icon icon="lucide:Download"></dees-icon>
|
||||
Download
|
||||
</dees-button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="icon-row">
|
||||
<dees-button type="secondary" size="sm">
|
||||
<dees-icon iconFA="faCog"></dees-icon>
|
||||
<dees-icon icon="fa:gear"></dees-icon>
|
||||
Settings
|
||||
</dees-button>
|
||||
<dees-button type="ghost">
|
||||
<dees-icon iconFA="faChevronLeft"></dees-icon>
|
||||
<dees-icon icon="fa:caretLeft"></dees-icon>
|
||||
Back
|
||||
</dees-button>
|
||||
<dees-button type="ghost">
|
||||
Next
|
||||
<dees-icon iconFA="faChevronRight"></dees-icon>
|
||||
<dees-icon icon="fa:caretRight"></dees-icon>
|
||||
</dees-button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="icon-row">
|
||||
<dees-button size="icon" type="default">
|
||||
<dees-icon iconFA="faPlus"></dees-icon>
|
||||
<dees-icon icon="fa:plus"></dees-icon>
|
||||
</dees-button>
|
||||
<dees-button size="icon" type="secondary">
|
||||
<dees-icon iconFA="faCog"></dees-icon>
|
||||
<dees-icon icon="fa:gear"></dees-icon>
|
||||
</dees-button>
|
||||
<dees-button size="icon" type="outline">
|
||||
<dees-icon iconFA="faSearch"></dees-icon>
|
||||
<dees-icon icon="lucide:Search"></dees-icon>
|
||||
</dees-button>
|
||||
<dees-button size="icon" type="ghost">
|
||||
<dees-icon iconFA="faEllipsisV"></dees-icon>
|
||||
<dees-icon icon="lucide:MoreVertical"></dees-icon>
|
||||
</dees-button>
|
||||
<dees-button size="icon" type="destructive">
|
||||
<dees-icon iconFA="faTrash"></dees-icon>
|
||||
<dees-icon icon="fa:trash"></dees-icon>
|
||||
</dees-button>
|
||||
</div>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
// Track icon property button clicks
|
||||
const buttons = elementArg.querySelectorAll('dees-button');
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener('clicked', () => {
|
||||
const icon = button.getAttribute('icon') || 'none';
|
||||
const position = button.getAttribute('iconPosition') || 'left';
|
||||
console.log(`Icon property button: icon=${icon}, position=${position}`);
|
||||
});
|
||||
});
|
||||
}}>
|
||||
<dees-panel .title=${'4. Icons via Property'} .subtitle=${'Simplified icon syntax using the icon property'}>
|
||||
<div class="icon-row">
|
||||
<dees-button icon="fa:plus">Add Item</dees-button>
|
||||
<dees-button type="destructive" icon="fa:trash">Delete</dees-button>
|
||||
<dees-button type="outline" icon="lucide:Download">Download</dees-button>
|
||||
</div>
|
||||
|
||||
<div class="icon-row">
|
||||
<dees-button type="secondary" size="sm" icon="fa:gear">Settings</dees-button>
|
||||
<dees-button type="ghost" icon="fa:caretLeft">Back</dees-button>
|
||||
<dees-button type="ghost" icon="fa:caretRight" iconPosition="right">Next</dees-button>
|
||||
</div>
|
||||
|
||||
<div class="icon-row">
|
||||
<dees-button size="icon" type="default" icon="fa:plus"></dees-button>
|
||||
<dees-button size="icon" type="secondary" icon="lucide:Settings"></dees-button>
|
||||
<dees-button size="icon" type="outline" icon="lucide:Search"></dees-button>
|
||||
<dees-button size="icon" type="ghost" icon="lucide:MoreVertical"></dees-button>
|
||||
<dees-button size="icon" type="destructive" icon="fa:trash"></dees-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 16px;">
|
||||
<div class="code-snippet">
|
||||
<dees-button icon="fa:plus">Add Item</dees-button><br>
|
||||
<dees-button icon="fa:caretRight" iconPosition="right">Next</dees-button>
|
||||
</div>
|
||||
</div>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
// Demonstrate status changes
|
||||
const pendingButton = elementArg.querySelector('dees-button[status="pending"]');
|
||||
@@ -215,7 +256,7 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'4. Button States'} .subtitle=${'Different states to indicate button status and loading conditions'}>
|
||||
<dees-panel .title=${'5. Button States'} .subtitle=${'Different states to indicate button status and loading conditions'}>
|
||||
<div class="button-group">
|
||||
<dees-button status="normal">Normal</dees-button>
|
||||
<dees-button status="pending">Processing...</dees-button>
|
||||
@@ -247,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}`;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -260,7 +301,7 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'5. Event Handling'} .subtitle=${'Interactive examples with click event handling'}>
|
||||
<dees-panel .title=${'6. Event Handling'} .subtitle=${'Interactive examples with click event handling'}>
|
||||
<div class="button-group">
|
||||
<dees-button>Click Me</dees-button>
|
||||
<dees-button type="secondary" .eventDetailData=${'custom-data-123'}>
|
||||
@@ -281,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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -303,7 +344,7 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'6. Form Integration'} .subtitle=${'Buttons working within forms with automatic spacing'}>
|
||||
<dees-panel .title=${'7. Form Integration'} .subtitle=${'Buttons working within forms with automatic spacing'}>
|
||||
<dees-form>
|
||||
<dees-input-text label="Name" key="name" required></dees-input-text>
|
||||
<dees-input-text label="Email" key="email" type="email" required></dees-input-text>
|
||||
@@ -330,7 +371,7 @@ export const demoFunc = () => html`
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<dees-panel .title=${'7. Backward Compatibility'} .subtitle=${'Old button types are automatically mapped to new variants'}>
|
||||
<dees-panel .title=${'8. Backward Compatibility'} .subtitle=${'Old button types are automatically mapped to new variants'}>
|
||||
<div class="button-group">
|
||||
<dees-button type="normal">Normal → Default</dees-button>
|
||||
<dees-button type="highlighted">Highlighted → Destructive</dees-button>
|
||||
@@ -371,36 +412,36 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'8. Advanced Examples'} .subtitle=${'Complex button configurations and real-world use cases'}>
|
||||
<dees-panel .title=${'9. Advanced Examples'} .subtitle=${'Complex button configurations and real-world use cases'}>
|
||||
<div class="horizontal-group">
|
||||
<div class="vertical-group">
|
||||
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 500;">Action Group</h4>
|
||||
<dees-button type="default" size="sm">
|
||||
<dees-icon iconFA="faSave"></dees-icon>
|
||||
<dees-icon icon="lucide:Save"></dees-icon>
|
||||
Save Changes
|
||||
</dees-button>
|
||||
<dees-button type="secondary" size="sm">
|
||||
<dees-icon iconFA="faUndo"></dees-icon>
|
||||
<dees-icon icon="lucide:Undo2"></dees-icon>
|
||||
Discard
|
||||
</dees-button>
|
||||
<dees-button type="ghost" size="sm">
|
||||
<dees-icon iconFA="faQuestionCircle"></dees-icon>
|
||||
<dees-icon icon="lucide:HelpCircle"></dees-icon>
|
||||
Help
|
||||
</dees-button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="vertical-group">
|
||||
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 500;">Danger Zone</h4>
|
||||
<dees-button type="destructive" size="sm">
|
||||
<dees-icon iconFA="faTrash"></dees-icon>
|
||||
<dees-icon icon="fa:trash"></dees-icon>
|
||||
Delete Account
|
||||
</dees-button>
|
||||
<dees-button type="outline" size="sm">
|
||||
<dees-icon iconFA="faArchive"></dees-icon>
|
||||
<dees-icon icon="lucide:Archive"></dees-icon>
|
||||
Archive Data
|
||||
</dees-button>
|
||||
<dees-button type="ghost" size="sm" disabled>
|
||||
<dees-icon iconFA="faBan"></dees-icon>
|
||||
<dees-icon icon="lucide:Ban"></dees-icon>
|
||||
Not Available
|
||||
</dees-button>
|
||||
</div>
|
||||
@@ -409,8 +450,7 @@ export const demoFunc = () => html`
|
||||
<div style="margin-top: 24px;">
|
||||
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 500;">Code Example:</h4>
|
||||
<div class="code-snippet">
|
||||
<dees-button type="default" size="sm" @clicked="\${handleClick}"><br>
|
||||
<dees-icon iconFA="faSave"></dees-icon><br>
|
||||
<dees-button type="default" size="sm" icon="lucide:Save" @clicked="\${handleClick}"><br>
|
||||
Save Changes<br>
|
||||
</dees-button>
|
||||
</div>
|
||||
|
||||
@@ -23,6 +23,7 @@ declare global {
|
||||
@customElement('dees-button')
|
||||
export class DeesButton extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['Button'];
|
||||
|
||||
@property({
|
||||
reflect: true,
|
||||
@@ -30,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,
|
||||
@@ -67,6 +68,12 @@ export class DeesButton extends DeesElement {
|
||||
})
|
||||
accessor insideForm: boolean = false;
|
||||
|
||||
@property({ type: String, reflect: true })
|
||||
accessor icon!: string;
|
||||
|
||||
@property({ type: String, reflect: true })
|
||||
accessor iconPosition: 'left' | 'right' = 'left';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@@ -339,9 +346,62 @@ export class DeesButton extends DeesElement {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* Text alignment */
|
||||
.textbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
/**
|
||||
* Extracts icon and text from light DOM and sets properties
|
||||
*/
|
||||
private extractLightDom(): void {
|
||||
const iconElement = this.querySelector('dees-icon') as any;
|
||||
|
||||
// Get all text content from light DOM
|
||||
const textContent = Array.from(this.childNodes)
|
||||
.filter(node => node.nodeType === Node.TEXT_NODE)
|
||||
.map(node => node.textContent?.trim())
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
if (textContent && !this.text) {
|
||||
this.text = textContent;
|
||||
}
|
||||
|
||||
if (iconElement) {
|
||||
// Get icon value
|
||||
const iconValue = iconElement.icon || iconElement.getAttribute('icon') ||
|
||||
(iconElement.iconFA ? `fa:${iconElement.iconFA}` : null);
|
||||
|
||||
if (iconValue) {
|
||||
// Determine position based on DOM order
|
||||
const children = Array.from(this.childNodes);
|
||||
const iconIndex = children.indexOf(iconElement);
|
||||
const textNodes = children.filter(node =>
|
||||
node.nodeType === Node.TEXT_NODE && node.textContent?.trim()
|
||||
);
|
||||
|
||||
if (textNodes.length > 0) {
|
||||
const firstTextIndex = children.indexOf(textNodes[0]);
|
||||
this.iconPosition = iconIndex < firstTextIndex ? 'left' : 'right';
|
||||
}
|
||||
|
||||
// Set the icon property
|
||||
this.icon = iconValue;
|
||||
}
|
||||
|
||||
// Remove the light DOM icon element
|
||||
iconElement.remove();
|
||||
}
|
||||
|
||||
// Clear all remaining light DOM
|
||||
this.innerHTML = '';
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
// Map old types to new types for backward compatibility
|
||||
const typeMap: {[key: string]: string} = {
|
||||
@@ -350,10 +410,20 @@ export class DeesButton extends DeesElement {
|
||||
'discreet': 'outline',
|
||||
'big': 'default' // Will use size instead
|
||||
};
|
||||
|
||||
|
||||
const actualType = typeMap[this.type] || this.type;
|
||||
const actualSize = this.type === 'big' ? 'lg' : this.size;
|
||||
|
||||
|
||||
const leftIcon = this.iconPosition === 'left' && this.icon
|
||||
? html`<dees-icon .icon=${this.icon}></dees-icon>`
|
||||
: '';
|
||||
const rightIcon = this.iconPosition === 'right' && this.icon
|
||||
? html`<dees-icon .icon=${this.icon}></dees-icon>`
|
||||
: '';
|
||||
|
||||
// For icon-only buttons, hide the textbox
|
||||
const isIconOnly = actualSize === 'icon' && this.icon;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="button ${this.isHidden ? 'hidden' : ''} ${actualType} size-${actualSize} ${this.status} ${this.disabled
|
||||
@@ -362,13 +432,15 @@ export class DeesButton extends DeesElement {
|
||||
@click="${this.dispatchClick}"
|
||||
>
|
||||
${this.status === 'normal' ? html``: html`
|
||||
<dees-spinner
|
||||
.bnw=${true}
|
||||
<dees-spinner
|
||||
.bnw=${true}
|
||||
status="${this.status}"
|
||||
size="${actualSize === 'sm' ? 14 : actualSize === 'lg' ? 18 : 16}"
|
||||
></dees-spinner>
|
||||
`}
|
||||
<div class="textbox">${this.text || html`<slot>Button</slot>`}</div>
|
||||
${leftIcon}
|
||||
${isIconOnly ? '' : html`<div class="textbox">${this.text || 'Button'}</div>`}
|
||||
${rightIcon}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -389,6 +461,7 @@ export class DeesButton extends DeesElement {
|
||||
}
|
||||
|
||||
public async firstUpdated() {
|
||||
// Don't set default text here as it interferes with slotted content
|
||||
// Extract light DOM content (icon + text) and set as properties
|
||||
this.extractLightDom();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,16 @@ import { chartAreaStyles } from './styles.js';
|
||||
import { renderChartArea } from './template.js';
|
||||
|
||||
import type ApexCharts from 'apexcharts';
|
||||
|
||||
type ApexAxisChartSeries = {
|
||||
name?: string;
|
||||
type?: string;
|
||||
color?: string;
|
||||
group?: string;
|
||||
hidden?: boolean;
|
||||
zIndex?: number;
|
||||
data: (number | null)[] | { x: any; y: any; [key: string]: any }[] | [number, number | null][] | number[][];
|
||||
}[];
|
||||
import { DeesServiceLibLoader } from '../../../services/index.js';
|
||||
|
||||
declare global {
|
||||
@@ -23,10 +33,11 @@ declare global {
|
||||
@customElement('dees-chart-area')
|
||||
export class DeesChartArea extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['Chart'];
|
||||
|
||||
// instance
|
||||
@state()
|
||||
accessor chart: ApexCharts;
|
||||
accessor chart!: ApexCharts;
|
||||
|
||||
@property()
|
||||
accessor label: string = 'Untitled Chart';
|
||||
@@ -57,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
|
||||
@@ -121,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);
|
||||
}
|
||||
@@ -159,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);
|
||||
@@ -357,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
|
||||
@@ -365,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();
|
||||
@@ -561,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;
|
||||
}
|
||||
|
||||