Compare commits
47 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dc8a3b620b | |||
| 9b96949a76 | |||
| 931797466a | |||
| 9bfb6446af | |||
| 976039798a | |||
| 0e2176ec7d | |||
| cada1a4234 | |||
| 465f7585ac | |||
| a7a710b320 | |||
| b1c174a4e2 | |||
| 395e0fa3da | |||
| f52b9d8b72 | |||
| 561d1b15d9 | |||
| 0722f362f3 | |||
| 2104b3bdce | |||
| e2d03107df | |||
| 54a87a5cc0 | |||
| 9bfbfcbb95 | |||
| 3505c390d8 | |||
| ff32470d8a | |||
| 4dba14060e | |||
| 31d728ec49 | |||
| ca290d1267 | |||
| dcef6faa66 | |||
| fe9eb21fe0 | |||
| f352314971 | |||
| 130ca68471 | |||
| cdde25d0b4 | |||
| 231c57b596 | |||
| 167dcb2b0a | |||
| fdccdcdf73 | |||
| bee1cafdb4 | |||
| 42b40da67c | |||
| 10cd1e2755 | |||
| 68ed024aaa | |||
| 6b6ccd0e3c | |||
| d933c47b49 | |||
| 3defbba5fd | |||
| 02522c9a15 | |||
| 4370efe6fb | |||
| cde2a833ef | |||
| 31fbe22f55 | |||
| e6f501e804 | |||
| f052fb9c9f | |||
| 77130ffb5e | |||
| 236b83d0a0 | |||
| a2e0760cc6 |
133
changelog.md
133
changelog.md
@@ -1,5 +1,138 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-04-05 - 3.59.1 - fix(project)
|
||||
no changes to commit
|
||||
|
||||
|
||||
## 2026-04-05 - 3.59.0 - feat(input)
|
||||
extract datepicker popup into a window-layer overlay and enhance the code editor modal status UI
|
||||
|
||||
- move the datepicker calendar, time, timezone, and event rendering into a dedicated popup component exported from the input module
|
||||
- render the datepicker popup in a window-layer overlay with reposition and cleanup handling for scroll, resize, and close events
|
||||
- preserve timezone-aware value formatting for selected dates
|
||||
- add a footer to the code editor modal showing cursor position, line count, and selected language
|
||||
- apply modal-specific Monaco background themes that react to light and dark mode
|
||||
|
||||
## 2026-04-05 - 3.58.0 - feat(dees-input-code)
|
||||
add editor status footer with cursor position, line count, and language display
|
||||
|
||||
- Tracks and displays the current cursor line and column in the code editor footer
|
||||
- Shows dynamic line count updates as editor content changes
|
||||
- Aligns the Monaco editor background with the surrounding tile theme, including light and dark mode updates
|
||||
|
||||
## 2026-04-05 - 3.57.0 - feat(dees-input-fileupload)
|
||||
redesign the file upload dropzone with dees-tile integration and themed file list styling
|
||||
|
||||
- Replace the custom dropzone container with dees-tile and move actions and metadata into header and footer slots
|
||||
- Add an explicit empty state for the file list and simplify file list layout and interaction handling
|
||||
- Adopt shared theme tokens in the file upload styles and introduce a reusable row hover color token
|
||||
|
||||
## 2026-04-04 - 3.56.1 - fix(dees-input-dropdown)
|
||||
improve dropdown popup lifecycle with window layer overlay and animated visibility transitions
|
||||
|
||||
- use a window layer to handle outside-click closing instead of document-level mousedown listeners
|
||||
- await popup show and search focus to keep popup initialization and overlay setup in sync
|
||||
- add guarded async hide logic with animated teardown and cleanup of scroll/resize listeners
|
||||
|
||||
## 2026-04-04 - 3.56.0 - feat(dees-input-dropdown)
|
||||
extract dropdown popup into a floating overlay component with search, keyboard navigation, and viewport repositioning
|
||||
|
||||
- adds a new dees-input-dropdown-popup export for rendering the menu as a fixed overlay attached to document.body
|
||||
- keeps the dropdown aligned to its trigger on scroll and resize, and closes it when the trigger moves off-screen
|
||||
- moves option filtering and keyboard selection handling into the popup component while preserving selection events
|
||||
|
||||
## 2026-04-04 - 3.55.6 - fix(dees-heading)
|
||||
adjust heading hr text color to use muted theme values
|
||||
|
||||
- Updates the dees-heading horizontal rule variant to use softer light and dark theme text colors instead of pure black and white.
|
||||
|
||||
## 2026-04-04 - 3.55.5 - fix(chart)
|
||||
refine ECharts series styling and legend color handling across bar, donut, and radar charts
|
||||
|
||||
- switch chart series palettes to hex colors and add rgba conversion to prevent black flashes during ECharts hover and emphasis animations
|
||||
- explicitly provide legend item colors and solid tooltip markers so translucent fills render consistently across chart types
|
||||
- deep-merge legend theme options in the shared ECharts base component to preserve nested legend text styling
|
||||
- adjust donut chart spacing and shared chart container styling for improved layout
|
||||
|
||||
## 2026-04-04 - 3.55.4 - fix(chart)
|
||||
align ECharts components with theme tokens and load the full ECharts ESM bundle
|
||||
|
||||
- replace hardcoded chart colors with centralized themeDefaults-based ECharts theme helpers across bar, donut, gauge, and radar components
|
||||
- improve donut label styling to use theme-aware text colors
|
||||
- switch CDN loading to the pre-built echarts.esm.min.js bundle so all chart types and components are available
|
||||
|
||||
## 2026-04-04 - 3.55.3 - fix(theme)
|
||||
align component styles with shared theme CSS variables
|
||||
|
||||
- replace hardcoded bdTheme color usages across chart, dataview, input, layout, modal, media, simple, and workspace components with shared --dees-* theme tokens
|
||||
- add themeDefaultStyles to components and style modules that were not yet inheriting the shared theme defaults
|
||||
- standardize hover, border, background, text, and scrollbar colors for more consistent theming across the catalog
|
||||
|
||||
## 2026-04-04 - 3.55.2 - fix(dees-simple-appdash,dees-simple-login)
|
||||
migrate app dashboard and login styles to shared theme CSS variables
|
||||
|
||||
- Replaces hardcoded bdTheme color values with --dees-* design tokens across dashboard and login components
|
||||
- Aligns backgrounds, borders, text, hover, active, and scrollbar colors with the shared theming system
|
||||
|
||||
## 2026-04-04 - 3.55.1 - fix(appui)
|
||||
replace hardcoded app UI theme colors with shared dees CSS variables
|
||||
|
||||
- Standardizes app UI component styling on shared --dees-* theme tokens across app bar, menus, tabs, main content, and bottom bar
|
||||
- Removes remaining hardcoded light/dark color values in favor of centralized background, border, text, badge, tooltip, scrollbar, and accent variables
|
||||
|
||||
## 2026-04-04 - 3.55.0 - feat(theme)
|
||||
centralize theme CSS tokens in theme defaults and add missing interactive color variables
|
||||
|
||||
- Refactors theme CSS variables to derive light and dark values from the shared themeDefaults token map instead of hardcoded color literals.
|
||||
- Adjusts dark background token values so secondary and tertiary surfaces align more consistently with the dark UI palette.
|
||||
- Adds new theme variables for interactive states, focus ring, tooltip, link, code, selection, and scrollbar styling.
|
||||
|
||||
## 2026-04-04 - 3.54.0 - feat(media)
|
||||
rename media tile components to thumbnail components and add shared thumbnail base exports
|
||||
|
||||
- Replaces dees-tile-* media component exports and implementations with dees-thumbnail-* counterparts for audio, image, video, note, folder, and pdf previews.
|
||||
- Introduces a shared DeesThumbnailBase and shared thumbnail styles for consistent sizing, hover overlays, loading states, error states, and lazy-loading behavior.
|
||||
- Updates the media index to export the new thumbnail component modules.
|
||||
- Includes a small layout cleanup in dees-dataview-codebox by removing forced full-height and absolute grid positioning.
|
||||
|
||||
## 2026-04-03 - 3.53.0 - feat(dees-pdf-viewer)
|
||||
add configurable sidebar position support
|
||||
|
||||
- introduces a sidebarPosition property with left and right options
|
||||
- updates viewer layout and footer alignment when the sidebar is displayed on the right
|
||||
|
||||
## 2026-04-03 - 3.52.5 - fix(dees-pdf-viewer)
|
||||
add top scroll offset when navigating to a page in the PDF viewer
|
||||
|
||||
- Subtracts 16px from the calculated scroll target so the selected page is not flush against the top edge of the viewer.
|
||||
- Improves page navigation positioning in the dees-pdf-viewer component.
|
||||
|
||||
## 2026-04-03 - 3.52.4 - fix(appui-maincontent)
|
||||
adjust main content background theme colors
|
||||
|
||||
- Update the main content background from pure white/near-black to softer light and dark theme values.
|
||||
|
||||
## 2026-04-03 - 3.52.3 - fix(input-richtext)
|
||||
resolve rich text editor initialization and layout issues by bundling Tiptap locally and anchoring editor containers
|
||||
|
||||
- Switch Tiptap imports from CDN URLs to bundled npm packages to avoid duplicate ProseMirror instances
|
||||
- Update rich text, code, dataview, and terminal preview containers to use absolute inset positioning for stable full-size layouts
|
||||
- Trigger a component update after rich text editor initialization and improve ProseMirror wrapping behavior
|
||||
|
||||
## 2026-04-03 - 3.52.2 - fix(chart-log, simple-appdash)
|
||||
align terminal and dashboard theming with brightness mode and improve app dashboard scroll presentation
|
||||
|
||||
- Update dees-chart-log to refresh the terminal theme when goBright changes and derive dark mode directly from the brightness setting.
|
||||
- Refine dees-simple-appdash control bar colors, borders, and shadow gradients for better light and dark theme consistency.
|
||||
- Expand the app dashboard demo with recent activity content to showcase scrollable layout behavior.
|
||||
|
||||
## 2026-04-03 - 3.52.1 - fix(dees-modal)
|
||||
refine modal styling, spacing, and animations for a cleaner overlay presentation
|
||||
|
||||
- Adjust modal entrance and exit transitions with updated transform, opacity, and timing values
|
||||
- Refresh heading and action button styling with tighter spacing, smaller controls, and improved theme-aware colors
|
||||
- Update tile shadow, margins, and content scrollbar styling to improve modal visual polish and readability
|
||||
|
||||
## 2026-04-03 - 3.52.0 - feat(dees-chart-area)
|
||||
add full page toggle control for chart area
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@design.estate/dees-catalog",
|
||||
"version": "3.52.0",
|
||||
"version": "3.59.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",
|
||||
@@ -34,6 +34,7 @@
|
||||
"@tiptap/extension-underline": "^2.23.0",
|
||||
"@tiptap/starter-kit": "^2.23.0",
|
||||
"@tsclass/tsclass": "^9.5.0",
|
||||
"echarts": "^5.6.0",
|
||||
"lightweight-charts": "^5.1.0",
|
||||
"highlight.js": "11.11.1",
|
||||
"ibantools": "^4.5.1",
|
||||
|
||||
23
pnpm-lock.yaml
generated
23
pnpm-lock.yaml
generated
@@ -62,6 +62,9 @@ importers:
|
||||
'@tsclass/tsclass':
|
||||
specifier: ^9.5.0
|
||||
version: 9.5.0
|
||||
echarts:
|
||||
specifier: ^5.6.0
|
||||
version: 5.6.0
|
||||
highlight.js:
|
||||
specifier: 11.11.1
|
||||
version: 11.11.1
|
||||
@@ -2534,6 +2537,9 @@ packages:
|
||||
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
|
||||
echarts@5.6.0:
|
||||
resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==}
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||
|
||||
@@ -3936,6 +3942,9 @@ packages:
|
||||
tslib@1.14.1:
|
||||
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
|
||||
|
||||
tslib@2.3.0:
|
||||
resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
@@ -4149,6 +4158,9 @@ packages:
|
||||
zod@3.25.76:
|
||||
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||
|
||||
zrender@5.6.1:
|
||||
resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==}
|
||||
|
||||
zwitch@2.0.4:
|
||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||
|
||||
@@ -7671,6 +7683,11 @@ snapshots:
|
||||
es-errors: 1.3.0
|
||||
gopd: 1.2.0
|
||||
|
||||
echarts@5.6.0:
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
zrender: 5.6.1
|
||||
|
||||
emoji-regex@8.0.0: {}
|
||||
|
||||
end-of-stream@1.4.5:
|
||||
@@ -9510,6 +9527,8 @@ snapshots:
|
||||
|
||||
tslib@1.14.1: {}
|
||||
|
||||
tslib@2.3.0: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tsx@4.21.0:
|
||||
@@ -9699,4 +9718,8 @@ snapshots:
|
||||
|
||||
zod@3.25.76: {}
|
||||
|
||||
zrender@5.6.1:
|
||||
dependencies:
|
||||
tslib: 2.3.0
|
||||
|
||||
zwitch@2.0.4: {}
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@design.estate/dees-catalog',
|
||||
version: '3.52.0',
|
||||
version: '3.59.1',
|
||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ export const appuiAppbarStyles = [
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: var(--appbar-height);
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
|
||||
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||
border-bottom: 1px solid var(--dees-color-border-default);
|
||||
background: var(--dees-color-bg-primary);
|
||||
color: ${cssManager.bdTheme('#00000080', '#ffffff80')};
|
||||
font-size: var(--appbar-font-size);
|
||||
display: grid;
|
||||
@@ -78,8 +78,8 @@ export const appuiAppbarStyles = [
|
||||
top: 100%;
|
||||
left: 0;
|
||||
min-width: 200px;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 4px;
|
||||
box-shadow: ${cssManager.bdTheme('0 4px 12px rgba(0, 0, 0, 0.15)', '0 4px 12px rgba(0, 0, 0, 0.3)')};
|
||||
margin-top: 4px;
|
||||
@@ -112,7 +112,7 @@ export const appuiAppbarStyles = [
|
||||
|
||||
.dropdown-divider {
|
||||
height: 1px;
|
||||
background: ${cssManager.bdTheme('#e0e0e0', '#202020')};
|
||||
background: var(--dees-color-border-default);
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ export const appuiAppbarStyles = [
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
border: 2px solid ${cssManager.bdTheme('#ffffff', '#000000')};
|
||||
border: 2px solid var(--dees-color-bg-primary);
|
||||
}
|
||||
|
||||
.user-status.online {
|
||||
|
||||
@@ -52,10 +52,10 @@ export class DeesAppuiBottombar extends DeesElement implements IBottomBarAPI {
|
||||
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%)')};
|
||||
background: var(--dees-color-bg-tertiary);
|
||||
border-top: 1px solid var(--dees-color-border-default);
|
||||
font-size: 11px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.widget {
|
||||
@@ -70,8 +70,8 @@ export class DeesAppuiBottombar extends DeesElement implements IBottomBarAPI {
|
||||
}
|
||||
|
||||
.widget:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 88%)', 'hsl(0 0% 12%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')};
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
|
||||
.widget dees-icon {
|
||||
@@ -81,7 +81,7 @@ export class DeesAppuiBottombar extends DeesElement implements IBottomBarAPI {
|
||||
.widget-separator {
|
||||
width: 1px;
|
||||
height: 14px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 80%)', 'hsl(0 0% 20%)')};
|
||||
background: var(--dees-color-border-strong);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
@@ -124,12 +124,12 @@ export class DeesAppuiBottombar extends DeesElement implements IBottomBarAPI {
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.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%)')};
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
|
||||
.action-button.disabled {
|
||||
@@ -139,7 +139,7 @@ export class DeesAppuiBottombar extends DeesElement implements IBottomBarAPI {
|
||||
|
||||
.action-button.disabled:hover {
|
||||
background: transparent;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -63,14 +63,13 @@ export class DeesAppuiMaincontent extends DeesElement {
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
:host {
|
||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#161616')};
|
||||
background: var(--dees-color-bg-secondary);
|
||||
}
|
||||
|
||||
.maincontainer {
|
||||
|
||||
@@ -55,28 +55,27 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
:host {
|
||||
--menu-width-expanded: 200px;
|
||||
--menu-width-collapsed: 56px;
|
||||
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
||||
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
|
||||
--tooltip-bg: var(--dees-color-tooltip-bg);
|
||||
--tooltip-fg: var(--dees-color-tooltip-fg);
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mainContainer {
|
||||
color: ${cssManager.bdTheme('#666', '#ccc')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
z-index: ${zIndexLayers.fixed.appBar};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
width: var(--menu-width-expanded);
|
||||
height: 100%;
|
||||
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
background: var(--dees-color-bg-secondary);
|
||||
user-select: none;
|
||||
border-right: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||
border-right: 1px solid var(--dees-color-border-subtle);
|
||||
font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
||||
transition: width 0.25s ease;
|
||||
}
|
||||
@@ -94,23 +93,23 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#3f3f46')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-strong);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${cssManager.bdTheme('#737373', '#a1a1aa')};
|
||||
color: var(--dees-color-text-muted);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease, background 0.15s ease;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.collapse-toggle:hover {
|
||||
background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')};
|
||||
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||
background: var(--dees-color-bg-tertiary);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
:host(:hover) .collapse-toggle {
|
||||
@@ -128,14 +127,14 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
gap: 10px;
|
||||
height: 48px;
|
||||
padding: 0 14px;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||
border-bottom: 1px solid var(--dees-color-border-subtle);
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.logoSection .logoIcon {
|
||||
font-size: 22px;
|
||||
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||
color: var(--dees-color-text-primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@@ -143,7 +142,7 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
flex: 1;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||
color: var(--dees-color-text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -178,12 +177,12 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
}
|
||||
|
||||
.menuSection::-webkit-scrollbar-thumb {
|
||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.15)', 'rgba(255, 255, 255, 0.15)')};
|
||||
background: var(--dees-color-scrollbar-thumb);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.menuSection::-webkit-scrollbar-thumb:hover {
|
||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.25)', 'rgba(255, 255, 255, 0.25)')};
|
||||
background: var(--dees-color-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
/* Menu Group */
|
||||
@@ -200,7 +199,7 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
padding: 8px 12px 6px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#737373', '#737373')};
|
||||
color: var(--dees-color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
white-space: nowrap;
|
||||
@@ -238,21 +237,21 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
color: ${cssManager.bdTheme('#525252', '#a3a3a3')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.06)')};
|
||||
background: var(--dees-color-hover);
|
||||
color: ${cssManager.bdTheme('#262626', '#e5e5e5')};
|
||||
}
|
||||
|
||||
.tab:active {
|
||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
|
||||
background: var(--dees-color-active);
|
||||
}
|
||||
|
||||
.tab.selectedTab {
|
||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
|
||||
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||
background: var(--dees-color-active);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.tab.selectedTab::before {
|
||||
@@ -263,7 +262,7 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 16px;
|
||||
background: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||
background: var(--dees-color-text-primary);
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
|
||||
@@ -353,23 +352,23 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
}
|
||||
|
||||
.badge.default {
|
||||
background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
|
||||
color: ${cssManager.bdTheme('#3f3f46', '#a1a1aa')};
|
||||
background: var(--dees-color-badge-default-bg);
|
||||
color: var(--dees-color-badge-default-fg);
|
||||
}
|
||||
|
||||
.badge.success {
|
||||
background: ${cssManager.bdTheme('#dcfce7', '#14532d')};
|
||||
color: ${cssManager.bdTheme('#166534', '#4ade80')};
|
||||
background: var(--dees-color-badge-success-bg);
|
||||
color: var(--dees-color-badge-success-fg);
|
||||
}
|
||||
|
||||
.badge.warning {
|
||||
background: ${cssManager.bdTheme('#fef3c7', '#451a03')};
|
||||
color: ${cssManager.bdTheme('#92400e', '#fbbf24')};
|
||||
background: var(--dees-color-badge-warning-bg);
|
||||
color: var(--dees-color-badge-warning-fg);
|
||||
}
|
||||
|
||||
.badge.error {
|
||||
background: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
|
||||
color: ${cssManager.bdTheme('#991b1b', '#f87171')};
|
||||
background: var(--dees-color-badge-error-bg);
|
||||
color: var(--dees-color-badge-error-fg);
|
||||
}
|
||||
|
||||
:host([collapsed]) .badge {
|
||||
@@ -380,7 +379,7 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
.bottomSection {
|
||||
flex-shrink: 0;
|
||||
padding: 8px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||
border-top: 1px solid var(--dees-color-border-subtle);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
@@ -73,30 +73,29 @@ export class DeesAppuiSecondarymenu extends DeesElement {
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
:host {
|
||||
--sidebar-width-expanded: 240px;
|
||||
--sidebar-width-collapsed: 56px;
|
||||
--sidebar-bg: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
|
||||
--sidebar-fg: ${cssManager.bdTheme('#525252', '#a3a3a3')};
|
||||
--sidebar-fg-muted: ${cssManager.bdTheme('#737373', '#737373')};
|
||||
--sidebar-fg-active: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||
--sidebar-border: ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
|
||||
--sidebar-hover: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.06)')};
|
||||
--sidebar-active: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
|
||||
--sidebar-accent: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
||||
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
|
||||
--sidebar-bg: var(--dees-color-bg-secondary);
|
||||
--sidebar-fg: var(--dees-color-text-secondary);
|
||||
--sidebar-fg-muted: var(--dees-color-text-muted);
|
||||
--sidebar-fg-active: var(--dees-color-text-primary);
|
||||
--sidebar-border: var(--dees-color-border-subtle);
|
||||
--sidebar-hover: var(--dees-color-hover);
|
||||
--sidebar-active: var(--dees-color-active);
|
||||
--sidebar-accent: var(--dees-color-text-primary);
|
||||
--tooltip-bg: var(--dees-color-tooltip-bg);
|
||||
--tooltip-fg: var(--dees-color-tooltip-fg);
|
||||
|
||||
/* Badge colors */
|
||||
--badge-default-bg: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
|
||||
--badge-default-fg: ${cssManager.bdTheme('#3f3f46', '#a1a1aa')};
|
||||
--badge-success-bg: ${cssManager.bdTheme('#dcfce7', '#14532d')};
|
||||
--badge-success-fg: ${cssManager.bdTheme('#166534', '#4ade80')};
|
||||
--badge-warning-bg: ${cssManager.bdTheme('#fef3c7', '#451a03')};
|
||||
--badge-warning-fg: ${cssManager.bdTheme('#92400e', '#fbbf24')};
|
||||
--badge-error-bg: ${cssManager.bdTheme('#fee2e2', '#450a0a')};
|
||||
--badge-error-fg: ${cssManager.bdTheme('#991b1b', '#f87171')};
|
||||
--badge-default-bg: var(--dees-color-badge-default-bg);
|
||||
--badge-default-fg: var(--dees-color-badge-default-fg);
|
||||
--badge-success-bg: var(--dees-color-badge-success-bg);
|
||||
--badge-success-fg: var(--dees-color-badge-success-fg);
|
||||
--badge-warning-bg: var(--dees-color-badge-warning-bg);
|
||||
--badge-warning-fg: var(--dees-color-badge-warning-fg);
|
||||
--badge-error-bg: var(--dees-color-badge-error-bg);
|
||||
--badge-error-fg: var(--dees-color-badge-error-fg);
|
||||
|
||||
/* Action colors */
|
||||
--action-primary: ${cssManager.bdTheme('#2563eb', '#3b82f6')};
|
||||
@@ -136,23 +135,23 @@ export class DeesAppuiSecondarymenu extends DeesElement {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#3f3f46')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-strong);
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${cssManager.bdTheme('#737373', '#a1a1aa')};
|
||||
color: var(--dees-color-text-muted);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease, background 0.15s ease;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.collapse-toggle:hover {
|
||||
background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')};
|
||||
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
|
||||
background: var(--dees-color-bg-tertiary);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
:host(:hover) .collapse-toggle {
|
||||
@@ -215,12 +214,12 @@ export class DeesAppuiSecondarymenu extends DeesElement {
|
||||
}
|
||||
|
||||
.menuSection::-webkit-scrollbar-thumb {
|
||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.15)', 'rgba(255, 255, 255, 0.15)')};
|
||||
background: var(--dees-color-scrollbar-thumb);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.menuSection::-webkit-scrollbar-thumb:hover {
|
||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.25)', 'rgba(255, 255, 255, 0.25)')};
|
||||
background: var(--dees-color-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
/* Menu Group */
|
||||
@@ -261,7 +260,7 @@ export class DeesAppuiSecondarymenu extends DeesElement {
|
||||
gap: 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('#78716c', '#b5a99a')};
|
||||
color: var(--dees-color-text-warm);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
white-space: nowrap;
|
||||
@@ -270,13 +269,13 @@ export class DeesAppuiSecondarymenu extends DeesElement {
|
||||
|
||||
.groupHeader .groupTitle dees-icon {
|
||||
font-size: 16px;
|
||||
color: ${cssManager.bdTheme('#78716c', '#b5a99a')};
|
||||
color: var(--dees-color-text-warm);
|
||||
}
|
||||
|
||||
.groupHeader .chevron {
|
||||
font-size: 12px;
|
||||
transition: transform 0.2s ease;
|
||||
color: ${cssManager.bdTheme('#78716c', '#b5a99a')};
|
||||
color: var(--dees-color-text-warm);
|
||||
}
|
||||
|
||||
.groupHeader.collapsed .chevron {
|
||||
|
||||
@@ -60,7 +60,6 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
:host {
|
||||
display: block;
|
||||
position: relative;
|
||||
@@ -76,7 +75,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
|
||||
.tabs-wrapper.horizontal-wrapper {
|
||||
height: 48px;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
border-bottom: 1px solid var(--dees-color-border-default);
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@@ -133,13 +132,13 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
.tab-actions.left {
|
||||
padding-left: 12px;
|
||||
padding-right: 8px;
|
||||
border-right: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
border-right: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
.tab-actions.right {
|
||||
padding-right: 12px;
|
||||
padding-left: 8px;
|
||||
border-left: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
border-left: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
.tab-action-button {
|
||||
@@ -152,17 +151,17 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
background: transparent;
|
||||
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
||||
color: var(--dees-color-text-muted);
|
||||
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')};
|
||||
background: var(--dees-color-active);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.tab-action-button:active {
|
||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')};
|
||||
background: var(--dees-color-pressed);
|
||||
}
|
||||
|
||||
.tab-action-button.disabled {
|
||||
@@ -172,7 +171,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
|
||||
.tab-action-button.disabled:hover {
|
||||
background: transparent;
|
||||
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.tab-action-button dees-icon {
|
||||
@@ -237,12 +236,12 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
font-size: 14px;
|
||||
gap: 2px;
|
||||
position: relative;
|
||||
background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
|
||||
background: var(--dees-color-bg-tertiary);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.tab {
|
||||
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
||||
color: var(--dees-color-text-muted);
|
||||
white-space: nowrap;
|
||||
cursor: pointer;
|
||||
transition: color 0.15s ease;
|
||||
@@ -270,7 +269,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
transform: translateY(-50%);
|
||||
height: 20px;
|
||||
width: 1px;
|
||||
background: ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
background: var(--dees-color-border-default);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -291,11 +290,11 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
}
|
||||
|
||||
.tab:hover {
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.horizontal .tab:hover {
|
||||
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.03)', 'rgba(255, 255, 255, 0.03)')};
|
||||
background: var(--dees-color-hover);
|
||||
}
|
||||
|
||||
.horizontal .tab:hover::after,
|
||||
@@ -308,7 +307,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
}
|
||||
|
||||
.horizontal .tab.selectedTab {
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.horizontal .tab.selectedTab::after,
|
||||
@@ -317,7 +316,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
}
|
||||
|
||||
.vertical .tab.selectedTab {
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.tab dees-icon {
|
||||
@@ -337,7 +336,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
.tabs-wrapper .tabIndicator {
|
||||
height: 3px;
|
||||
bottom: 0;
|
||||
background: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
|
||||
background: var(--dees-color-accent-primary);
|
||||
border-radius: 3px 3px 0 0;
|
||||
z-index: 3;
|
||||
}
|
||||
@@ -350,7 +349,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
left: 8px;
|
||||
right: 8px;
|
||||
border-radius: 6px;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
z-index: 1;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
@@ -366,7 +365,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
margin-left: 8px;
|
||||
opacity: 0.4;
|
||||
transition: opacity 0.15s, background 0.15s;
|
||||
color: ${cssManager.bdTheme('#71717a', '#71717a')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.tab:hover .tab-close {
|
||||
@@ -375,8 +374,8 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
|
||||
.tab-close:hover {
|
||||
opacity: 1;
|
||||
background: ${cssManager.bdTheme('rgba(0,0,0,0.1)', 'rgba(255,255,255,0.1)')};
|
||||
color: ${cssManager.bdTheme('#ef4444', '#f87171')};
|
||||
background: var(--dees-color-pressed);
|
||||
color: var(--dees-color-accent-error);
|
||||
}
|
||||
|
||||
.tab.selectedTab .tab-close {
|
||||
|
||||
@@ -184,12 +184,11 @@ export class DeesAppui extends DeesElement {
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
:host {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background: ${cssManager.bdTheme('#f0f0f0', '#1a1a1a')};
|
||||
background: var(--dees-color-bg-tertiary);
|
||||
}
|
||||
.maingrid {
|
||||
position: absolute;
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
import { css, cssManager } from '@design.estate/dees-element';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
export const chartAreaStyles = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 400px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
dees-tile {
|
||||
@@ -24,7 +26,7 @@ export const chartAreaStyles = [
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
.expandBtn {
|
||||
display: flex;
|
||||
@@ -36,13 +38,13 @@ export const chartAreaStyles = [
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 55%)', 'hsl(0 0% 45%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
transition: all 0.15s ease;
|
||||
padding: 0;
|
||||
}
|
||||
.expandBtn:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 93%)', 'hsl(0 0% 12%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
.chartContainer {
|
||||
position: absolute;
|
||||
@@ -64,7 +66,7 @@ export const chartAreaStyles = [
|
||||
}
|
||||
.statsSeries + .statsSeries {
|
||||
padding-left: 24px;
|
||||
border-left: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-left: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
.statsColor {
|
||||
width: 8px;
|
||||
@@ -75,15 +77,15 @@ export const chartAreaStyles = [
|
||||
.statsName {
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
margin-right: 4px;
|
||||
}
|
||||
.statsItem {
|
||||
font-size: 11px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45%)', 'hsl(0 0% 55%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
.statsItem strong {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
.lw-tooltip {
|
||||
position: absolute;
|
||||
|
||||
158
ts_web/elements/00group-chart/dees-chart-bar/component.ts
Normal file
158
ts_web/elements/00group-chart/dees-chart-bar/component.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import { DeesChartEchartsBase } from '../dees-chart-echarts-base.js';
|
||||
import { demoFunc } from './demo.js';
|
||||
import { barStyles } from './styles.js';
|
||||
import { renderChartBar } from './template.js';
|
||||
import { getEchartsSeriesColors, getThemeColors, hexToRgba } from '../dees-chart-echarts-theme.js';
|
||||
|
||||
export interface IBarSeriesItem {
|
||||
name: string;
|
||||
data: number[];
|
||||
color?: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-chart-bar': DeesChartBar;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-chart-bar')
|
||||
export class DeesChartBar extends DeesChartEchartsBase {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['Chart'];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor categories: string[] = [];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor series: IBarSeriesItem[] = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor horizontal: boolean = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor stacked: boolean = false;
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor showLegend: boolean = true;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor valueFormatter: (value: number) => string = (val) => `${val}`;
|
||||
|
||||
public static styles = barStyles;
|
||||
|
||||
public render(): TemplateResult {
|
||||
return renderChartBar(this);
|
||||
}
|
||||
|
||||
public async updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
this.chartInstance &&
|
||||
(changedProperties.has('categories') ||
|
||||
changedProperties.has('series') ||
|
||||
changedProperties.has('horizontal') ||
|
||||
changedProperties.has('stacked') ||
|
||||
changedProperties.has('showLegend'))
|
||||
) {
|
||||
this.updateChart();
|
||||
}
|
||||
}
|
||||
|
||||
protected buildOption(): Record<string, any> {
|
||||
const colors = getThemeColors(this.goBright);
|
||||
const seriesColors = getEchartsSeriesColors(this.goBright);
|
||||
const formatter = this.valueFormatter;
|
||||
|
||||
const categoryAxis: Record<string, any> = {
|
||||
type: 'category',
|
||||
data: this.categories,
|
||||
axisLine: { lineStyle: { color: colors.borderStrong } },
|
||||
axisLabel: { color: colors.textMuted },
|
||||
};
|
||||
|
||||
const valueAxis: Record<string, any> = {
|
||||
type: 'value',
|
||||
axisLine: { show: false },
|
||||
axisLabel: {
|
||||
color: colors.textMuted,
|
||||
formatter: (val: number) => formatter(val),
|
||||
},
|
||||
splitLine: { lineStyle: { color: colors.borderSubtle } },
|
||||
};
|
||||
|
||||
const fillAlpha = this.goBright ? 0.15 : 0.25;
|
||||
const borderRadius = this.horizontal ? [0, 4, 4, 0] : [4, 4, 0, 0];
|
||||
const noBorderRadius = [0, 0, 0, 0];
|
||||
|
||||
const legendData: Array<{ name: string; itemStyle: { color: string } }> = [];
|
||||
|
||||
const seriesData = this.series.map((s, index) => {
|
||||
const color = s.color || seriesColors[index % seriesColors.length];
|
||||
legendData.push({ name: s.name, itemStyle: { color } });
|
||||
return {
|
||||
name: s.name,
|
||||
type: 'bar' as const,
|
||||
data: s.data,
|
||||
stack: this.stacked ? 'total' : undefined,
|
||||
itemStyle: {
|
||||
color: hexToRgba(color, fillAlpha),
|
||||
borderColor: color,
|
||||
borderWidth: 1,
|
||||
borderRadius: this.stacked ? noBorderRadius : borderRadius,
|
||||
},
|
||||
barMaxWidth: 40,
|
||||
barGap: '20%',
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: hexToRgba(color, fillAlpha + 0.15),
|
||||
borderColor: color,
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// For stacked bars, round the top corners of the last visible series
|
||||
if (this.stacked && seriesData.length > 0) {
|
||||
const last = seriesData[seriesData.length - 1];
|
||||
last.itemStyle.borderRadius = borderRadius;
|
||||
}
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
formatter: (params: any) => {
|
||||
const items = Array.isArray(params) ? params : [params];
|
||||
let result = `<strong>${items[0].axisValueLabel}</strong><br/>`;
|
||||
for (const p of items) {
|
||||
const solidColor = p.borderColor || p.color;
|
||||
const marker = `<span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${solidColor};"></span>`;
|
||||
result += `${marker}${p.seriesName}: <strong>${formatter(p.value)}</strong><br/>`;
|
||||
}
|
||||
return result;
|
||||
},
|
||||
},
|
||||
legend: this.showLegend && this.series.length > 1
|
||||
? { bottom: 8, itemWidth: 10, itemHeight: 10, data: legendData }
|
||||
: { show: false },
|
||||
grid: {
|
||||
left: 16,
|
||||
right: 16,
|
||||
top: 16,
|
||||
bottom: this.showLegend && this.series.length > 1 ? 40 : 16,
|
||||
containLabel: true,
|
||||
},
|
||||
xAxis: this.horizontal ? valueAxis : categoryAxis,
|
||||
yAxis: this.horizontal ? categoryAxis : valueAxis,
|
||||
series: seriesData,
|
||||
};
|
||||
}
|
||||
}
|
||||
120
ts_web/elements/00group-chart/dees-chart-bar/demo.ts
Normal file
120
ts_web/elements/00group-chart/dees-chart-bar/demo.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||
import type { DeesChartBar } from './component.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import './component.js';
|
||||
|
||||
export const demoFunc = () => {
|
||||
const endpointCategories = ['/api/users', '/api/orders', '/api/products', '/api/auth', '/api/search'];
|
||||
const endpointSeries = [
|
||||
{ name: 'GET', data: [1240, 890, 720, 2100, 560] },
|
||||
{ name: 'POST', data: [320, 450, 180, 890, 40] },
|
||||
{ name: 'PUT', data: [90, 210, 150, 30, 10] },
|
||||
];
|
||||
|
||||
const regionCategories = ['US-East', 'US-West', 'EU', 'Asia', 'Other'];
|
||||
const regionSeries = [
|
||||
{ name: 'Requests', data: [4500, 3200, 2800, 1900, 600] },
|
||||
];
|
||||
|
||||
return html`
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
const vertChart = elementArg.querySelector('#vert-chart') as DeesChartBar;
|
||||
const horizChart = elementArg.querySelector('#horiz-chart') as DeesChartBar;
|
||||
const stackChart = elementArg.querySelector('#stack-chart') as DeesChartBar;
|
||||
|
||||
const buttons = elementArg.querySelectorAll('dees-button');
|
||||
buttons.forEach((button: any) => {
|
||||
const text = button.text?.trim();
|
||||
if (text === 'Randomize') {
|
||||
button.addEventListener('click', () => {
|
||||
vertChart.series = endpointSeries.map((s) => ({
|
||||
...s,
|
||||
data: s.data.map((v) => Math.round(v * (0.5 + Math.random()))),
|
||||
}));
|
||||
horizChart.series = regionSeries.map((s) => ({
|
||||
...s,
|
||||
data: s.data.map((v) => Math.round(v * (0.5 + Math.random()))),
|
||||
}));
|
||||
stackChart.series = endpointSeries.map((s) => ({
|
||||
...s,
|
||||
data: s.data.map((v) => Math.round(v * (0.5 + Math.random()))),
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<style>
|
||||
${css`
|
||||
.demoBox {
|
||||
position: relative;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 9%)')};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
.chartRow {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.info {
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||
font-size: 12px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Geist Sans', sans-serif;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div class="demoBox">
|
||||
<div class="controls">
|
||||
<dees-button-group label="Actions:">
|
||||
<dees-button>Randomize</dees-button>
|
||||
</dees-button-group>
|
||||
</div>
|
||||
|
||||
<div class="chartRow">
|
||||
<dees-chart-bar
|
||||
id="vert-chart"
|
||||
.label=${'Requests by Endpoint'}
|
||||
.categories=${endpointCategories}
|
||||
.series=${endpointSeries}
|
||||
.valueFormatter=${(val: number) => `${val} req`}
|
||||
></dees-chart-bar>
|
||||
|
||||
<dees-chart-bar
|
||||
id="horiz-chart"
|
||||
.label=${'Traffic by Region'}
|
||||
.categories=${regionCategories}
|
||||
.series=${regionSeries}
|
||||
.horizontal=${true}
|
||||
.valueFormatter=${(val: number) => `${(val / 1000).toFixed(1)}k`}
|
||||
></dees-chart-bar>
|
||||
</div>
|
||||
|
||||
<dees-chart-bar
|
||||
id="stack-chart"
|
||||
.label=${'Stacked: Requests by Endpoint'}
|
||||
.categories=${endpointCategories}
|
||||
.series=${endpointSeries}
|
||||
.stacked=${true}
|
||||
.valueFormatter=${(val: number) => `${val} req`}
|
||||
></dees-chart-bar>
|
||||
|
||||
<div class="info">
|
||||
Bar chart with vertical, horizontal, and stacked modes •
|
||||
Click 'Randomize' to update data with animation
|
||||
</div>
|
||||
</div>
|
||||
</dees-demowrapper>
|
||||
`;
|
||||
};
|
||||
7
ts_web/elements/00group-chart/dees-chart-bar/styles.ts
Normal file
7
ts_web/elements/00group-chart/dees-chart-bar/styles.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { css } from '@design.estate/dees-element';
|
||||
import { echartsBaseStyles } from '../dees-chart-echarts-styles.js';
|
||||
|
||||
export const barStyles = [
|
||||
...echartsBaseStyles,
|
||||
css``,
|
||||
];
|
||||
13
ts_web/elements/00group-chart/dees-chart-bar/template.ts
Normal file
13
ts_web/elements/00group-chart/dees-chart-bar/template.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { html, type TemplateResult } from '@design.estate/dees-element';
|
||||
import type { DeesChartBar } from './component.js';
|
||||
|
||||
export const renderChartBar = (component: DeesChartBar): TemplateResult => {
|
||||
return html`
|
||||
<dees-tile>
|
||||
<div slot="header" class="chartHeader">
|
||||
<span class="chartLabel">${component.label}</span>
|
||||
</div>
|
||||
<div class="chartContainer"></div>
|
||||
</dees-tile>
|
||||
`;
|
||||
};
|
||||
142
ts_web/elements/00group-chart/dees-chart-donut/component.ts
Normal file
142
ts_web/elements/00group-chart/dees-chart-donut/component.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import { DeesChartEchartsBase } from '../dees-chart-echarts-base.js';
|
||||
import { demoFunc } from './demo.js';
|
||||
import { donutStyles } from './styles.js';
|
||||
import { renderChartDonut } from './template.js';
|
||||
import { getEchartsSeriesColors, getThemeColors, hexToRgba } from '../dees-chart-echarts-theme.js';
|
||||
|
||||
export interface IDonutDataItem {
|
||||
name: string;
|
||||
value: number;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-chart-donut': DeesChartDonut;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-chart-donut')
|
||||
export class DeesChartDonut extends DeesChartEchartsBase {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['Chart'];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor data: IDonutDataItem[] = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor showLegend: boolean = true;
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor showLabels: boolean = true;
|
||||
|
||||
@property({ type: String })
|
||||
accessor innerRadiusPercent: string = '55%';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor valueFormatter: (value: number) => string = (val) => `${val}`;
|
||||
|
||||
public static styles = donutStyles;
|
||||
|
||||
public render(): TemplateResult {
|
||||
return renderChartDonut(this);
|
||||
}
|
||||
|
||||
public async updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
this.chartInstance &&
|
||||
(changedProperties.has('data') ||
|
||||
changedProperties.has('showLegend') ||
|
||||
changedProperties.has('showLabels') ||
|
||||
changedProperties.has('innerRadiusPercent'))
|
||||
) {
|
||||
this.updateChart();
|
||||
}
|
||||
}
|
||||
|
||||
protected buildOption(): Record<string, any> {
|
||||
const themeColors = getThemeColors(this.goBright);
|
||||
const seriesColors = getEchartsSeriesColors(this.goBright);
|
||||
const fillAlpha = this.goBright ? 0.15 : 0.2;
|
||||
|
||||
const legendData: Array<{ name: string; itemStyle: { color: string } }> = [];
|
||||
|
||||
const data = this.data.map((item, index) => {
|
||||
const color = item.color || seriesColors[index % seriesColors.length];
|
||||
legendData.push({ name: item.name, itemStyle: { color } });
|
||||
return {
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: hexToRgba(color, fillAlpha),
|
||||
borderColor: color,
|
||||
borderWidth: 1,
|
||||
},
|
||||
emphasis: {
|
||||
itemStyle: {
|
||||
color: hexToRgba(color, fillAlpha + 0.15),
|
||||
borderColor: color,
|
||||
borderWidth: 1.5,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const formatter = this.valueFormatter;
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: (params: any) => {
|
||||
const solidColor = params.data?.itemStyle?.borderColor || params.color;
|
||||
const marker = `<span style="display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:${solidColor};"></span>`;
|
||||
return `${marker}${params.name}: <strong>${formatter(params.value)}</strong> (${params.percent}%)`;
|
||||
},
|
||||
},
|
||||
legend: this.showLegend
|
||||
? {
|
||||
orient: 'vertical',
|
||||
right: 16,
|
||||
top: 'center',
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
itemGap: 12,
|
||||
data: legendData,
|
||||
formatter: (name: string) => {
|
||||
const item = this.data.find((d) => d.name === name);
|
||||
return item ? `${name} ${formatter(item.value)}` : name;
|
||||
},
|
||||
}
|
||||
: { show: false },
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: [this.innerRadiusPercent, '85%'],
|
||||
center: this.showLegend ? ['35%', '50%'] : ['50%', '50%'],
|
||||
avoidLabelOverlap: true,
|
||||
padAngle: 2,
|
||||
itemStyle: {
|
||||
borderRadius: 4,
|
||||
},
|
||||
label: this.showLabels
|
||||
? {
|
||||
show: true,
|
||||
formatter: '{b}: {d}%',
|
||||
fontSize: 11,
|
||||
color: themeColors.textSecondary,
|
||||
textBorderColor: 'transparent',
|
||||
}
|
||||
: { show: false },
|
||||
data,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
127
ts_web/elements/00group-chart/dees-chart-donut/demo.ts
Normal file
127
ts_web/elements/00group-chart/dees-chart-donut/demo.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||
import type { DeesChartDonut } from './component.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import './component.js';
|
||||
|
||||
export const demoFunc = () => {
|
||||
const diskData = [
|
||||
{ name: 'Documents', value: 42 },
|
||||
{ name: 'Media', value: 28 },
|
||||
{ name: 'Applications', value: 15 },
|
||||
{ name: 'System', value: 10 },
|
||||
{ name: 'Other', value: 5 },
|
||||
];
|
||||
|
||||
const statusData = [
|
||||
{ name: 'Healthy', value: 156 },
|
||||
{ name: 'Warning', value: 23 },
|
||||
{ name: 'Critical', value: 8 },
|
||||
{ name: 'Unknown', value: 3 },
|
||||
];
|
||||
|
||||
const trafficData = [
|
||||
{ name: 'API', value: 45200 },
|
||||
{ name: 'Static Assets', value: 23100 },
|
||||
{ name: 'WebSocket', value: 12800 },
|
||||
{ name: 'GraphQL', value: 8900 },
|
||||
];
|
||||
|
||||
return html`
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
const diskChart = elementArg.querySelector('#disk-chart') as DeesChartDonut;
|
||||
const statusChart = elementArg.querySelector('#status-chart') as DeesChartDonut;
|
||||
const trafficChart = elementArg.querySelector('#traffic-chart') as DeesChartDonut;
|
||||
|
||||
// Wire up buttons
|
||||
const buttons = elementArg.querySelectorAll('dees-button');
|
||||
buttons.forEach((button: any) => {
|
||||
const text = button.text?.trim();
|
||||
if (text === 'Randomize') {
|
||||
button.addEventListener('click', () => {
|
||||
diskChart.data = diskData.map((d) => ({
|
||||
...d,
|
||||
value: Math.round(d.value * (0.5 + Math.random())),
|
||||
}));
|
||||
statusChart.data = statusData.map((d) => ({
|
||||
...d,
|
||||
value: Math.round(d.value * (0.3 + Math.random() * 1.4)),
|
||||
}));
|
||||
trafficChart.data = trafficData.map((d) => ({
|
||||
...d,
|
||||
value: Math.round(d.value * (0.5 + Math.random())),
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<style>
|
||||
${css`
|
||||
.demoBox {
|
||||
position: relative;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 9%)')};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
.chartRow {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.info {
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||
font-size: 12px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Geist Sans', sans-serif;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div class="demoBox">
|
||||
<div class="controls">
|
||||
<dees-button-group label="Actions:">
|
||||
<dees-button>Randomize</dees-button>
|
||||
</dees-button-group>
|
||||
</div>
|
||||
|
||||
<div class="chartRow">
|
||||
<dees-chart-donut
|
||||
id="disk-chart"
|
||||
.label=${'Disk Usage (GB)'}
|
||||
.data=${diskData}
|
||||
.valueFormatter=${(val: number) => `${val} GB`}
|
||||
></dees-chart-donut>
|
||||
|
||||
<dees-chart-donut
|
||||
id="status-chart"
|
||||
.label=${'Service Status'}
|
||||
.data=${statusData}
|
||||
.valueFormatter=${(val: number) => `${val} services`}
|
||||
.innerRadiusPercent=${'0%'}
|
||||
></dees-chart-donut>
|
||||
</div>
|
||||
|
||||
<dees-chart-donut
|
||||
id="traffic-chart"
|
||||
.label=${'Traffic Distribution'}
|
||||
.data=${trafficData}
|
||||
.valueFormatter=${(val: number) => `${(val / 1000).toFixed(1)}k req`}
|
||||
></dees-chart-donut>
|
||||
|
||||
<div class="info">
|
||||
Donut chart with configurable inner radius (set to 0% for full pie) •
|
||||
Click 'Randomize' to update data with animation
|
||||
</div>
|
||||
</div>
|
||||
</dees-demowrapper>
|
||||
`;
|
||||
};
|
||||
14
ts_web/elements/00group-chart/dees-chart-donut/styles.ts
Normal file
14
ts_web/elements/00group-chart/dees-chart-donut/styles.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { css, cssManager } from '@design.estate/dees-element';
|
||||
import { echartsBaseStyles } from '../dees-chart-echarts-styles.js';
|
||||
|
||||
export const donutStyles = [
|
||||
...echartsBaseStyles,
|
||||
css`
|
||||
:host {
|
||||
height: 360px;
|
||||
}
|
||||
.chartContainer {
|
||||
inset: 12px 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
13
ts_web/elements/00group-chart/dees-chart-donut/template.ts
Normal file
13
ts_web/elements/00group-chart/dees-chart-donut/template.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { html, type TemplateResult } from '@design.estate/dees-element';
|
||||
import type { DeesChartDonut } from './component.js';
|
||||
|
||||
export const renderChartDonut = (component: DeesChartDonut): TemplateResult => {
|
||||
return html`
|
||||
<dees-tile>
|
||||
<div slot="header" class="chartHeader">
|
||||
<span class="chartLabel">${component.label}</span>
|
||||
</div>
|
||||
<div class="chartContainer"></div>
|
||||
</dees-tile>
|
||||
`;
|
||||
};
|
||||
112
ts_web/elements/00group-chart/dees-chart-echarts-base.ts
Normal file
112
ts_web/elements/00group-chart/dees-chart-echarts-base.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
DeesElement,
|
||||
property,
|
||||
html,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { DeesServiceLibLoader, type IEchartsBundle, type IEchartsInstance } from '../../services/index.js';
|
||||
import { getEchartsThemeOptions } from './dees-chart-echarts-theme.js';
|
||||
import '../00group-layout/dees-tile/dees-tile.js';
|
||||
|
||||
/**
|
||||
* Abstract base class for ECharts-based chart components.
|
||||
* Handles library loading, chart lifecycle, resize observation, and theme switching.
|
||||
* Subclasses implement `buildOption()` to define their chart configuration.
|
||||
*/
|
||||
export abstract class DeesChartEchartsBase extends DeesElement {
|
||||
@property()
|
||||
accessor label: string = 'Untitled Chart';
|
||||
|
||||
protected chartInstance: IEchartsInstance | null = null;
|
||||
protected echartsBundle: IEchartsBundle | null = null;
|
||||
private resizeObserver: ResizeObserver | null = null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
domtools.elementBasic.setup();
|
||||
this.registerGarbageFunction(async () => {
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
if (this.chartInstance) {
|
||||
try {
|
||||
this.chartInstance.dispose();
|
||||
this.chartInstance = null;
|
||||
} catch (e) {
|
||||
console.error('Error disposing ECharts instance:', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<dees-tile>
|
||||
<div slot="header" class="chartHeader">
|
||||
<span class="chartLabel">${this.label}</span>
|
||||
</div>
|
||||
<div class="chartContainer"></div>
|
||||
</dees-tile>
|
||||
`;
|
||||
}
|
||||
|
||||
public async firstUpdated() {
|
||||
await this.domtoolsPromise;
|
||||
this.echartsBundle = await DeesServiceLibLoader.getInstance().loadEcharts();
|
||||
await new Promise(resolve => requestAnimationFrame(resolve));
|
||||
|
||||
const chartContainer = this.shadowRoot!.querySelector('.chartContainer') as HTMLDivElement;
|
||||
if (!chartContainer) return;
|
||||
|
||||
try {
|
||||
this.chartInstance = this.echartsBundle.init(chartContainer, null, { renderer: 'svg' });
|
||||
this.updateChart();
|
||||
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
this.chartInstance?.resize();
|
||||
});
|
||||
this.resizeObserver.observe(chartContainer);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize ECharts:', error);
|
||||
}
|
||||
}
|
||||
|
||||
public async updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('goBright') && this.chartInstance) {
|
||||
this.applyTheme();
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract buildOption(): Record<string, any>;
|
||||
|
||||
protected updateChart(): void {
|
||||
if (!this.chartInstance) return;
|
||||
const themeOptions = getEchartsThemeOptions(this.goBright);
|
||||
const chartOption = this.buildOption();
|
||||
// Deep-merge theme defaults with chart-specific options for nested objects
|
||||
const merged = {
|
||||
...themeOptions,
|
||||
...chartOption,
|
||||
textStyle: { ...themeOptions.textStyle, ...(chartOption.textStyle || {}) },
|
||||
tooltip: { ...themeOptions.tooltip, ...(chartOption.tooltip || {}) },
|
||||
legend: {
|
||||
...themeOptions.legend,
|
||||
...(chartOption.legend || {}),
|
||||
textStyle: { ...(themeOptions.legend?.textStyle || {}), ...(chartOption.legend?.textStyle || {}) },
|
||||
},
|
||||
};
|
||||
this.chartInstance.setOption(merged, true);
|
||||
}
|
||||
|
||||
protected applyTheme(): void {
|
||||
this.updateChart();
|
||||
}
|
||||
|
||||
public async forceResize(): Promise<void> {
|
||||
this.chartInstance?.resize();
|
||||
}
|
||||
}
|
||||
36
ts_web/elements/00group-chart/dees-chart-echarts-styles.ts
Normal file
36
ts_web/elements/00group-chart/dees-chart-echarts-styles.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { css, cssManager } from '@design.estate/dees-element';
|
||||
import { themeDefaultStyles } from '../00theme.js';
|
||||
|
||||
export const echartsBaseStyles = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
display: block;
|
||||
height: 400px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||
color: var(--dees-color-text-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
dees-tile {
|
||||
height: 100%;
|
||||
}
|
||||
.chartHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
padding: 0 8px 0 16px;
|
||||
}
|
||||
.chartLabel {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
.chartContainer {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
`,
|
||||
];
|
||||
91
ts_web/elements/00group-chart/dees-chart-echarts-theme.ts
Normal file
91
ts_web/elements/00group-chart/dees-chart-echarts-theme.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
/**
|
||||
* Shared theme utilities for ECharts-based chart components.
|
||||
* Uses the centralized themeDefaults tokens so chart colors stay in sync
|
||||
* with the rest of the dees-catalog design system.
|
||||
*
|
||||
* ECharts renders on <svg> and cannot read CSS custom properties,
|
||||
* so we reference the TypeScript source-of-truth (themeDefaults) directly.
|
||||
*
|
||||
* IMPORTANT: All colors passed to ECharts for data series must be hex or rgb/rgba.
|
||||
* ECharts cannot interpolate HSL strings during hover/emphasis animations,
|
||||
* causing them to flash black.
|
||||
*/
|
||||
|
||||
import { themeDefaults } from '../00theme.js';
|
||||
|
||||
const light = themeDefaults.colors.light;
|
||||
const dark = themeDefaults.colors.dark;
|
||||
|
||||
/**
|
||||
* Series color palette for ECharts charts.
|
||||
* Aligned with the Tailwind/shadcn-inspired palette used throughout the codebase.
|
||||
* All values are hex — ECharts requires this for animation interpolation.
|
||||
*/
|
||||
const SERIES_COLORS = {
|
||||
dark: [
|
||||
'#60a5fa', // blue-400 — softer in dark mode
|
||||
'#2dd4bf', // teal-400
|
||||
'#a78bfa', // violet-400
|
||||
'#fbbf24', // amber-400
|
||||
'#34d399', // emerald-400
|
||||
'#fb7185', // rose-400
|
||||
],
|
||||
light: [
|
||||
'#3b82f6', // blue-500
|
||||
'#14b8a6', // teal-500
|
||||
'#8b5cf6', // violet-500
|
||||
'#f59e0b', // amber-500
|
||||
'#10b981', // emerald-500
|
||||
'#f43f5e', // rose-500
|
||||
],
|
||||
};
|
||||
|
||||
export function getEchartsSeriesColors(goBright: boolean): string[] {
|
||||
return goBright ? SERIES_COLORS.light : SERIES_COLORS.dark;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a hex color to an rgba string with the given alpha.
|
||||
*/
|
||||
export function hexToRgba(hex: string, alpha: number): string {
|
||||
const r = parseInt(hex.slice(1, 3), 16);
|
||||
const g = parseInt(hex.slice(3, 5), 16);
|
||||
const b = parseInt(hex.slice(5, 7), 16);
|
||||
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
||||
}
|
||||
|
||||
export function getEchartsThemeOptions(goBright: boolean): Record<string, any> {
|
||||
const colors = goBright ? light : dark;
|
||||
return {
|
||||
backgroundColor: 'transparent',
|
||||
textStyle: {
|
||||
color: colors.textSecondary,
|
||||
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
||||
fontSize: 12,
|
||||
},
|
||||
// No global `color` array — each component sets per-item/per-series
|
||||
// colors explicitly to avoid conflicts during emphasis animations.
|
||||
tooltip: {
|
||||
backgroundColor: colors.bgPrimary,
|
||||
borderColor: colors.borderDefault,
|
||||
textStyle: {
|
||||
color: colors.textPrimary,
|
||||
fontSize: 12,
|
||||
},
|
||||
confine: true,
|
||||
},
|
||||
legend: {
|
||||
textStyle: {
|
||||
color: colors.textSecondary,
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to get the resolved theme colors object for use in buildOption().
|
||||
*/
|
||||
export function getThemeColors(goBright: boolean) {
|
||||
return goBright ? light : dark;
|
||||
}
|
||||
161
ts_web/elements/00group-chart/dees-chart-gauge/component.ts
Normal file
161
ts_web/elements/00group-chart/dees-chart-gauge/component.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import { DeesChartEchartsBase } from '../dees-chart-echarts-base.js';
|
||||
import { demoFunc } from './demo.js';
|
||||
import { gaugeStyles } from './styles.js';
|
||||
import { renderChartGauge } from './template.js';
|
||||
import { getEchartsSeriesColors, getThemeColors } from '../dees-chart-echarts-theme.js';
|
||||
|
||||
export interface IGaugeThreshold {
|
||||
value: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-chart-gauge': DeesChartGauge;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-chart-gauge')
|
||||
export class DeesChartGauge extends DeesChartEchartsBase {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['Chart'];
|
||||
|
||||
@property({ type: Number })
|
||||
accessor value: number = 0;
|
||||
|
||||
@property({ type: Number })
|
||||
accessor min: number = 0;
|
||||
|
||||
@property({ type: Number })
|
||||
accessor max: number = 100;
|
||||
|
||||
@property({ type: String })
|
||||
accessor unit: string = '%';
|
||||
|
||||
@property({ type: Array })
|
||||
accessor thresholds: IGaugeThreshold[] = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor showTicks: boolean = true;
|
||||
|
||||
public static styles = gaugeStyles;
|
||||
|
||||
public render(): TemplateResult {
|
||||
return renderChartGauge(this);
|
||||
}
|
||||
|
||||
public async updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
this.chartInstance &&
|
||||
(changedProperties.has('value') ||
|
||||
changedProperties.has('min') ||
|
||||
changedProperties.has('max') ||
|
||||
changedProperties.has('unit') ||
|
||||
changedProperties.has('thresholds') ||
|
||||
changedProperties.has('showTicks'))
|
||||
) {
|
||||
this.updateChart();
|
||||
}
|
||||
}
|
||||
|
||||
protected buildOption(): Record<string, any> {
|
||||
const colors = getThemeColors(this.goBright);
|
||||
const seriesColors = getEchartsSeriesColors(this.goBright);
|
||||
const primaryColor = seriesColors[0];
|
||||
|
||||
// Build axis line color stops from thresholds
|
||||
let axisLineColors: Array<[number, string]>;
|
||||
if (this.thresholds.length > 0) {
|
||||
const sorted = [...this.thresholds].sort((a, b) => a.value - b.value);
|
||||
axisLineColors = sorted.map((t) => [
|
||||
(t.value - this.min) / (this.max - this.min),
|
||||
t.color,
|
||||
]);
|
||||
// Ensure we end at 1
|
||||
if (axisLineColors[axisLineColors.length - 1][0] < 1) {
|
||||
axisLineColors.push([1, sorted[sorted.length - 1].color]);
|
||||
}
|
||||
} else {
|
||||
axisLineColors = [[1, primaryColor]];
|
||||
}
|
||||
|
||||
return {
|
||||
series: [
|
||||
{
|
||||
type: 'gauge',
|
||||
min: this.min,
|
||||
max: this.max,
|
||||
startAngle: 220,
|
||||
endAngle: -40,
|
||||
progress: {
|
||||
show: true,
|
||||
width: 14,
|
||||
roundCap: true,
|
||||
},
|
||||
pointer: {
|
||||
show: true,
|
||||
length: '60%',
|
||||
width: 5,
|
||||
itemStyle: {
|
||||
color: 'auto',
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
width: 14,
|
||||
color: axisLineColors,
|
||||
opacity: 0.3,
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: this.showTicks,
|
||||
distance: -20,
|
||||
length: 6,
|
||||
lineStyle: {
|
||||
color: colors.borderStrong,
|
||||
width: 1,
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: this.showTicks,
|
||||
distance: -24,
|
||||
length: 10,
|
||||
lineStyle: {
|
||||
color: colors.textMuted,
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
show: this.showTicks,
|
||||
distance: 30,
|
||||
color: colors.textMuted,
|
||||
fontSize: 11,
|
||||
},
|
||||
detail: {
|
||||
valueAnimation: true,
|
||||
fontSize: 28,
|
||||
fontWeight: 600,
|
||||
offsetCenter: [0, '65%'],
|
||||
color: colors.textPrimary,
|
||||
formatter: `{value}${this.unit}`,
|
||||
},
|
||||
title: {
|
||||
show: false,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: this.value,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
125
ts_web/elements/00group-chart/dees-chart-gauge/demo.ts
Normal file
125
ts_web/elements/00group-chart/dees-chart-gauge/demo.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||
import type { DeesChartGauge } from './component.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import './component.js';
|
||||
|
||||
export const demoFunc = () => {
|
||||
const defaultThresholds = [
|
||||
{ value: 60, color: 'hsl(142 76% 36%)' },
|
||||
{ value: 80, color: 'hsl(38 92% 50%)' },
|
||||
{ value: 100, color: 'hsl(0 72% 50%)' },
|
||||
];
|
||||
|
||||
return html`
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
const cpuGauge = elementArg.querySelector('#cpu-gauge') as DeesChartGauge;
|
||||
const memGauge = elementArg.querySelector('#mem-gauge') as DeesChartGauge;
|
||||
const slaGauge = elementArg.querySelector('#sla-gauge') as DeesChartGauge;
|
||||
|
||||
let animInterval: number | null = null;
|
||||
|
||||
const buttons = elementArg.querySelectorAll('dees-button');
|
||||
buttons.forEach((button: any) => {
|
||||
const text = button.text?.trim();
|
||||
if (text === 'Animate') {
|
||||
button.addEventListener('click', () => {
|
||||
if (animInterval) return;
|
||||
animInterval = window.setInterval(() => {
|
||||
cpuGauge.value = Math.round(30 + Math.random() * 60);
|
||||
memGauge.value = Math.round(40 + Math.random() * 50);
|
||||
slaGauge.value = Math.round((95 + Math.random() * 5) * 100) / 100;
|
||||
}, 2000);
|
||||
});
|
||||
} else if (text === 'Stop') {
|
||||
button.addEventListener('click', () => {
|
||||
if (animInterval) {
|
||||
window.clearInterval(animInterval);
|
||||
animInterval = null;
|
||||
}
|
||||
});
|
||||
} else if (text === 'Spike') {
|
||||
button.addEventListener('click', () => {
|
||||
cpuGauge.value = 95;
|
||||
memGauge.value = 88;
|
||||
slaGauge.value = 96.5;
|
||||
});
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<style>
|
||||
${css`
|
||||
.demoBox {
|
||||
position: relative;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 9%)')};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
.gaugeRow {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.info {
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||
font-size: 12px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Geist Sans', sans-serif;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div class="demoBox">
|
||||
<div class="controls">
|
||||
<dees-button-group label="Actions:">
|
||||
<dees-button>Animate</dees-button>
|
||||
<dees-button>Stop</dees-button>
|
||||
<dees-button>Spike</dees-button>
|
||||
</dees-button-group>
|
||||
</div>
|
||||
|
||||
<div class="gaugeRow">
|
||||
<dees-chart-gauge
|
||||
id="cpu-gauge"
|
||||
.label=${'CPU Usage'}
|
||||
.value=${42}
|
||||
.unit=${'%'}
|
||||
.thresholds=${defaultThresholds}
|
||||
></dees-chart-gauge>
|
||||
|
||||
<dees-chart-gauge
|
||||
id="mem-gauge"
|
||||
.label=${'Memory Usage'}
|
||||
.value=${67}
|
||||
.unit=${'%'}
|
||||
.thresholds=${defaultThresholds}
|
||||
></dees-chart-gauge>
|
||||
|
||||
<dees-chart-gauge
|
||||
id="sla-gauge"
|
||||
.label=${'SLA Uptime'}
|
||||
.value=${99.8}
|
||||
.min=${95}
|
||||
.max=${100}
|
||||
.unit=${'%'}
|
||||
.showTicks=${true}
|
||||
></dees-chart-gauge>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
Gauge chart with animated value transitions and threshold coloring •
|
||||
Click 'Animate' for live updates, 'Spike' to simulate high load
|
||||
</div>
|
||||
</div>
|
||||
</dees-demowrapper>
|
||||
`;
|
||||
};
|
||||
11
ts_web/elements/00group-chart/dees-chart-gauge/styles.ts
Normal file
11
ts_web/elements/00group-chart/dees-chart-gauge/styles.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { css } from '@design.estate/dees-element';
|
||||
import { echartsBaseStyles } from '../dees-chart-echarts-styles.js';
|
||||
|
||||
export const gaugeStyles = [
|
||||
...echartsBaseStyles,
|
||||
css`
|
||||
:host {
|
||||
height: 320px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
13
ts_web/elements/00group-chart/dees-chart-gauge/template.ts
Normal file
13
ts_web/elements/00group-chart/dees-chart-gauge/template.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { html, type TemplateResult } from '@design.estate/dees-element';
|
||||
import type { DeesChartGauge } from './component.js';
|
||||
|
||||
export const renderChartGauge = (component: DeesChartGauge): TemplateResult => {
|
||||
return html`
|
||||
<dees-tile>
|
||||
<div slot="header" class="chartHeader">
|
||||
<span class="chartLabel">${component.label}</span>
|
||||
</div>
|
||||
<div class="chartContainer"></div>
|
||||
</dees-tile>
|
||||
`;
|
||||
};
|
||||
@@ -106,7 +106,7 @@ export class DeesChartLog extends DeesElement {
|
||||
display: block;
|
||||
height: 400px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
dees-tile {
|
||||
@@ -124,7 +124,7 @@ export class DeesChartLog extends DeesElement {
|
||||
.title {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@@ -141,10 +141,10 @@ export class DeesChartLog extends DeesElement {
|
||||
flex: 1;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 4px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
color: var(--dees-color-text-primary);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ export class DeesChartLog extends DeesElement {
|
||||
}
|
||||
|
||||
.search-box input::placeholder {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.search-nav {
|
||||
@@ -164,35 +164,35 @@ export class DeesChartLog extends DeesElement {
|
||||
.search-nav button {
|
||||
padding: 4px 6px;
|
||||
font-size: 11px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 3px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
cursor: pointer;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.search-nav button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 20%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.filter-toggle {
|
||||
padding: 4px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 4px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.filter-toggle:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 20%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.filter-toggle.active {
|
||||
@@ -208,11 +208,11 @@ export class DeesChartLog extends DeesElement {
|
||||
}
|
||||
|
||||
.control-button {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 4px;
|
||||
padding: 4px 10px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
@@ -220,9 +220,9 @@ export class DeesChartLog extends DeesElement {
|
||||
}
|
||||
|
||||
.control-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 20%)')};
|
||||
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 25%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||
background: var(--dees-color-hover);
|
||||
border-color: var(--dees-color-border-strong);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.control-button.active {
|
||||
@@ -247,7 +247,7 @@ export class DeesChartLog extends DeesElement {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -299,7 +299,7 @@ export class DeesChartLog extends DeesElement {
|
||||
|
||||
.metric.rate {
|
||||
margin-left: auto;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.metric.rate::before {
|
||||
@@ -458,6 +458,9 @@ export class DeesChartLog extends DeesElement {
|
||||
|
||||
public updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
if (changedProperties.has('goBright') && this.terminal) {
|
||||
this.terminal.options.theme = this.getTerminalTheme();
|
||||
}
|
||||
if (changedProperties.has('logEntries') && this.terminalReady && this.logEntries.length > 0) {
|
||||
const oldEntries: ILogEntry[] = changedProperties.get('logEntries') || [];
|
||||
const newEntries = this.logEntries;
|
||||
@@ -504,7 +507,7 @@ export class DeesChartLog extends DeesElement {
|
||||
}
|
||||
|
||||
private getTerminalTheme() {
|
||||
const isDark = this.domtoolsInstance?.themeManager?.isDarkMode ?? true;
|
||||
const isDark = !this.goBright;
|
||||
return isDark
|
||||
? {
|
||||
background: '#0a0a0a',
|
||||
|
||||
132
ts_web/elements/00group-chart/dees-chart-radar/component.ts
Normal file
132
ts_web/elements/00group-chart/dees-chart-radar/component.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import {
|
||||
customElement,
|
||||
property,
|
||||
type TemplateResult,
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import { DeesChartEchartsBase } from '../dees-chart-echarts-base.js';
|
||||
import { demoFunc } from './demo.js';
|
||||
import { radarStyles } from './styles.js';
|
||||
import { renderChartRadar } from './template.js';
|
||||
import { getEchartsSeriesColors, getThemeColors, hexToRgba } from '../dees-chart-echarts-theme.js';
|
||||
|
||||
export interface IRadarIndicator {
|
||||
name: string;
|
||||
max: number;
|
||||
}
|
||||
|
||||
export interface IRadarSeriesItem {
|
||||
name: string;
|
||||
values: number[];
|
||||
color?: string;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-chart-radar': DeesChartRadar;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-chart-radar')
|
||||
export class DeesChartRadar extends DeesChartEchartsBase {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['Chart'];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor indicators: IRadarIndicator[] = [];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor series: IRadarSeriesItem[] = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor showLegend: boolean = true;
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor fillArea: boolean = true;
|
||||
|
||||
public static styles = radarStyles;
|
||||
|
||||
public render(): TemplateResult {
|
||||
return renderChartRadar(this);
|
||||
}
|
||||
|
||||
public async updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
if (
|
||||
this.chartInstance &&
|
||||
(changedProperties.has('indicators') ||
|
||||
changedProperties.has('series') ||
|
||||
changedProperties.has('showLegend') ||
|
||||
changedProperties.has('fillArea'))
|
||||
) {
|
||||
this.updateChart();
|
||||
}
|
||||
}
|
||||
|
||||
protected buildOption(): Record<string, any> {
|
||||
const colors = getThemeColors(this.goBright);
|
||||
const seriesColors = getEchartsSeriesColors(this.goBright);
|
||||
|
||||
const fillAlpha = this.goBright ? 0.1 : 0.15;
|
||||
|
||||
const seriesData = this.series.map((s, index) => {
|
||||
const color = s.color || seriesColors[index % seriesColors.length];
|
||||
return {
|
||||
name: s.name,
|
||||
value: s.values,
|
||||
itemStyle: { color, borderColor: color, borderWidth: 1 },
|
||||
lineStyle: { color, width: 1.5 },
|
||||
areaStyle: this.fillArea ? { color: hexToRgba(color, fillAlpha) } : undefined,
|
||||
symbol: 'circle',
|
||||
symbolSize: 5,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
},
|
||||
legend: this.showLegend && this.series.length > 1
|
||||
? { bottom: 8, itemWidth: 10, itemHeight: 10 }
|
||||
: { show: false },
|
||||
radar: {
|
||||
indicator: this.indicators.map((ind) => ({
|
||||
name: ind.name,
|
||||
max: ind.max,
|
||||
})),
|
||||
shape: 'polygon',
|
||||
splitNumber: 4,
|
||||
axisName: {
|
||||
color: colors.textSecondary,
|
||||
fontSize: 11,
|
||||
},
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: [colors.bgTertiary, colors.bgSecondary],
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: colors.borderDefault,
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: colors.borderDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'radar',
|
||||
data: seriesData,
|
||||
emphasis: {
|
||||
lineStyle: {
|
||||
width: 3,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
119
ts_web/elements/00group-chart/dees-chart-radar/demo.ts
Normal file
119
ts_web/elements/00group-chart/dees-chart-radar/demo.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { html, css, cssManager } from '@design.estate/dees-element';
|
||||
import type { DeesChartRadar } from './component.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import './component.js';
|
||||
|
||||
export const demoFunc = () => {
|
||||
const indicators = [
|
||||
{ name: 'Latency', max: 100 },
|
||||
{ name: 'Throughput', max: 100 },
|
||||
{ name: 'Availability', max: 100 },
|
||||
{ name: 'Error Rate', max: 100 },
|
||||
{ name: 'Saturation', max: 100 },
|
||||
{ name: 'Security', max: 100 },
|
||||
];
|
||||
|
||||
const series = [
|
||||
{ name: 'Service A', values: [85, 90, 99, 12, 45, 78] },
|
||||
{ name: 'Service B', values: [70, 65, 95, 28, 60, 90] },
|
||||
];
|
||||
|
||||
const singleIndicators = [
|
||||
{ name: 'Speed', max: 10 },
|
||||
{ name: 'Reliability', max: 10 },
|
||||
{ name: 'Comfort', max: 10 },
|
||||
{ name: 'Safety', max: 10 },
|
||||
{ name: 'Cost', max: 10 },
|
||||
];
|
||||
|
||||
const singleSeries = [
|
||||
{ name: 'Rating', values: [8.5, 9.2, 7.0, 9.5, 6.0] },
|
||||
];
|
||||
|
||||
return html`
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
const compChart = elementArg.querySelector('#comparison-chart') as DeesChartRadar;
|
||||
const singleChart = elementArg.querySelector('#single-chart') as DeesChartRadar;
|
||||
|
||||
const buttons = elementArg.querySelectorAll('dees-button');
|
||||
buttons.forEach((button: any) => {
|
||||
const text = button.text?.trim();
|
||||
if (text === 'Randomize') {
|
||||
button.addEventListener('click', () => {
|
||||
compChart.series = series.map((s) => ({
|
||||
...s,
|
||||
values: s.values.map(() => Math.round(20 + Math.random() * 80)),
|
||||
}));
|
||||
singleChart.series = singleSeries.map((s) => ({
|
||||
...s,
|
||||
values: s.values.map(() => Math.round((2 + Math.random() * 8) * 10) / 10),
|
||||
}));
|
||||
});
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<style>
|
||||
${css`
|
||||
.demoBox {
|
||||
position: relative;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 9%)')};
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 40px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
.chartRow {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.info {
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 56.9%)', 'hsl(215 20.2% 55.1%)')};
|
||||
font-size: 12px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Geist Sans', sans-serif;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
<div class="demoBox">
|
||||
<div class="controls">
|
||||
<dees-button-group label="Actions:">
|
||||
<dees-button>Randomize</dees-button>
|
||||
</dees-button-group>
|
||||
</div>
|
||||
|
||||
<div class="chartRow">
|
||||
<dees-chart-radar
|
||||
id="comparison-chart"
|
||||
.label=${'Service Health Comparison'}
|
||||
.indicators=${indicators}
|
||||
.series=${series}
|
||||
></dees-chart-radar>
|
||||
|
||||
<dees-chart-radar
|
||||
id="single-chart"
|
||||
.label=${'Product Rating'}
|
||||
.indicators=${singleIndicators}
|
||||
.series=${singleSeries}
|
||||
.fillArea=${true}
|
||||
></dees-chart-radar>
|
||||
</div>
|
||||
|
||||
<div class="info">
|
||||
Radar chart for multi-dimensional comparison •
|
||||
Supports multiple overlay series and configurable fill •
|
||||
Click 'Randomize' to update data
|
||||
</div>
|
||||
</div>
|
||||
</dees-demowrapper>
|
||||
`;
|
||||
};
|
||||
7
ts_web/elements/00group-chart/dees-chart-radar/styles.ts
Normal file
7
ts_web/elements/00group-chart/dees-chart-radar/styles.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { css } from '@design.estate/dees-element';
|
||||
import { echartsBaseStyles } from '../dees-chart-echarts-styles.js';
|
||||
|
||||
export const radarStyles = [
|
||||
...echartsBaseStyles,
|
||||
css``,
|
||||
];
|
||||
13
ts_web/elements/00group-chart/dees-chart-radar/template.ts
Normal file
13
ts_web/elements/00group-chart/dees-chart-radar/template.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { html, type TemplateResult } from '@design.estate/dees-element';
|
||||
import type { DeesChartRadar } from './component.js';
|
||||
|
||||
export const renderChartRadar = (component: DeesChartRadar): TemplateResult => {
|
||||
return html`
|
||||
<dees-tile>
|
||||
<div slot="header" class="chartHeader">
|
||||
<span class="chartLabel">${component.label}</span>
|
||||
</div>
|
||||
<div class="chartContainer"></div>
|
||||
</dees-tile>
|
||||
`;
|
||||
};
|
||||
@@ -1,3 +1,7 @@
|
||||
// Chart Components
|
||||
export * from './dees-chart-area/index.js';
|
||||
export * from './dees-chart-bar/index.js';
|
||||
export * from './dees-chart-donut/index.js';
|
||||
export * from './dees-chart-gauge/index.js';
|
||||
export * from './dees-chart-log/index.js';
|
||||
export * from './dees-chart-radar/index.js';
|
||||
|
||||
@@ -2,6 +2,7 @@ import { demoFunc } from './dees-dataview-codebox.demo.js';
|
||||
import {
|
||||
DeesElement,
|
||||
html,
|
||||
css,
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
property,
|
||||
@@ -9,6 +10,7 @@ import {
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
import { cssGeistFontFamily, cssMonoFontFamily } from '../../00fonts.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
import type { HLJSApi } from 'highlight.js';
|
||||
|
||||
@@ -39,6 +41,11 @@ export class DeesDataviewCodebox extends DeesElement {
|
||||
})
|
||||
accessor codeToDisplay: string = '';
|
||||
|
||||
public static styles = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@@ -53,16 +60,14 @@ export class DeesDataviewCodebox extends DeesElement {
|
||||
text-align: left;
|
||||
font-size: 16px;
|
||||
font-family: ${cssGeistFontFamily};
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
dees-tile {
|
||||
height: 100%;
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.appbar {
|
||||
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
||||
color: var(--dees-color-text-muted);
|
||||
height: 32px;
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
@@ -80,7 +85,7 @@ export class DeesDataviewCodebox extends DeesElement {
|
||||
}
|
||||
|
||||
.bottomBar {
|
||||
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
||||
color: var(--dees-color-text-muted);
|
||||
height: 28px;
|
||||
font-size: 11px;
|
||||
line-height: 28px;
|
||||
@@ -117,15 +122,13 @@ export class DeesDataviewCodebox extends DeesElement {
|
||||
display: grid;
|
||||
grid-template-columns: 50px auto;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.lineNumbers {
|
||||
color: ${cssManager.bdTheme('#71717a', '#52525b')};
|
||||
color: var(--dees-color-text-muted);
|
||||
padding: 24px 16px 0px 0px;
|
||||
text-align: right;
|
||||
border-right: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
border-right: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
.lineCounter:last-child {
|
||||
|
||||
@@ -42,7 +42,7 @@ export class DeesDataviewStatusobject extends DeesElement {
|
||||
}
|
||||
|
||||
dees-tile {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ export class DeesDataviewStatusobject extends DeesElement {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -78,21 +78,21 @@ export class DeesDataviewStatusobject extends DeesElement {
|
||||
.copyMain {
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
text-align: center;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
user-select: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.copyMain:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||
background: var(--dees-color-hover);
|
||||
border-color: var(--dees-color-border-strong);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.copyMain:active {
|
||||
@@ -121,7 +121,7 @@ export class DeesDataviewStatusobject extends DeesElement {
|
||||
gap: 10px;
|
||||
height: 36px;
|
||||
padding: 0 16px;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 10%)')};
|
||||
border-bottom: 1px solid var(--dees-color-border-subtle);
|
||||
transition: background-color 0.15s ease;
|
||||
cursor: context-menu;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export class DeesDataviewStatusobject extends DeesElement {
|
||||
.detail .detailsText .label {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
letter-spacing: -0.01em;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
@@ -167,7 +167,7 @@ export class DeesDataviewStatusobject extends DeesElement {
|
||||
}
|
||||
|
||||
.bottomBar {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
height: 28px;
|
||||
font-size: 11px;
|
||||
line-height: 28px;
|
||||
|
||||
@@ -160,7 +160,7 @@ export class DeesStatsGrid extends DeesElement {
|
||||
.grid-title {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
color: var(--dees-color-text-primary);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ export class DeesStatsGrid extends DeesElement {
|
||||
}
|
||||
|
||||
.stats-tile:hover::part(outer) {
|
||||
border-color: ${cssManager.bdTheme('#d0d0d0', '#2a2a2a')};
|
||||
border-color: var(--dees-color-border-strong);
|
||||
}
|
||||
|
||||
.stats-tile.clickable {
|
||||
@@ -281,7 +281,7 @@ export class DeesStatsGrid extends DeesElement {
|
||||
|
||||
.gauge-background {
|
||||
fill: none;
|
||||
stroke: ${cssManager.bdTheme('#e8e8e8', '#1a1a1a')};
|
||||
stroke: var(--dees-color-border-default);
|
||||
stroke-width: 6;
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ export class DeesStatsGrid extends DeesElement {
|
||||
.percentage-bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: ${cssManager.bdTheme('#e8e8e8', '#1a1a1a')};
|
||||
background: var(--dees-color-border-default);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
margin-top: auto;
|
||||
@@ -337,7 +337,7 @@ export class DeesStatsGrid extends DeesElement {
|
||||
|
||||
.percentage-fill {
|
||||
height: 100%;
|
||||
background: ${cssManager.bdTheme('#333333', '#e0e0e0')};
|
||||
background: var(--dees-color-text-muted);
|
||||
transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
@@ -386,14 +386,14 @@ export class DeesStatsGrid extends DeesElement {
|
||||
.multi-percentage-bar {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: ${cssManager.bdTheme('#e8e8e8', '#1a1a1a')};
|
||||
background: var(--dees-color-border-default);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.multi-percentage-fill {
|
||||
height: 100%;
|
||||
background: ${cssManager.bdTheme('#333333', '#e0e0e0')};
|
||||
background: var(--dees-color-text-muted);
|
||||
transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border-radius: 2px;
|
||||
}
|
||||
@@ -464,7 +464,7 @@ export class DeesStatsGrid extends DeesElement {
|
||||
.cpu-core-bar-wrapper {
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
background: ${cssManager.bdTheme('#e8e8e8', '#1a1a1a')};
|
||||
background: var(--dees-color-border-default);
|
||||
border-radius: 2px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
@@ -477,21 +477,21 @@ export class DeesStatsGrid extends DeesElement {
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
background: ${cssManager.bdTheme('#666666', '#888888')};
|
||||
background: var(--dees-color-text-muted);
|
||||
transition: height 0.3s cubic-bezier(0.4, 0, 0.2, 1), background-color 0.3s ease;
|
||||
border-radius: 2px 2px 0 0;
|
||||
}
|
||||
|
||||
.cpu-core-bar-fill.low {
|
||||
background: ${cssManager.bdTheme('hsl(142.1 76.2% 36.3%)', 'hsl(142.1 70.6% 45.3%)')};
|
||||
background: var(--dees-color-accent-success);
|
||||
}
|
||||
|
||||
.cpu-core-bar-fill.medium {
|
||||
background: ${cssManager.bdTheme('hsl(45.4 93.4% 47.5%)', 'hsl(45.4 93.4% 47.5%)')};
|
||||
background: var(--dees-color-accent-warning);
|
||||
}
|
||||
|
||||
.cpu-core-bar-fill.high {
|
||||
background: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 84.2% 60.2%)')};
|
||||
background: var(--dees-color-accent-error);
|
||||
}
|
||||
|
||||
.cpu-core-label {
|
||||
@@ -531,24 +531,24 @@ export class DeesStatsGrid extends DeesElement {
|
||||
.partition-bar {
|
||||
width: 100%;
|
||||
height: 6px;
|
||||
background: ${cssManager.bdTheme('#e8e8e8', '#1a1a1a')};
|
||||
background: var(--dees-color-border-default);
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.partition-bar-fill {
|
||||
height: 100%;
|
||||
background: ${cssManager.bdTheme('#333333', '#e0e0e0')};
|
||||
background: var(--dees-color-text-muted);
|
||||
transition: width 0.6s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.partition-bar-fill.warning {
|
||||
background: ${cssManager.bdTheme('hsl(45.4 93.4% 47.5%)', 'hsl(45.4 93.4% 47.5%)')};
|
||||
background: var(--dees-color-accent-warning);
|
||||
}
|
||||
|
||||
.partition-bar-fill.critical {
|
||||
background: ${cssManager.bdTheme('hsl(0 84.2% 60.2%)', 'hsl(0 84.2% 60.2%)')};
|
||||
background: var(--dees-color-accent-error);
|
||||
}
|
||||
|
||||
.partition-stats {
|
||||
@@ -695,7 +695,7 @@ export class DeesStatsGrid extends DeesElement {
|
||||
.disk-health-bar {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background: ${cssManager.bdTheme('#e8e8e8', '#1a1a1a')};
|
||||
background: var(--dees-color-border-default);
|
||||
border-radius: 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -771,7 +771,7 @@ export class DeesStatsGrid extends DeesElement {
|
||||
|
||||
.trend-line {
|
||||
fill: none;
|
||||
stroke: ${cssManager.bdTheme('#999999', '#666666')};
|
||||
stroke: var(--dees-color-text-muted);
|
||||
stroke-width: 1.5;
|
||||
stroke-linejoin: round;
|
||||
stroke-linecap: round;
|
||||
|
||||
@@ -13,7 +13,7 @@ export const tableStyles: CSSResult[] = [
|
||||
}
|
||||
|
||||
dees-tile {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
font-family: ${cssGeistFontFamily};
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
@@ -41,7 +41,7 @@ export const tableStyles: CSSResult[] = [
|
||||
.heading1 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
@@ -69,18 +69,18 @@ export const tableStyles: CSSResult[] = [
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
background: transparent;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.headerAction:hover {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
background: var(--dees-color-hover);
|
||||
border-color: var(--dees-color-border-strong);
|
||||
}
|
||||
|
||||
.headerAction dees-icon {
|
||||
@@ -93,8 +93,8 @@ export const tableStyles: CSSResult[] = [
|
||||
grid-gap: 16px;
|
||||
grid-template-columns: 1fr max-content;
|
||||
padding: 16px 24px;
|
||||
background: ${cssManager.bdTheme('hsl(210 40% 98%)', 'hsl(0 0% 3.9%)')};
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
background: var(--dees-color-bg-secondary);
|
||||
border-bottom: 1px solid var(--dees-color-border-default);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
@@ -157,7 +157,7 @@ export const tableStyles: CSSResult[] = [
|
||||
|
||||
thead {
|
||||
background: ${cssManager.bdTheme('hsl(210 40% 96.1%)', 'hsl(0 0% 9%)')};
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||
border-bottom: 1px solid var(--dees-color-border-strong);
|
||||
}
|
||||
:host([sticky-header]) thead th {
|
||||
position: sticky;
|
||||
@@ -172,7 +172,7 @@ export const tableStyles: CSSResult[] = [
|
||||
|
||||
/* Default horizontal lines (bottom border only) */
|
||||
tbody tr {
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-bottom: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
tbody tr:last-child {
|
||||
@@ -181,8 +181,8 @@ export const tableStyles: CSSResult[] = [
|
||||
|
||||
/* Full horizontal lines when enabled */
|
||||
:host([show-horizontal-lines]) tbody tr {
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-top: 1px solid var(--dees-color-border-default);
|
||||
border-bottom: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
:host([show-horizontal-lines]) tbody tr:first-child {
|
||||
@@ -190,7 +190,7 @@ export const tableStyles: CSSResult[] = [
|
||||
}
|
||||
|
||||
:host([show-horizontal-lines]) tbody tr:last-child {
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-bottom: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
tbody tr:hover {
|
||||
@@ -222,13 +222,13 @@ export const tableStyles: CSSResult[] = [
|
||||
|
||||
/* Grid mode - shows both vertical and horizontal lines */
|
||||
:host([show-grid]) th {
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
:host([show-grid]) td {
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
}
|
||||
@@ -251,12 +251,12 @@ export const tableStyles: CSSResult[] = [
|
||||
tbody td.actionsCol {
|
||||
position: sticky;
|
||||
right: 0;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
}
|
||||
thead th.actionsCol { z-index: 3; }
|
||||
tbody td.actionsCol {
|
||||
z-index: 1;
|
||||
box-shadow: -1px 0 0 0 ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
box-shadow: -1px 0 0 0 var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
tbody tr.selected {
|
||||
@@ -278,18 +278,18 @@ export const tableStyles: CSSResult[] = [
|
||||
}
|
||||
|
||||
:host([show-vertical-lines]) th {
|
||||
border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-right: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 8px 16px;
|
||||
vertical-align: middle;
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
:host([show-vertical-lines]) td {
|
||||
border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-right: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
th:first-child,
|
||||
@@ -327,10 +327,10 @@ export const tableStyles: CSSResult[] = [
|
||||
height: calc(100% - 8px);
|
||||
padding: 0 12px;
|
||||
outline: none;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 6px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
@@ -355,9 +355,9 @@ export const tableStyles: CSSResult[] = [
|
||||
padding: 6px 8px;
|
||||
font-size: 13px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
.actionsContainer {
|
||||
display: flex;
|
||||
@@ -398,7 +398,7 @@ export const tableStyles: CSSResult[] = [
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
font-size: 11px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45%)', 'hsl(0 0% 55%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,12 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
@state()
|
||||
accessor copySuccess: boolean = false;
|
||||
|
||||
@state()
|
||||
accessor lineCount: number = 0;
|
||||
|
||||
@state()
|
||||
accessor cursorPosition: { line: number; column: number } = { line: 1, column: 1 };
|
||||
|
||||
private editorElement: DeesWorkspaceMonaco | null = null;
|
||||
|
||||
public static styles = [
|
||||
@@ -142,16 +148,16 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 12%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 20%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.language-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 15%)')};
|
||||
background: var(--dees-color-hover);
|
||||
}
|
||||
|
||||
.language-dropdown {
|
||||
@@ -159,8 +165,8 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
top: 100%;
|
||||
left: 0;
|
||||
margin-top: 4px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 20%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
z-index: 100;
|
||||
@@ -173,16 +179,16 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
transition: background 0.15s ease;
|
||||
}
|
||||
|
||||
.language-option:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95%)', 'hsl(0 0% 15%)')};
|
||||
background: var(--dees-color-hover);
|
||||
}
|
||||
|
||||
.language-option.selected {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 20%)')};
|
||||
background: var(--dees-color-active);
|
||||
}
|
||||
|
||||
.toolbar-button {
|
||||
@@ -195,18 +201,18 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45%)', 'hsl(0 0% 60%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.toolbar-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 15%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
|
||||
.toolbar-button.active {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
|
||||
background: var(--dees-color-active);
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
|
||||
.toolbar-button.success {
|
||||
@@ -214,9 +220,8 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
}
|
||||
|
||||
.editor-wrapper {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
dees-workspace-monaco {
|
||||
@@ -224,13 +229,44 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Match Monaco background to tile */
|
||||
dees-workspace-monaco::part(container) {
|
||||
background: var(--dees-color-bg-primary);
|
||||
}
|
||||
|
||||
.toolbar-divider {
|
||||
width: 1px;
|
||||
height: 20px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
|
||||
background: var(--dees-color-border-default);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.editor-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
height: 28px;
|
||||
font-size: 11px;
|
||||
color: var(--dees-color-text-muted);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.footer-left,
|
||||
.footer-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.footer-separator {
|
||||
width: 1px;
|
||||
height: 12px;
|
||||
background: var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
:host([disabled]) .code-container {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
@@ -315,6 +351,16 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
@content-change=${this.handleContentChange}
|
||||
></dees-workspace-monaco>
|
||||
</div>
|
||||
<div slot="footer" class="editor-footer">
|
||||
<div class="footer-left">
|
||||
<span>Ln ${this.cursorPosition.line}, Col ${this.cursorPosition.column}</span>
|
||||
<div class="footer-separator"></div>
|
||||
<span>${this.lineCount} line${this.lineCount !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<span>${(LANGUAGES.find(l => l.key === this.language) || LANGUAGES[0]).label}</span>
|
||||
</div>
|
||||
</div>
|
||||
</dees-tile>
|
||||
</div>
|
||||
`;
|
||||
@@ -330,6 +376,49 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
this.changeSubject.next(this as any);
|
||||
}
|
||||
});
|
||||
|
||||
// Track cursor position and line count
|
||||
const editor = await this.editorElement.editorDeferred.promise;
|
||||
|
||||
// Set initial line count
|
||||
const model = editor.getModel();
|
||||
if (model) {
|
||||
this.lineCount = model.getLineCount();
|
||||
model.onDidChangeContent(() => {
|
||||
this.lineCount = model.getLineCount();
|
||||
});
|
||||
}
|
||||
|
||||
// Track cursor position
|
||||
editor.onDidChangeCursorPosition((e) => {
|
||||
this.cursorPosition = { line: e.position.lineNumber, column: e.position.column };
|
||||
});
|
||||
|
||||
// Override Monaco editor background to match tile
|
||||
const domtoolsInstance = await this.editorElement.domtoolsPromise;
|
||||
const updateEditorBg = (isBright: boolean) => {
|
||||
const bg = isBright ? '#ffffff' : '#0a0a0a';
|
||||
editor.updateOptions({});
|
||||
// Override via Monaco's theme API
|
||||
(window as any).monaco?.editor?.defineTheme?.('dees-light', {
|
||||
base: 'vs',
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: { 'editor.background': bg },
|
||||
});
|
||||
(window as any).monaco?.editor?.defineTheme?.('dees-dark', {
|
||||
base: 'vs-dark',
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: { 'editor.background': bg },
|
||||
});
|
||||
editor.updateOptions({ theme: isBright ? 'dees-light' : 'dees-dark' });
|
||||
};
|
||||
|
||||
updateEditorBg(domtoolsInstance.themeManager.goBrightBoolean);
|
||||
domtoolsInstance.themeManager.themeObservable.subscribe((goBright: boolean) => {
|
||||
updateEditorBg(goBright);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -419,23 +508,15 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
const toolbar = modal.shadowRoot?.querySelector('.modal-toolbar');
|
||||
if (!toolbar) return;
|
||||
|
||||
// Update language button text
|
||||
const langBtn = toolbar.querySelector('.language-button span');
|
||||
if (langBtn) langBtn.textContent = getLanguageLabel();
|
||||
|
||||
// Update word wrap button
|
||||
const wrapBtn = toolbar.querySelector('.wrap-btn') as HTMLElement;
|
||||
if (wrapBtn) {
|
||||
wrapBtn.classList.toggle('active', modalWordWrap === 'on');
|
||||
}
|
||||
if (wrapBtn) wrapBtn.classList.toggle('active', modalWordWrap === 'on');
|
||||
|
||||
// Update line numbers button
|
||||
const linesBtn = toolbar.querySelector('.lines-btn') as HTMLElement;
|
||||
if (linesBtn) {
|
||||
linesBtn.classList.toggle('active', modalShowLineNumbers);
|
||||
}
|
||||
if (linesBtn) linesBtn.classList.toggle('active', modalShowLineNumbers);
|
||||
|
||||
// Update copy button
|
||||
const copyBtn = toolbar.querySelector('.copy-btn') as HTMLElement;
|
||||
const copyIcon = copyBtn?.querySelector('dees-icon') as any;
|
||||
if (copyBtn && copyIcon) {
|
||||
@@ -443,13 +524,28 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
copyIcon.icon = modalCopySuccess ? 'lucide:Check' : 'lucide:Copy';
|
||||
}
|
||||
|
||||
// Update dropdown visibility
|
||||
const dropdown = toolbar.querySelector('.language-dropdown') as HTMLElement;
|
||||
if (dropdown) {
|
||||
dropdown.style.display = modalLanguageDropdownOpen ? 'block' : 'none';
|
||||
}
|
||||
if (dropdown) dropdown.style.display = modalLanguageDropdownOpen ? 'block' : 'none';
|
||||
};
|
||||
|
||||
// Helper to update footer UI
|
||||
const updateFooterUI = (modal: DeesModal) => {
|
||||
const footer = modal.shadowRoot?.querySelector('.modal-footer');
|
||||
if (!footer) return;
|
||||
|
||||
const cursorEl = footer.querySelector('.footer-cursor');
|
||||
const linesEl = footer.querySelector('.footer-lines');
|
||||
const langEl = footer.querySelector('.footer-lang');
|
||||
|
||||
if (cursorEl) cursorEl.textContent = `Ln ${modalCursorLine}, Col ${modalCursorCol}`;
|
||||
if (linesEl) linesEl.textContent = `${modalLineCount} line${modalLineCount !== 1 ? 's' : ''}`;
|
||||
if (langEl) langEl.textContent = getLanguageLabel();
|
||||
};
|
||||
|
||||
let modalCursorLine = 1;
|
||||
let modalCursorCol = 1;
|
||||
let modalLineCount = currentValue.split('\n').length;
|
||||
|
||||
const modal = await DeesModal.createAndShow({
|
||||
heading: this.label || 'Code Editor',
|
||||
width: 'fullscreen',
|
||||
@@ -460,9 +556,7 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 7%)')};
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
padding: 4px 12px;
|
||||
gap: 8px;
|
||||
}
|
||||
.modal-toolbar .toolbar-left {
|
||||
@@ -555,9 +649,30 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
}
|
||||
.modal-editor-wrapper {
|
||||
position: relative;
|
||||
height: calc(100vh - 175px);
|
||||
height: calc(100vh - 200px);
|
||||
width: 100%;
|
||||
}
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
height: 28px;
|
||||
font-size: 11px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45%)', 'hsl(0 0% 55%)')};
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
}
|
||||
.modal-footer .footer-left,
|
||||
.modal-footer .footer-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.modal-footer .footer-separator {
|
||||
width: 1px;
|
||||
height: 12px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
|
||||
}
|
||||
</style>
|
||||
<div class="modal-toolbar">
|
||||
<div class="toolbar-left">
|
||||
@@ -598,6 +713,16 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
.wordWrap=${modalWordWrap}
|
||||
></dees-workspace-monaco>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="footer-left">
|
||||
<span class="footer-cursor">Ln ${modalCursorLine}, Col ${modalCursorCol}</span>
|
||||
<div class="footer-separator"></div>
|
||||
<span class="footer-lines">${modalLineCount} line${modalLineCount !== 1 ? 's' : ''}</span>
|
||||
</div>
|
||||
<div class="footer-right">
|
||||
<span class="footer-lang">${getLanguageLabel()}</span>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
menuOptions: [
|
||||
{
|
||||
@@ -609,7 +734,6 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
{
|
||||
name: 'Save & Close',
|
||||
action: async (modalRef) => {
|
||||
// Get the editor content from the modal
|
||||
modalEditorElement = modalRef!.shadowRoot?.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
|
||||
if (modalEditorElement) {
|
||||
const editor = await modalEditorElement.editorDeferred.promise;
|
||||
@@ -626,17 +750,61 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
modalEditorElement = modal.shadowRoot?.querySelector('dees-workspace-monaco') as DeesWorkspaceMonaco;
|
||||
|
||||
// Apply custom Monaco theme for matching background
|
||||
if (modalEditorElement) {
|
||||
const editor = await modalEditorElement.editorDeferred.promise;
|
||||
const domtoolsInstance = await modalEditorElement.domtoolsPromise;
|
||||
|
||||
const applyModalTheme = (isBright: boolean) => {
|
||||
const bg = isBright ? '#ffffff' : '#0a0a0a';
|
||||
(window as any).monaco?.editor?.defineTheme?.('dees-light', {
|
||||
base: 'vs',
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: { 'editor.background': bg },
|
||||
});
|
||||
(window as any).monaco?.editor?.defineTheme?.('dees-dark', {
|
||||
base: 'vs-dark',
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: { 'editor.background': bg },
|
||||
});
|
||||
editor.updateOptions({ theme: isBright ? 'dees-light' : 'dees-dark' });
|
||||
};
|
||||
|
||||
applyModalTheme(domtoolsInstance.themeManager.goBrightBoolean);
|
||||
domtoolsInstance.themeManager.themeObservable.subscribe((goBright: boolean) => {
|
||||
applyModalTheme(goBright);
|
||||
});
|
||||
|
||||
// Track cursor position
|
||||
editor.onDidChangeCursorPosition((e) => {
|
||||
modalCursorLine = e.position.lineNumber;
|
||||
modalCursorCol = e.position.column;
|
||||
updateFooterUI(modal);
|
||||
});
|
||||
|
||||
// Track line count
|
||||
const model = editor.getModel();
|
||||
if (model) {
|
||||
modalLineCount = model.getLineCount();
|
||||
updateFooterUI(modal);
|
||||
model.onDidChangeContent(() => {
|
||||
modalLineCount = model.getLineCount();
|
||||
updateFooterUI(modal);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Wire up toolbar event handlers
|
||||
const toolbar = modal.shadowRoot?.querySelector('.modal-toolbar');
|
||||
if (toolbar) {
|
||||
// Language button click
|
||||
const langBtn = toolbar.querySelector('.language-button');
|
||||
langBtn?.addEventListener('click', () => {
|
||||
modalLanguageDropdownOpen = !modalLanguageDropdownOpen;
|
||||
updateToolbarUI(modal);
|
||||
});
|
||||
|
||||
// Language option clicks
|
||||
const langOptions = toolbar.querySelectorAll('.language-option');
|
||||
langOptions.forEach((option) => {
|
||||
option.addEventListener('click', async () => {
|
||||
@@ -645,23 +813,21 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
modalLanguage = newLang;
|
||||
modalLanguageDropdownOpen = false;
|
||||
|
||||
// Update editor language
|
||||
const editor = await modalEditorElement.editorDeferred.promise;
|
||||
const model = editor.getModel();
|
||||
if (model) {
|
||||
(window as any).monaco.editor.setModelLanguage(model, newLang);
|
||||
const editorModel = editor.getModel();
|
||||
if (editorModel) {
|
||||
(window as any).monaco.editor.setModelLanguage(editorModel, newLang);
|
||||
}
|
||||
|
||||
// Update selected state
|
||||
langOptions.forEach(opt => opt.classList.remove('selected'));
|
||||
option.classList.add('selected');
|
||||
|
||||
updateToolbarUI(modal);
|
||||
updateFooterUI(modal);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Word wrap button
|
||||
const wrapBtn = toolbar.querySelector('.wrap-btn');
|
||||
wrapBtn?.addEventListener('click', async () => {
|
||||
modalWordWrap = modalWordWrap === 'on' ? 'off' : 'on';
|
||||
@@ -672,7 +838,6 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
updateToolbarUI(modal);
|
||||
});
|
||||
|
||||
// Line numbers button
|
||||
const linesBtn = toolbar.querySelector('.lines-btn');
|
||||
linesBtn?.addEventListener('click', async () => {
|
||||
modalShowLineNumbers = !modalShowLineNumbers;
|
||||
@@ -683,7 +848,6 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
updateToolbarUI(modal);
|
||||
});
|
||||
|
||||
// Copy button
|
||||
const copyBtn = toolbar.querySelector('.copy-btn');
|
||||
copyBtn?.addEventListener('click', async () => {
|
||||
if (modalEditorElement) {
|
||||
@@ -703,7 +867,6 @@ export class DeesInputCode extends DeesInputBase<string> {
|
||||
}
|
||||
});
|
||||
|
||||
// Close dropdown when clicking outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (modalLanguageDropdownOpen && !langBtn?.contains(e.target as Node)) {
|
||||
modalLanguageDropdownOpen = false;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { DeesInputBase } from '../dees-input-base/dees-input-base.js';
|
||||
import { demoFunc } from './demo.js';
|
||||
import { datepickerStyles } from './styles.js';
|
||||
import { renderDatepicker } from './template.js';
|
||||
import type { IDateEvent } from './types.js';
|
||||
import { DeesInputDatepickerPopup } from './datepicker-popup.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import '../../00group-layout/dees-label/dees-label.js';
|
||||
|
||||
@@ -49,7 +49,7 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
accessor disabledDates: string[] = [];
|
||||
|
||||
@property({ type: Number })
|
||||
accessor weekStartsOn: 0 | 1 = 1; // Default to Monday
|
||||
accessor weekStartsOn: 0 | 1 = 1;
|
||||
|
||||
@property({ type: String })
|
||||
accessor placeholder: string = 'YYYY-MM-DD';
|
||||
@@ -61,14 +61,11 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
accessor timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
@property({ type: Array })
|
||||
accessor events: IDateEvent[] = [];
|
||||
accessor events: import('./types.js').IDateEvent[] = [];
|
||||
|
||||
@state()
|
||||
accessor isOpened: boolean = false;
|
||||
|
||||
@state()
|
||||
accessor opensToTop: boolean = false;
|
||||
|
||||
@state()
|
||||
accessor selectedDate: Date | null = null;
|
||||
|
||||
@@ -81,57 +78,19 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
@state()
|
||||
accessor selectedMinute: number = 0;
|
||||
|
||||
private popupInstance: DeesInputDatepickerPopup | null = null;
|
||||
|
||||
public static styles = datepickerStyles;
|
||||
|
||||
|
||||
|
||||
public getTimezones(): { value: string; label: string }[] {
|
||||
// Common timezones with their display names
|
||||
return [
|
||||
{ value: 'UTC', label: 'UTC (Coordinated Universal Time)' },
|
||||
{ value: 'America/New_York', label: 'Eastern Time (US & Canada)' },
|
||||
{ value: 'America/Chicago', label: 'Central Time (US & Canada)' },
|
||||
{ value: 'America/Denver', label: 'Mountain Time (US & Canada)' },
|
||||
{ value: 'America/Los_Angeles', label: 'Pacific Time (US & Canada)' },
|
||||
{ value: 'America/Phoenix', label: 'Arizona' },
|
||||
{ value: 'America/Anchorage', label: 'Alaska' },
|
||||
{ value: 'Pacific/Honolulu', label: 'Hawaii' },
|
||||
{ value: 'Europe/London', label: 'London' },
|
||||
{ value: 'Europe/Paris', label: 'Paris' },
|
||||
{ value: 'Europe/Berlin', label: 'Berlin' },
|
||||
{ value: 'Europe/Moscow', label: 'Moscow' },
|
||||
{ value: 'Asia/Dubai', label: 'Dubai' },
|
||||
{ value: 'Asia/Kolkata', label: 'India Standard Time' },
|
||||
{ value: 'Asia/Shanghai', label: 'China Standard Time' },
|
||||
{ value: 'Asia/Tokyo', label: 'Tokyo' },
|
||||
{ value: 'Australia/Sydney', label: 'Sydney' },
|
||||
{ value: 'Pacific/Auckland', label: 'Auckland' },
|
||||
];
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
return renderDatepicker(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||
}
|
||||
|
||||
async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
// Initialize with empty value if not set
|
||||
if (!this.value) {
|
||||
this.value = '';
|
||||
}
|
||||
|
||||
// Initialize view date and selected time
|
||||
if (this.value) {
|
||||
try {
|
||||
const date = new Date(this.value);
|
||||
@@ -160,19 +119,15 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
if (isNaN(date.getTime())) return '';
|
||||
|
||||
let formatted = this.dateFormat;
|
||||
|
||||
// Basic date formatting
|
||||
const day = date.getDate().toString().padStart(2, '0');
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||||
const year = date.getFullYear().toString();
|
||||
|
||||
// Replace in correct order to avoid conflicts
|
||||
|
||||
formatted = formatted.replace('YYYY', year);
|
||||
formatted = formatted.replace('YY', year.slice(-2));
|
||||
formatted = formatted.replace('MM', month);
|
||||
formatted = formatted.replace('DD', day);
|
||||
|
||||
// Time formatting if enabled
|
||||
if (this.enableTime) {
|
||||
const hours24 = date.getHours();
|
||||
const hours12 = hours24 === 0 ? 12 : hours24 > 12 ? hours24 - 12 : hours24;
|
||||
@@ -186,17 +141,11 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
}
|
||||
}
|
||||
|
||||
// Timezone formatting if enabled
|
||||
if (this.enableTimezone) {
|
||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||
timeZoneName: 'short',
|
||||
timeZone: this.timezone
|
||||
});
|
||||
const formatter = new Intl.DateTimeFormat('en-US', { timeZoneName: 'short', timeZone: this.timezone });
|
||||
const parts = formatter.formatToParts(date);
|
||||
const tzPart = parts.find(part => part.type === 'timeZoneName');
|
||||
if (tzPart) {
|
||||
formatted += ` ${tzPart.value}`;
|
||||
}
|
||||
if (tzPart) formatted += ` ${tzPart.value}`;
|
||||
}
|
||||
|
||||
return formatted;
|
||||
@@ -205,274 +154,101 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
}
|
||||
}
|
||||
|
||||
private handleClickOutside = (event: MouseEvent) => {
|
||||
const path = event.composedPath();
|
||||
if (!path.includes(this)) {
|
||||
this.isOpened = false;
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
}
|
||||
};
|
||||
|
||||
public async toggleCalendar(): Promise<void> {
|
||||
if (this.disabled) return;
|
||||
|
||||
this.isOpened = !this.isOpened;
|
||||
|
||||
if (this.isOpened) {
|
||||
// Check available space and set position
|
||||
const inputContainer = this.shadowRoot!.querySelector('.input-container') as HTMLElement;
|
||||
const rect = inputContainer.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const spaceAbove = rect.top;
|
||||
|
||||
// Determine if we should open upwards (approximate height of 400px)
|
||||
this.opensToTop = spaceBelow < 400 && spaceAbove > spaceBelow;
|
||||
this.closePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
// Add click outside listener
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
}, 0);
|
||||
} else {
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
this.isOpened = true;
|
||||
|
||||
const inputContainer = this.shadowRoot!.querySelector('.input-container') as HTMLElement;
|
||||
const rect = inputContainer.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const spaceAbove = rect.top;
|
||||
const opensToTop = spaceBelow < 400 && spaceAbove > spaceBelow;
|
||||
|
||||
if (!this.popupInstance) {
|
||||
this.popupInstance = new DeesInputDatepickerPopup();
|
||||
}
|
||||
|
||||
// Configure popup
|
||||
this.popupInstance.triggerRect = rect;
|
||||
this.popupInstance.ownerComponent = this;
|
||||
this.popupInstance.opensToTop = opensToTop;
|
||||
this.popupInstance.enableTime = this.enableTime;
|
||||
this.popupInstance.timeFormat = this.timeFormat;
|
||||
this.popupInstance.minuteIncrement = this.minuteIncrement;
|
||||
this.popupInstance.weekStartsOn = this.weekStartsOn;
|
||||
this.popupInstance.minDate = this.minDate;
|
||||
this.popupInstance.maxDate = this.maxDate;
|
||||
this.popupInstance.disabledDates = this.disabledDates;
|
||||
this.popupInstance.enableTimezone = this.enableTimezone;
|
||||
this.popupInstance.timezone = this.timezone;
|
||||
this.popupInstance.events = this.events;
|
||||
this.popupInstance.selectedDate = this.selectedDate;
|
||||
this.popupInstance.viewDate = new Date(this.viewDate);
|
||||
this.popupInstance.selectedHour = this.selectedHour;
|
||||
this.popupInstance.selectedMinute = this.selectedMinute;
|
||||
|
||||
// Listen for popup events
|
||||
this.popupInstance.addEventListener('date-selected', this.handleDateSelected);
|
||||
this.popupInstance.addEventListener('date-cleared', this.handleDateCleared);
|
||||
this.popupInstance.addEventListener('close-request', this.handleCloseRequest);
|
||||
this.popupInstance.addEventListener('reposition-request', this.handleRepositionRequest);
|
||||
|
||||
await this.popupInstance.show();
|
||||
}
|
||||
|
||||
private closePopup(): void {
|
||||
this.isOpened = false;
|
||||
if (this.popupInstance) {
|
||||
this.popupInstance.removeEventListener('date-selected', this.handleDateSelected);
|
||||
this.popupInstance.removeEventListener('date-cleared', this.handleDateCleared);
|
||||
this.popupInstance.removeEventListener('close-request', this.handleCloseRequest);
|
||||
this.popupInstance.removeEventListener('reposition-request', this.handleRepositionRequest);
|
||||
this.popupInstance.hide();
|
||||
}
|
||||
}
|
||||
|
||||
public getDaysInMonth(): Date[] {
|
||||
const year = this.viewDate.getFullYear();
|
||||
const month = this.viewDate.getMonth();
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const days: Date[] = [];
|
||||
|
||||
// Adjust for week start
|
||||
const startOffset = this.weekStartsOn === 1
|
||||
? (firstDay.getDay() === 0 ? 6 : firstDay.getDay() - 1)
|
||||
: firstDay.getDay();
|
||||
|
||||
// Add days from previous month
|
||||
for (let i = startOffset; i > 0; i--) {
|
||||
days.push(new Date(year, month, 1 - i));
|
||||
}
|
||||
|
||||
// Add days of current month
|
||||
for (let i = 1; i <= lastDay.getDate(); i++) {
|
||||
days.push(new Date(year, month, i));
|
||||
}
|
||||
|
||||
// Add days from next month to complete the grid (6 rows)
|
||||
const remainingDays = 42 - days.length;
|
||||
for (let i = 1; i <= remainingDays; i++) {
|
||||
days.push(new Date(year, month + 1, i));
|
||||
}
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
public isToday(date: Date): boolean {
|
||||
const today = new Date();
|
||||
return date.getDate() === today.getDate() &&
|
||||
date.getMonth() === today.getMonth() &&
|
||||
date.getFullYear() === today.getFullYear();
|
||||
}
|
||||
|
||||
public isSelected(date: Date): boolean {
|
||||
if (!this.selectedDate) return false;
|
||||
return date.getDate() === this.selectedDate.getDate() &&
|
||||
date.getMonth() === this.selectedDate.getMonth() &&
|
||||
date.getFullYear() === this.selectedDate.getFullYear();
|
||||
}
|
||||
|
||||
public isDisabled(date: Date): boolean {
|
||||
// Check min date
|
||||
if (this.minDate) {
|
||||
const min = new Date(this.minDate);
|
||||
if (date < min) return true;
|
||||
}
|
||||
|
||||
// Check max date
|
||||
if (this.maxDate) {
|
||||
const max = new Date(this.maxDate);
|
||||
if (date > max) return true;
|
||||
}
|
||||
|
||||
// Check disabled dates
|
||||
if (this.disabledDates && this.disabledDates.length > 0) {
|
||||
return this.disabledDates.some(disabledStr => {
|
||||
try {
|
||||
const disabled = new Date(disabledStr);
|
||||
return date.getDate() === disabled.getDate() &&
|
||||
date.getMonth() === disabled.getMonth() &&
|
||||
date.getFullYear() === disabled.getFullYear();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public getEventsForDate(date: Date): IDateEvent[] {
|
||||
if (!this.events || this.events.length === 0) return [];
|
||||
|
||||
const dateStr = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
|
||||
return this.events.filter(event => event.date === dateStr);
|
||||
}
|
||||
|
||||
public selectDate(date: Date): void {
|
||||
this.selectedDate = new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
date.getDate(),
|
||||
this.selectedHour,
|
||||
this.selectedMinute
|
||||
);
|
||||
|
||||
this.value = this.formatValueWithTimezone(this.selectedDate);
|
||||
private handleDateSelected = (event: Event): void => {
|
||||
const date = (event as CustomEvent).detail as Date;
|
||||
this.selectedDate = date;
|
||||
this.selectedHour = date.getHours();
|
||||
this.selectedMinute = date.getMinutes();
|
||||
this.viewDate = new Date(date);
|
||||
this.value = this.formatValueWithTimezone(date);
|
||||
this.changeSubject.next(this);
|
||||
|
||||
if (!this.enableTime) {
|
||||
this.isOpened = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public selectToday(): void {
|
||||
const today = new Date();
|
||||
this.selectedDate = today;
|
||||
this.viewDate = new Date(today);
|
||||
this.selectedHour = today.getHours();
|
||||
this.selectedMinute = today.getMinutes();
|
||||
|
||||
this.value = this.formatValueWithTimezone(this.selectedDate);
|
||||
this.changeSubject.next(this);
|
||||
|
||||
if (!this.enableTime) {
|
||||
this.isOpened = false;
|
||||
}
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
private handleDateCleared = (): void => {
|
||||
this.value = '';
|
||||
this.selectedDate = null;
|
||||
this.changeSubject.next(this);
|
||||
this.isOpened = false;
|
||||
}
|
||||
};
|
||||
|
||||
public previousMonth(): void {
|
||||
this.viewDate = new Date(this.viewDate.getFullYear(), this.viewDate.getMonth() - 1, 1);
|
||||
}
|
||||
private handleCloseRequest = (): void => {
|
||||
this.closePopup();
|
||||
};
|
||||
|
||||
public nextMonth(): void {
|
||||
this.viewDate = new Date(this.viewDate.getFullYear(), this.viewDate.getMonth() + 1, 1);
|
||||
}
|
||||
private handleRepositionRequest = (): void => {
|
||||
if (!this.popupInstance || !this.isOpened) return;
|
||||
const inputContainer = this.shadowRoot!.querySelector('.input-container') as HTMLElement;
|
||||
if (!inputContainer) return;
|
||||
|
||||
public handleHourInput(e: InputEvent): void {
|
||||
const input = e.target as HTMLInputElement;
|
||||
let value = parseInt(input.value) || 0;
|
||||
|
||||
if (this.timeFormat === '12h') {
|
||||
value = Math.max(1, Math.min(12, value));
|
||||
// Convert to 24h format
|
||||
if (this.selectedHour >= 12 && value !== 12) {
|
||||
this.selectedHour = value + 12;
|
||||
} else if (this.selectedHour < 12 && value === 12) {
|
||||
this.selectedHour = 0;
|
||||
} else {
|
||||
this.selectedHour = value;
|
||||
}
|
||||
} else {
|
||||
this.selectedHour = Math.max(0, Math.min(23, value));
|
||||
const rect = inputContainer.getBoundingClientRect();
|
||||
if (rect.bottom < 0 || rect.top > window.innerHeight) {
|
||||
this.closePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateSelectedDateTime();
|
||||
}
|
||||
|
||||
public handleMinuteInput(e: InputEvent): void {
|
||||
const input = e.target as HTMLInputElement;
|
||||
let value = parseInt(input.value) || 0;
|
||||
value = Math.max(0, Math.min(59, value));
|
||||
|
||||
if (this.minuteIncrement && this.minuteIncrement > 1) {
|
||||
value = Math.round(value / this.minuteIncrement) * this.minuteIncrement;
|
||||
}
|
||||
|
||||
this.selectedMinute = value;
|
||||
this.updateSelectedDateTime();
|
||||
}
|
||||
|
||||
public setAMPM(period: 'am' | 'pm'): void {
|
||||
if (period === 'am' && this.selectedHour >= 12) {
|
||||
this.selectedHour -= 12;
|
||||
} else if (period === 'pm' && this.selectedHour < 12) {
|
||||
this.selectedHour += 12;
|
||||
}
|
||||
this.updateSelectedDateTime();
|
||||
}
|
||||
|
||||
private updateSelectedDateTime(): void {
|
||||
if (this.selectedDate) {
|
||||
this.selectedDate = new Date(
|
||||
this.selectedDate.getFullYear(),
|
||||
this.selectedDate.getMonth(),
|
||||
this.selectedDate.getDate(),
|
||||
this.selectedHour,
|
||||
this.selectedMinute
|
||||
);
|
||||
this.value = this.formatValueWithTimezone(this.selectedDate);
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
}
|
||||
|
||||
public handleTimezoneChange(e: Event): void {
|
||||
const select = e.target as HTMLSelectElement;
|
||||
this.timezone = select.value;
|
||||
this.updateSelectedDateTime();
|
||||
}
|
||||
|
||||
private formatValueWithTimezone(date: Date): string {
|
||||
if (!this.enableTimezone) {
|
||||
return date.toISOString();
|
||||
}
|
||||
|
||||
// Format the date with timezone offset
|
||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false,
|
||||
timeZone: this.timezone,
|
||||
timeZoneName: 'short'
|
||||
});
|
||||
|
||||
const parts = formatter.formatToParts(date);
|
||||
const dateParts: any = {};
|
||||
parts.forEach(part => {
|
||||
dateParts[part.type] = part.value;
|
||||
});
|
||||
|
||||
// Create ISO-like format with timezone
|
||||
const isoString = `${dateParts.year}-${dateParts.month}-${dateParts.day}T${dateParts.hour}:${dateParts.minute}:${dateParts.second}`;
|
||||
|
||||
// Get timezone offset
|
||||
const tzOffset = this.getTimezoneOffset(date, this.timezone);
|
||||
return `${isoString}${tzOffset}`;
|
||||
}
|
||||
|
||||
private getTimezoneOffset(date: Date, timezone: string): string {
|
||||
// Create a date in the target timezone
|
||||
const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }));
|
||||
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||
|
||||
const offsetMinutes = (tzDate.getTime() - utcDate.getTime()) / (1000 * 60);
|
||||
const hours = Math.floor(Math.abs(offsetMinutes) / 60);
|
||||
const minutes = Math.abs(offsetMinutes) % 60;
|
||||
const sign = offsetMinutes >= 0 ? '+' : '-';
|
||||
|
||||
return `${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
||||
}
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const spaceAbove = rect.top;
|
||||
this.popupInstance.opensToTop = spaceBelow < 400 && spaceAbove > spaceBelow;
|
||||
this.popupInstance.triggerRect = rect;
|
||||
};
|
||||
|
||||
public handleKeydown(e: KeyboardEvent): void {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
@@ -480,7 +256,7 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
this.toggleCalendar();
|
||||
} else if (e.key === 'Escape' && this.isOpened) {
|
||||
e.preventDefault();
|
||||
this.isOpened = false;
|
||||
this.closePopup();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,9 +270,8 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
public handleManualInput(e: InputEvent): void {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const inputValue = input.value.trim();
|
||||
|
||||
|
||||
if (!inputValue) {
|
||||
// Clear the value if input is empty
|
||||
this.value = '';
|
||||
this.selectedDate = null;
|
||||
return;
|
||||
@@ -504,7 +279,6 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
|
||||
const parsedDate = this.parseManualDate(inputValue);
|
||||
if (parsedDate && !isNaN(parsedDate.getTime())) {
|
||||
// Update internal state without triggering re-render of input
|
||||
this.value = parsedDate.toISOString();
|
||||
this.selectedDate = parsedDate;
|
||||
this.viewDate = new Date(parsedDate);
|
||||
@@ -517,7 +291,7 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
public handleInputBlur(e: FocusEvent): void {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const inputValue = input.value.trim();
|
||||
|
||||
|
||||
if (!inputValue) {
|
||||
this.value = '';
|
||||
this.selectedDate = null;
|
||||
@@ -533,10 +307,8 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
this.selectedHour = parsedDate.getHours();
|
||||
this.selectedMinute = parsedDate.getMinutes();
|
||||
this.changeSubject.next(this);
|
||||
// Update the input with formatted date
|
||||
input.value = this.formatDate(this.value);
|
||||
} else {
|
||||
// Revert to previous valid value on blur if parsing failed
|
||||
input.value = this.formatDate(this.value);
|
||||
}
|
||||
}
|
||||
@@ -544,22 +316,17 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
private parseManualDate(input: string): Date | null {
|
||||
if (!input) return null;
|
||||
|
||||
// Split date and time parts if present
|
||||
const parts = input.split(' ');
|
||||
let datePart = parts[0];
|
||||
let timePart = parts[1] || '';
|
||||
|
||||
let parsedDate: Date | null = null;
|
||||
|
||||
// Try different date formats
|
||||
// Format 1: YYYY-MM-DD (ISO-like)
|
||||
const isoMatch = datePart.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
|
||||
if (isoMatch) {
|
||||
const [_, year, month, day] = isoMatch;
|
||||
parsedDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
|
||||
}
|
||||
|
||||
// Format 2: DD.MM.YYYY (European)
|
||||
if (!parsedDate) {
|
||||
const euMatch = datePart.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);
|
||||
if (euMatch) {
|
||||
@@ -568,7 +335,6 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
}
|
||||
}
|
||||
|
||||
// Format 3: MM/DD/YYYY (US)
|
||||
if (!parsedDate) {
|
||||
const usMatch = datePart.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})$/);
|
||||
if (usMatch) {
|
||||
@@ -577,12 +343,8 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
}
|
||||
}
|
||||
|
||||
// If no date was parsed, return null
|
||||
if (!parsedDate || isNaN(parsedDate.getTime())) {
|
||||
return null;
|
||||
}
|
||||
if (!parsedDate || isNaN(parsedDate.getTime())) return null;
|
||||
|
||||
// Parse time if present (HH:MM format)
|
||||
if (timePart) {
|
||||
const timeMatch = timePart.match(/^(\d{1,2}):(\d{2})$/);
|
||||
if (timeMatch) {
|
||||
@@ -591,7 +353,6 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
parsedDate.setMinutes(parseInt(minutes));
|
||||
}
|
||||
} else if (!this.enableTime) {
|
||||
// If time is not enabled and not provided, use current time
|
||||
const now = new Date();
|
||||
parsedDate.setHours(now.getHours());
|
||||
parsedDate.setMinutes(now.getMinutes());
|
||||
@@ -602,6 +363,34 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
return parsedDate;
|
||||
}
|
||||
|
||||
private formatValueWithTimezone(date: Date): string {
|
||||
if (!this.enableTimezone) return date.toISOString();
|
||||
|
||||
const formatter = new Intl.DateTimeFormat('en-US', {
|
||||
year: 'numeric', month: '2-digit', day: '2-digit',
|
||||
hour: '2-digit', minute: '2-digit', second: '2-digit',
|
||||
hour12: false, timeZone: this.timezone, timeZoneName: 'short',
|
||||
});
|
||||
|
||||
const parts = formatter.formatToParts(date);
|
||||
const dateParts: any = {};
|
||||
parts.forEach(part => { dateParts[part.type] = part.value; });
|
||||
|
||||
const isoString = `${dateParts.year}-${dateParts.month}-${dateParts.day}T${dateParts.hour}:${dateParts.minute}:${dateParts.second}`;
|
||||
const tzOffset = this.getTimezoneOffset(date, this.timezone);
|
||||
return `${isoString}${tzOffset}`;
|
||||
}
|
||||
|
||||
private getTimezoneOffset(date: Date, timezone: string): string {
|
||||
const tzDate = new Date(date.toLocaleString('en-US', { timeZone: timezone }));
|
||||
const utcDate = new Date(date.toLocaleString('en-US', { timeZone: 'UTC' }));
|
||||
const offsetMinutes = (tzDate.getTime() - utcDate.getTime()) / (1000 * 60);
|
||||
const hours = Math.floor(Math.abs(offsetMinutes) / 60);
|
||||
const minutes = Math.abs(offsetMinutes) % 60;
|
||||
const sign = offsetMinutes >= 0 ? '+' : '-';
|
||||
return `${sign}${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
public getValue(): string {
|
||||
return this.value;
|
||||
}
|
||||
@@ -622,4 +411,16 @@ export class DeesInputDatepicker extends DeesInputBase<DeesInputDatepicker> {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
if (this.popupInstance) {
|
||||
this.popupInstance.removeEventListener('date-selected', this.handleDateSelected);
|
||||
this.popupInstance.removeEventListener('date-cleared', this.handleDateCleared);
|
||||
this.popupInstance.removeEventListener('close-request', this.handleCloseRequest);
|
||||
this.popupInstance.removeEventListener('reposition-request', this.handleRepositionRequest);
|
||||
this.popupInstance.hide();
|
||||
this.popupInstance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,758 @@
|
||||
import {
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
property,
|
||||
state,
|
||||
html,
|
||||
css,
|
||||
cssManager,
|
||||
DeesElement,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { zIndexRegistry } from '../../00zindex.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 type { IDateEvent } from './types.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-input-datepicker-popup': DeesInputDatepickerPopup;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-input-datepicker-popup')
|
||||
export class DeesInputDatepickerPopup extends DeesElement {
|
||||
// Properties set by the parent
|
||||
@property({ attribute: false })
|
||||
accessor triggerRect: DOMRect | null = null;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor ownerComponent: HTMLElement | null = null;
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor enableTime: boolean = false;
|
||||
|
||||
@property({ type: String })
|
||||
accessor timeFormat: '24h' | '12h' = '24h';
|
||||
|
||||
@property({ type: Number })
|
||||
accessor minuteIncrement: number = 1;
|
||||
|
||||
@property({ type: Number })
|
||||
accessor weekStartsOn: 0 | 1 = 1;
|
||||
|
||||
@property({ type: String })
|
||||
accessor minDate: string = '';
|
||||
|
||||
@property({ type: String })
|
||||
accessor maxDate: string = '';
|
||||
|
||||
@property({ type: Array })
|
||||
accessor disabledDates: string[] = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor enableTimezone: boolean = false;
|
||||
|
||||
@property({ type: String })
|
||||
accessor timezone: string = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||
|
||||
@property({ type: Array })
|
||||
accessor events: IDateEvent[] = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor opensToTop: boolean = false;
|
||||
|
||||
// Internal state
|
||||
@state()
|
||||
accessor selectedDate: Date | null = null;
|
||||
|
||||
@state()
|
||||
accessor viewDate: Date = new Date();
|
||||
|
||||
@state()
|
||||
accessor selectedHour: number = 0;
|
||||
|
||||
@state()
|
||||
accessor selectedMinute: number = 0;
|
||||
|
||||
@state()
|
||||
accessor menuZIndex: number = 1000;
|
||||
|
||||
@state()
|
||||
accessor visible: boolean = false;
|
||||
|
||||
private windowLayer: DeesWindowLayer | null = null;
|
||||
private isDestroying: boolean = false;
|
||||
|
||||
public static styles = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.calendar-popup {
|
||||
position: fixed;
|
||||
pointer-events: auto;
|
||||
will-change: transform, opacity;
|
||||
transition: all 0.15s ease;
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
box-shadow: ${cssManager.bdTheme(
|
||||
'0 10px 15px -3px hsl(0 0% 0% / 0.1), 0 4px 6px -4px hsl(0 0% 0% / 0.1)',
|
||||
'0 10px 15px -3px hsl(0 0% 0% / 0.2), 0 4px 6px -4px hsl(0 0% 0% / 0.2)'
|
||||
)};
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
user-select: none;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
.calendar-popup.top {
|
||||
transform: translateY(4px);
|
||||
}
|
||||
|
||||
.calendar-popup.show {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Calendar Header */
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.month-year-display {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
}
|
||||
|
||||
/* Weekday headers */
|
||||
.weekdays {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.weekday {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
padding: 0 0 8px 0;
|
||||
}
|
||||
|
||||
/* Days grid */
|
||||
.days-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.day {
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
border: none;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: transparent;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.day:hover:not(.disabled) {
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
}
|
||||
|
||||
.day.other-month {
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.day.today {
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.day.selected {
|
||||
background: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')};
|
||||
color: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(222.2 47.4% 11.2%)')};
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.day.disabled {
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
cursor: not-allowed;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* Event indicators */
|
||||
.event-indicator {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.event-dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.event-dot.info { background: ${cssManager.bdTheme('hsl(211 70% 52%)', 'hsl(211 70% 62%)')}; }
|
||||
.event-dot.warning { background: ${cssManager.bdTheme('hsl(45 90% 45%)', 'hsl(45 90% 55%)')}; }
|
||||
.event-dot.success { background: ${cssManager.bdTheme('hsl(142 69% 45%)', 'hsl(142 69% 55%)')}; }
|
||||
.event-dot.error { background: ${cssManager.bdTheme('hsl(0 72% 51%)', 'hsl(0 72% 61%)')}; }
|
||||
|
||||
.event-count {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
background: ${cssManager.bdTheme('hsl(0 72% 51%)', 'hsl(0 72% 61%)')};
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.event-tooltip {
|
||||
position: absolute;
|
||||
bottom: calc(100% + 8px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 0%)')};
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.day.has-event:hover .event-tooltip { opacity: 1; }
|
||||
|
||||
/* Time selector */
|
||||
.time-selector {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
}
|
||||
|
||||
.time-selector-title {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.time-inputs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.time-input {
|
||||
width: 65px;
|
||||
height: 36px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')};
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.time-input:focus {
|
||||
outline: none;
|
||||
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')};
|
||||
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(222.2 47.4% 11.2% / 0.1)', 'hsl(210 20% 98% / 0.1)')};
|
||||
}
|
||||
|
||||
.time-separator {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.am-pm-selector { display: flex; gap: 4px; margin-left: 8px; }
|
||||
|
||||
.am-pm-button {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')};
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.am-pm-button.selected {
|
||||
background: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')};
|
||||
color: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(222.2 47.4% 11.2%)')};
|
||||
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')};
|
||||
}
|
||||
|
||||
.am-pm-button:hover:not(.selected) {
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
}
|
||||
|
||||
/* Timezone selector */
|
||||
.timezone-selector {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
}
|
||||
|
||||
.timezone-selector-title {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.timezone-select {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')};
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* Action buttons */
|
||||
.calendar-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
}
|
||||
|
||||
.action-button {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.today-button {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
}
|
||||
|
||||
.today-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
}
|
||||
|
||||
.clear-action-button {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.clear-action-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 72.2% 50.6% / 0.1)', 'hsl(0 62.8% 30.6% / 0.1)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
private static readonly MONTH_NAMES = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December',
|
||||
];
|
||||
|
||||
private static readonly TIMEZONES = [
|
||||
{ value: 'UTC', label: 'UTC (Coordinated Universal Time)' },
|
||||
{ value: 'America/New_York', label: 'Eastern Time (US & Canada)' },
|
||||
{ value: 'America/Chicago', label: 'Central Time (US & Canada)' },
|
||||
{ value: 'America/Denver', label: 'Mountain Time (US & Canada)' },
|
||||
{ value: 'America/Los_Angeles', label: 'Pacific Time (US & Canada)' },
|
||||
{ value: 'Europe/London', label: 'London' },
|
||||
{ value: 'Europe/Paris', label: 'Paris' },
|
||||
{ value: 'Europe/Berlin', label: 'Berlin' },
|
||||
{ value: 'Europe/Moscow', label: 'Moscow' },
|
||||
{ value: 'Asia/Dubai', label: 'Dubai' },
|
||||
{ value: 'Asia/Kolkata', label: 'India Standard Time' },
|
||||
{ value: 'Asia/Shanghai', label: 'China Standard Time' },
|
||||
{ value: 'Asia/Tokyo', label: 'Tokyo' },
|
||||
{ value: 'Australia/Sydney', label: 'Sydney' },
|
||||
{ value: 'Pacific/Auckland', label: 'Auckland' },
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
if (!this.triggerRect) return html``;
|
||||
|
||||
const posStyle = this.computePositionStyle();
|
||||
const weekDays = this.weekStartsOn === 1
|
||||
? ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']
|
||||
: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
||||
const days = this.getDaysInMonth();
|
||||
const isAM = this.selectedHour < 12;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="calendar-popup ${this.visible ? 'show' : ''} ${this.opensToTop ? 'top' : 'bottom'}"
|
||||
style="${posStyle}; z-index: ${this.menuZIndex};"
|
||||
>
|
||||
<div class="calendar-header">
|
||||
<button class="nav-button" @click=${this.previousMonth}>
|
||||
<dees-icon icon="lucide:chevronLeft" iconSize="16"></dees-icon>
|
||||
</button>
|
||||
<div class="month-year-display">
|
||||
${DeesInputDatepickerPopup.MONTH_NAMES[this.viewDate.getMonth()]} ${this.viewDate.getFullYear()}
|
||||
</div>
|
||||
<button class="nav-button" @click=${this.nextMonth}>
|
||||
<dees-icon icon="lucide:chevronRight" iconSize="16"></dees-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="weekdays">
|
||||
${weekDays.map(day => html`<div class="weekday">${day}</div>`)}
|
||||
</div>
|
||||
|
||||
<div class="days-grid">
|
||||
${days.map(day => {
|
||||
const isToday = this.isToday(day);
|
||||
const isSelected = this.isSelected(day);
|
||||
const isOtherMonth = day.getMonth() !== this.viewDate.getMonth();
|
||||
const isDayDisabled = this.isDayDisabled(day);
|
||||
const dayEvents = this.getEventsForDate(day);
|
||||
const hasEvents = dayEvents.length > 0;
|
||||
const totalEventCount = dayEvents.reduce((sum, event) => sum + (event.count || 1), 0);
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="day ${isOtherMonth ? 'other-month' : ''} ${isToday ? 'today' : ''} ${isSelected ? 'selected' : ''} ${isDayDisabled ? 'disabled' : ''} ${hasEvents ? 'has-event' : ''}"
|
||||
@click=${() => !isDayDisabled && this.handleSelectDate(day)}
|
||||
>
|
||||
${day.getDate()}
|
||||
${hasEvents ? html`
|
||||
${totalEventCount > 3 ? html`
|
||||
<div class="event-count">${totalEventCount}</div>
|
||||
` : html`
|
||||
<div class="event-indicator">
|
||||
${dayEvents.slice(0, 3).map(event => html`
|
||||
<div class="event-dot ${event.type || 'info'}"></div>
|
||||
`)}
|
||||
</div>
|
||||
`}
|
||||
${dayEvents[0].title ? html`
|
||||
<div class="event-tooltip">
|
||||
${dayEvents[0].title}
|
||||
${totalEventCount > 1 ? html` (+${totalEventCount - 1} more)` : ''}
|
||||
</div>
|
||||
` : ''}
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
|
||||
${this.enableTime ? html`
|
||||
<div class="time-selector">
|
||||
<div class="time-selector-title">Time</div>
|
||||
<div class="time-inputs">
|
||||
<input type="number" class="time-input"
|
||||
.value=${this.timeFormat === '12h'
|
||||
? (this.selectedHour === 0 ? 12 : this.selectedHour > 12 ? this.selectedHour - 12 : this.selectedHour).toString().padStart(2, '0')
|
||||
: this.selectedHour.toString().padStart(2, '0')}
|
||||
@input=${this.handleHourInput}
|
||||
min="${this.timeFormat === '12h' ? 1 : 0}"
|
||||
max="${this.timeFormat === '12h' ? 12 : 23}"
|
||||
/>
|
||||
<span class="time-separator">:</span>
|
||||
<input type="number" class="time-input"
|
||||
.value=${this.selectedMinute.toString().padStart(2, '0')}
|
||||
@input=${this.handleMinuteInput}
|
||||
min="0" max="59" step="${this.minuteIncrement || 1}"
|
||||
/>
|
||||
${this.timeFormat === '12h' ? html`
|
||||
<div class="am-pm-selector">
|
||||
<button class="am-pm-button ${isAM ? 'selected' : ''}" @click=${() => this.setAMPM('am')}>AM</button>
|
||||
<button class="am-pm-button ${!isAM ? 'selected' : ''}" @click=${() => this.setAMPM('pm')}>PM</button>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${this.enableTimezone ? html`
|
||||
<div class="timezone-selector">
|
||||
<div class="timezone-selector-title">Timezone</div>
|
||||
<select class="timezone-select" .value=${this.timezone} @change=${this.handleTimezoneChange}>
|
||||
${DeesInputDatepickerPopup.TIMEZONES.map(tz => html`
|
||||
<option value="${tz.value}" ?selected=${tz.value === this.timezone}>${tz.label}</option>
|
||||
`)}
|
||||
</select>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="calendar-actions">
|
||||
<button class="action-button today-button" @click=${this.handleSelectToday}>Today</button>
|
||||
<button class="action-button clear-action-button" @click=${this.handleClear}>Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private computePositionStyle(): string {
|
||||
const rect = this.triggerRect!;
|
||||
const left = rect.left;
|
||||
|
||||
if (this.opensToTop) {
|
||||
const bottom = window.innerHeight - rect.top + 4;
|
||||
return `left: ${left}px; bottom: ${bottom}px; top: auto`;
|
||||
} else {
|
||||
const top = rect.bottom + 4;
|
||||
return `left: ${left}px; top: ${top}px`;
|
||||
}
|
||||
}
|
||||
|
||||
// Calendar logic
|
||||
private getDaysInMonth(): Date[] {
|
||||
const year = this.viewDate.getFullYear();
|
||||
const month = this.viewDate.getMonth();
|
||||
const firstDay = new Date(year, month, 1);
|
||||
const lastDay = new Date(year, month + 1, 0);
|
||||
const days: Date[] = [];
|
||||
|
||||
const startOffset = this.weekStartsOn === 1
|
||||
? (firstDay.getDay() === 0 ? 6 : firstDay.getDay() - 1)
|
||||
: firstDay.getDay();
|
||||
|
||||
for (let i = startOffset; i > 0; i--) days.push(new Date(year, month, 1 - i));
|
||||
for (let i = 1; i <= lastDay.getDate(); i++) days.push(new Date(year, month, i));
|
||||
const remaining = 42 - days.length;
|
||||
for (let i = 1; i <= remaining; i++) days.push(new Date(year, month + 1, i));
|
||||
|
||||
return days;
|
||||
}
|
||||
|
||||
private isToday(date: Date): boolean {
|
||||
const today = new Date();
|
||||
return date.getDate() === today.getDate() && date.getMonth() === today.getMonth() && date.getFullYear() === today.getFullYear();
|
||||
}
|
||||
|
||||
private isSelected(date: Date): boolean {
|
||||
if (!this.selectedDate) return false;
|
||||
return date.getDate() === this.selectedDate.getDate() && date.getMonth() === this.selectedDate.getMonth() && date.getFullYear() === this.selectedDate.getFullYear();
|
||||
}
|
||||
|
||||
private isDayDisabled(date: Date): boolean {
|
||||
if (this.minDate) { const min = new Date(this.minDate); if (date < min) return true; }
|
||||
if (this.maxDate) { const max = new Date(this.maxDate); if (date > max) return true; }
|
||||
if (this.disabledDates?.length) {
|
||||
return this.disabledDates.some(ds => {
|
||||
try { const d = new Date(ds); return date.getDate() === d.getDate() && date.getMonth() === d.getMonth() && date.getFullYear() === d.getFullYear(); }
|
||||
catch { return false; }
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private getEventsForDate(date: Date): IDateEvent[] {
|
||||
if (!this.events?.length) return [];
|
||||
const dateStr = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
|
||||
return this.events.filter(e => e.date === dateStr);
|
||||
}
|
||||
|
||||
private previousMonth(): void {
|
||||
this.viewDate = new Date(this.viewDate.getFullYear(), this.viewDate.getMonth() - 1, 1);
|
||||
}
|
||||
|
||||
private nextMonth(): void {
|
||||
this.viewDate = new Date(this.viewDate.getFullYear(), this.viewDate.getMonth() + 1, 1);
|
||||
}
|
||||
|
||||
// Event dispatching
|
||||
private handleSelectDate(day: Date): void {
|
||||
this.selectedDate = new Date(day.getFullYear(), day.getMonth(), day.getDate(), this.selectedHour, this.selectedMinute);
|
||||
this.dispatchEvent(new CustomEvent('date-selected', { detail: this.selectedDate }));
|
||||
if (!this.enableTime) {
|
||||
this.dispatchEvent(new CustomEvent('close-request'));
|
||||
}
|
||||
}
|
||||
|
||||
private handleSelectToday(): void {
|
||||
const today = new Date();
|
||||
this.selectedDate = today;
|
||||
this.viewDate = new Date(today);
|
||||
this.selectedHour = today.getHours();
|
||||
this.selectedMinute = today.getMinutes();
|
||||
this.dispatchEvent(new CustomEvent('date-selected', { detail: this.selectedDate }));
|
||||
if (!this.enableTime) {
|
||||
this.dispatchEvent(new CustomEvent('close-request'));
|
||||
}
|
||||
}
|
||||
|
||||
private handleClear(): void {
|
||||
this.dispatchEvent(new CustomEvent('date-cleared'));
|
||||
this.dispatchEvent(new CustomEvent('close-request'));
|
||||
}
|
||||
|
||||
private handleHourInput = (e: InputEvent): void => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
let value = parseInt(input.value) || 0;
|
||||
if (this.timeFormat === '12h') {
|
||||
value = Math.max(1, Math.min(12, value));
|
||||
if (this.selectedHour >= 12 && value !== 12) this.selectedHour = value + 12;
|
||||
else if (this.selectedHour < 12 && value === 12) this.selectedHour = 0;
|
||||
else this.selectedHour = value;
|
||||
} else {
|
||||
this.selectedHour = Math.max(0, Math.min(23, value));
|
||||
}
|
||||
this.emitTimeUpdate();
|
||||
};
|
||||
|
||||
private handleMinuteInput = (e: InputEvent): void => {
|
||||
const input = e.target as HTMLInputElement;
|
||||
let value = parseInt(input.value) || 0;
|
||||
value = Math.max(0, Math.min(59, value));
|
||||
if (this.minuteIncrement > 1) value = Math.round(value / this.minuteIncrement) * this.minuteIncrement;
|
||||
this.selectedMinute = value;
|
||||
this.emitTimeUpdate();
|
||||
};
|
||||
|
||||
private setAMPM(period: 'am' | 'pm'): void {
|
||||
if (period === 'am' && this.selectedHour >= 12) this.selectedHour -= 12;
|
||||
else if (period === 'pm' && this.selectedHour < 12) this.selectedHour += 12;
|
||||
this.emitTimeUpdate();
|
||||
}
|
||||
|
||||
private handleTimezoneChange = (e: Event): void => {
|
||||
this.timezone = (e.target as HTMLSelectElement).value;
|
||||
this.emitTimeUpdate();
|
||||
};
|
||||
|
||||
private emitTimeUpdate(): void {
|
||||
if (this.selectedDate) {
|
||||
this.selectedDate = new Date(this.selectedDate.getFullYear(), this.selectedDate.getMonth(), this.selectedDate.getDate(), this.selectedHour, this.selectedMinute);
|
||||
this.dispatchEvent(new CustomEvent('date-selected', { detail: this.selectedDate }));
|
||||
}
|
||||
}
|
||||
|
||||
// Show/hide lifecycle
|
||||
public async show(): Promise<void> {
|
||||
this.windowLayer = await DeesWindowLayer.createAndShow();
|
||||
this.windowLayer.addEventListener('click', () => {
|
||||
this.dispatchEvent(new CustomEvent('close-request'));
|
||||
});
|
||||
|
||||
this.menuZIndex = zIndexRegistry.getNextZIndex();
|
||||
zIndexRegistry.register(this, this.menuZIndex);
|
||||
this.style.zIndex = this.menuZIndex.toString();
|
||||
|
||||
document.body.appendChild(this);
|
||||
|
||||
await domtools.plugins.smartdelay.delayFor(0);
|
||||
this.visible = true;
|
||||
|
||||
window.addEventListener('scroll', this.handleScrollOrResize, { capture: true, passive: true });
|
||||
window.addEventListener('resize', this.handleScrollOrResize, { passive: true });
|
||||
}
|
||||
|
||||
public async hide(): Promise<void> {
|
||||
if (this.isDestroying) return;
|
||||
this.isDestroying = true;
|
||||
|
||||
window.removeEventListener('scroll', this.handleScrollOrResize, { capture: true } as EventListenerOptions);
|
||||
window.removeEventListener('resize', this.handleScrollOrResize);
|
||||
zIndexRegistry.unregister(this);
|
||||
|
||||
if (this.windowLayer) {
|
||||
this.windowLayer.destroy();
|
||||
this.windowLayer = null;
|
||||
}
|
||||
|
||||
this.visible = false;
|
||||
await domtools.plugins.smartdelay.delayFor(150);
|
||||
|
||||
if (this.parentElement) this.parentElement.removeChild(this);
|
||||
this.isDestroying = false;
|
||||
}
|
||||
|
||||
private handleScrollOrResize = (): void => {
|
||||
this.dispatchEvent(new CustomEvent('reposition-request'));
|
||||
};
|
||||
|
||||
async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
window.removeEventListener('scroll', this.handleScrollOrResize, { capture: true } as EventListenerOptions);
|
||||
window.removeEventListener('resize', this.handleScrollOrResize);
|
||||
zIndexRegistry.unregister(this);
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './component.js';
|
||||
export * from './datepicker-popup.js';
|
||||
|
||||
@@ -56,7 +56,6 @@ export const datepickerStyles = [
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Icon container using flexbox for better positioning */
|
||||
.icon-container {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
@@ -101,414 +100,5 @@ export const datepickerStyles = [
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
}
|
||||
|
||||
.clear-button:disabled {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Calendar Popup Styles */
|
||||
.calendar-popup {
|
||||
will-change: transform, opacity;
|
||||
pointer-events: none;
|
||||
transition: all 0.2s ease;
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
box-shadow: ${cssManager.bdTheme(
|
||||
'0 10px 15px -3px hsl(0 0% 0% / 0.1), 0 4px 6px -4px hsl(0 0% 0% / 0.1)',
|
||||
'0 10px 15px -3px hsl(0 0% 0% / 0.2), 0 4px 6px -4px hsl(0 0% 0% / 0.2)'
|
||||
)};
|
||||
border-radius: 6px;
|
||||
padding: 12px;
|
||||
position: absolute;
|
||||
user-select: none;
|
||||
margin-top: 4px;
|
||||
z-index: 50;
|
||||
left: 0;
|
||||
min-width: 280px;
|
||||
}
|
||||
|
||||
.calendar-popup.top {
|
||||
bottom: calc(100% + 4px);
|
||||
top: auto;
|
||||
margin-top: 0;
|
||||
margin-bottom: 4px;
|
||||
transform: translateY(4px);
|
||||
}
|
||||
|
||||
.calendar-popup.bottom {
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.calendar-popup.show {
|
||||
pointer-events: all;
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Calendar Header */
|
||||
.calendar-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.month-year-display {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-button {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.nav-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
}
|
||||
|
||||
.nav-button:active {
|
||||
background: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
}
|
||||
|
||||
/* Weekday headers */
|
||||
.weekdays {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 0;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.weekday {
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
padding: 0 0 8px 0;
|
||||
}
|
||||
|
||||
/* Days grid */
|
||||
.days-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.day {
|
||||
aspect-ratio: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
transition: all 0.2s ease;
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
border: none;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.day:hover:not(.disabled) {
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
}
|
||||
|
||||
.day.other-month {
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.day.today {
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.day.selected {
|
||||
background: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')};
|
||||
color: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(222.2 47.4% 11.2%)')};
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.day.disabled {
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
cursor: not-allowed;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* Event indicators */
|
||||
.day.has-event {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.event-indicator {
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.event-dot {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
border-radius: 50%;
|
||||
background: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.event-dot.info {
|
||||
background: ${cssManager.bdTheme('hsl(211 70% 52%)', 'hsl(211 70% 62%)')};
|
||||
}
|
||||
|
||||
.event-dot.warning {
|
||||
background: ${cssManager.bdTheme('hsl(45 90% 45%)', 'hsl(45 90% 55%)')};
|
||||
}
|
||||
|
||||
.event-dot.success {
|
||||
background: ${cssManager.bdTheme('hsl(142 69% 45%)', 'hsl(142 69% 55%)')};
|
||||
}
|
||||
|
||||
.event-dot.error {
|
||||
background: ${cssManager.bdTheme('hsl(0 72% 51%)', 'hsl(0 72% 61%)')};
|
||||
}
|
||||
|
||||
.event-count {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 2px;
|
||||
min-width: 16px;
|
||||
height: 16px;
|
||||
padding: 0 4px;
|
||||
background: ${cssManager.bdTheme('hsl(0 72% 51%)', 'hsl(0 72% 61%)')};
|
||||
color: white;
|
||||
border-radius: 8px;
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Tooltip for event details */
|
||||
.event-tooltip {
|
||||
position: absolute;
|
||||
bottom: calc(100% + 8px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 0%)')};
|
||||
padding: 8px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: 10;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.event-tooltip::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
border: 4px solid transparent;
|
||||
border-top-color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 90%)')};
|
||||
}
|
||||
|
||||
.day.has-event:hover .event-tooltip {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Time selector */
|
||||
.time-selector {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
}
|
||||
|
||||
.time-selector-title {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.time-inputs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.time-input {
|
||||
width: 65px;
|
||||
height: 36px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')};
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.time-input:hover {
|
||||
border-color: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
}
|
||||
|
||||
.time-input:focus {
|
||||
outline: none;
|
||||
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')};
|
||||
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(222.2 47.4% 11.2% / 0.1)', 'hsl(210 20% 98% / 0.1)')};
|
||||
}
|
||||
|
||||
.time-separator {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.am-pm-selector {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.am-pm-button {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')};
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.am-pm-button.selected {
|
||||
background: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')};
|
||||
color: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(222.2 47.4% 11.2%)')};
|
||||
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')};
|
||||
}
|
||||
|
||||
.am-pm-button:hover:not(.selected) {
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
border-color: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
}
|
||||
|
||||
/* Action buttons */
|
||||
.calendar-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
}
|
||||
|
||||
.action-button {
|
||||
flex: 1;
|
||||
height: 36px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.today-button {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
}
|
||||
|
||||
.today-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
border-color: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
}
|
||||
|
||||
.today-button:active {
|
||||
background: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
}
|
||||
|
||||
.clear-button {
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.clear-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 72.2% 50.6% / 0.1)', 'hsl(0 62.8% 30.6% / 0.1)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 72.2% 50.6%)', 'hsl(0 62.8% 30.6%)')};
|
||||
}
|
||||
|
||||
.clear-button:active {
|
||||
background: ${cssManager.bdTheme('hsl(0 72.2% 50.6% / 0.2)', 'hsl(0 62.8% 30.6% / 0.2)')};
|
||||
}
|
||||
|
||||
/* Timezone selector */
|
||||
.timezone-selector {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
}
|
||||
|
||||
.timezone-selector-title {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8px;
|
||||
color: ${cssManager.bdTheme('hsl(220 8.9% 46.1%)', 'hsl(215 20.2% 65.1%)')};
|
||||
}
|
||||
|
||||
.timezone-select {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
border-radius: 6px;
|
||||
padding: 0 12px;
|
||||
font-size: 14px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(224 71.4% 4.1%)')};
|
||||
color: ${cssManager.bdTheme('hsl(224 71.4% 4.1%)', 'hsl(210 20% 98%)')};
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.timezone-select:hover {
|
||||
border-color: ${cssManager.bdTheme('hsl(214.3 31.8% 91.4%)', 'hsl(217.2 32.6% 17.5%)')};
|
||||
background: ${cssManager.bdTheme('hsl(210 20% 98%)', 'hsl(215 27.9% 16.9%)')};
|
||||
}
|
||||
|
||||
.timezone-select:focus {
|
||||
outline: none;
|
||||
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 11.2%)', 'hsl(210 20% 98%)')};
|
||||
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(222.2 47.4% 11.2% / 0.1)', 'hsl(210 20% 98% / 0.1)')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
];
|
||||
|
||||
@@ -2,19 +2,6 @@ import { html, type TemplateResult } from '@design.estate/dees-element';
|
||||
import type { DeesInputDatepicker } from './component.js';
|
||||
|
||||
export const renderDatepicker = (component: DeesInputDatepicker): TemplateResult => {
|
||||
const monthNames = [
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December'
|
||||
];
|
||||
|
||||
const weekDays = component.weekStartsOn === 1
|
||||
? ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su']
|
||||
: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
|
||||
|
||||
const days = component.getDaysInMonth();
|
||||
const isAM = component.selectedHour < 12;
|
||||
const timezones = component.getTimezones();
|
||||
|
||||
return html`
|
||||
<div class="input-wrapper">
|
||||
<dees-label .label=${component.label} .description=${component.description} .required=${component.required}></dees-label>
|
||||
@@ -39,141 +26,8 @@ export const renderDatepicker = (component: DeesInputDatepicker): TemplateResult
|
||||
` : ''}
|
||||
<dees-icon class="calendar-icon" icon="lucide:calendar" iconSize="16"></dees-icon>
|
||||
</div>
|
||||
|
||||
<!-- Calendar Popup -->
|
||||
<div class="calendar-popup ${component.isOpened ? 'show' : ''} ${component.opensToTop ? 'top' : 'bottom'}">
|
||||
<!-- Month/Year Navigation -->
|
||||
<div class="calendar-header">
|
||||
<button class="nav-button" @click=${component.previousMonth}>
|
||||
<dees-icon icon="lucide:chevronLeft" iconSize="16"></dees-icon>
|
||||
</button>
|
||||
<div class="month-year-display">
|
||||
${monthNames[component.viewDate.getMonth()]} ${component.viewDate.getFullYear()}
|
||||
</div>
|
||||
<button class="nav-button" @click=${component.nextMonth}>
|
||||
<dees-icon icon="lucide:chevronRight" iconSize="16"></dees-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Weekday Headers -->
|
||||
<div class="weekdays">
|
||||
${weekDays.map(day => html`<div class="weekday">${day}</div>`)}
|
||||
</div>
|
||||
|
||||
<!-- Days Grid -->
|
||||
<div class="days-grid">
|
||||
${days.map(day => {
|
||||
const isToday = component.isToday(day);
|
||||
const isSelected = component.isSelected(day);
|
||||
const isOtherMonth = day.getMonth() !== component.viewDate.getMonth();
|
||||
const isDisabled = component.isDisabled(day);
|
||||
const dayEvents = component.getEventsForDate(day);
|
||||
const hasEvents = dayEvents.length > 0;
|
||||
const totalEventCount = dayEvents.reduce((sum, event) => sum + (event.count || 1), 0);
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="day ${isOtherMonth ? 'other-month' : ''} ${isToday ? 'today' : ''} ${isSelected ? 'selected' : ''} ${isDisabled ? 'disabled' : ''} ${hasEvents ? 'has-event' : ''}"
|
||||
@click=${() => !isDisabled && component.selectDate(day)}
|
||||
>
|
||||
${day.getDate()}
|
||||
${hasEvents ? html`
|
||||
${totalEventCount > 3 ? html`
|
||||
<div class="event-count">${totalEventCount}</div>
|
||||
` : html`
|
||||
<div class="event-indicator">
|
||||
${dayEvents.slice(0, 3).map(event => html`
|
||||
<div class="event-dot ${event.type || 'info'}"></div>
|
||||
`)}
|
||||
</div>
|
||||
`}
|
||||
${dayEvents[0].title ? html`
|
||||
<div class="event-tooltip">
|
||||
${dayEvents[0].title}
|
||||
${totalEventCount > 1 ? html` (+${totalEventCount - 1} more)` : ''}
|
||||
</div>
|
||||
` : ''}
|
||||
` : ''}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
|
||||
<!-- Time Selector -->
|
||||
${component.enableTime ? html`
|
||||
<div class="time-selector">
|
||||
<div class="time-selector-title">Time</div>
|
||||
<div class="time-inputs">
|
||||
<input
|
||||
type="number"
|
||||
class="time-input"
|
||||
.value=${component.timeFormat === '12h'
|
||||
? (component.selectedHour === 0 ? 12 : component.selectedHour > 12 ? component.selectedHour - 12 : component.selectedHour).toString().padStart(2, '0')
|
||||
: component.selectedHour.toString().padStart(2, '0')}
|
||||
@input=${(e: InputEvent) => component.handleHourInput(e)}
|
||||
min="${component.timeFormat === '12h' ? 1 : 0}"
|
||||
max="${component.timeFormat === '12h' ? 12 : 23}"
|
||||
/>
|
||||
<span class="time-separator">:</span>
|
||||
<input
|
||||
type="number"
|
||||
class="time-input"
|
||||
.value=${component.selectedMinute.toString().padStart(2, '0')}
|
||||
@input=${(e: InputEvent) => component.handleMinuteInput(e)}
|
||||
min="0"
|
||||
max="59"
|
||||
step="${component.minuteIncrement || 1}"
|
||||
/>
|
||||
${component.timeFormat === '12h' ? html`
|
||||
<div class="am-pm-selector">
|
||||
<button
|
||||
class="am-pm-button ${isAM ? 'selected' : ''}"
|
||||
@click=${() => component.setAMPM('am')}
|
||||
>
|
||||
AM
|
||||
</button>
|
||||
<button
|
||||
class="am-pm-button ${!isAM ? 'selected' : ''}"
|
||||
@click=${() => component.setAMPM('pm')}
|
||||
>
|
||||
PM
|
||||
</button>
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Timezone Selector -->
|
||||
${component.enableTimezone ? html`
|
||||
<div class="timezone-selector">
|
||||
<div class="timezone-selector-title">Timezone</div>
|
||||
<select
|
||||
class="timezone-select"
|
||||
.value=${component.timezone}
|
||||
@change=${(e: Event) => component.handleTimezoneChange(e)}
|
||||
>
|
||||
${timezones.map(tz => html`
|
||||
<option value="${tz.value}" ?selected=${tz.value === component.timezone}>
|
||||
${tz.label}
|
||||
</option>
|
||||
`)}
|
||||
</select>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="calendar-actions">
|
||||
<button class="action-button today-button" @click=${component.selectToday}>
|
||||
Today
|
||||
</button>
|
||||
<button class="action-button clear-button" @click=${component.clear}>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,374 @@
|
||||
import {
|
||||
customElement,
|
||||
type TemplateResult,
|
||||
property,
|
||||
state,
|
||||
html,
|
||||
css,
|
||||
cssManager,
|
||||
DeesElement,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { zIndexRegistry } from '../../00zindex.js';
|
||||
import { cssGeistFontFamily } from '../../00fonts.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-input-dropdown-popup': DeesInputDropdownPopup;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-input-dropdown-popup')
|
||||
export class DeesInputDropdownPopup extends DeesElement {
|
||||
@property({ type: Array })
|
||||
accessor options: { option: string; key: string; payload?: any }[] = [];
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor enableSearch: boolean = true;
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor opensToTop: boolean = false;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor triggerRect: DOMRect | null = null;
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor ownerComponent: HTMLElement | null = null;
|
||||
|
||||
@state()
|
||||
accessor filteredOptions: { option: string; key: string; payload?: any }[] = [];
|
||||
|
||||
@state()
|
||||
accessor highlightedIndex: number = 0;
|
||||
|
||||
@state()
|
||||
accessor searchValue: string = '';
|
||||
|
||||
@state()
|
||||
accessor menuZIndex: number = 1000;
|
||||
|
||||
@state()
|
||||
accessor visible: boolean = false;
|
||||
|
||||
private windowLayer: DeesWindowLayer | null = null;
|
||||
private isDestroying: boolean = false;
|
||||
|
||||
public static styles = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
font-family: ${cssGeistFontFamily};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.selectionBox {
|
||||
position: fixed;
|
||||
pointer-events: auto;
|
||||
will-change: transform, opacity;
|
||||
transition: all 0.15s ease;
|
||||
opacity: 0;
|
||||
transform: translateY(-8px) scale(0.98);
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
box-shadow: 0 4px 6px -1px hsl(0 0% 0% / 0.1), 0 2px 4px -2px hsl(0 0% 0% / 0.1);
|
||||
min-height: 40px;
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.selectionBox.top {
|
||||
transform: translateY(8px) scale(0.98);
|
||||
}
|
||||
|
||||
.selectionBox.show {
|
||||
pointer-events: auto;
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.options-container {
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.option {
|
||||
transition: all 0.15s ease;
|
||||
line-height: 32px;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
margin: 2px 0;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||
}
|
||||
|
||||
.option.highlighted {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
}
|
||||
|
||||
.no-options {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.search {
|
||||
padding: 4px;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.search input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
background: transparent;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-radius: 4px;
|
||||
color: inherit;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
transition: border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.search input::placeholder {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
|
||||
}
|
||||
|
||||
.search input:focus {
|
||||
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||
}
|
||||
|
||||
.options-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.options-container::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.options-container::-webkit-scrollbar-thumb {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.options-container::-webkit-scrollbar-thumb:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
public render(): TemplateResult {
|
||||
if (!this.triggerRect) return html``;
|
||||
|
||||
const posStyle = this.computePositionStyle();
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="selectionBox ${this.visible ? 'show' : ''} ${this.opensToTop ? 'top' : 'bottom'}"
|
||||
style="${posStyle}; z-index: ${this.menuZIndex};"
|
||||
>
|
||||
${this.enableSearch
|
||||
? html`
|
||||
<div class="search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search options..."
|
||||
.value="${this.searchValue}"
|
||||
@input="${this.handleSearch}"
|
||||
@click="${(e: Event) => e.stopPropagation()}"
|
||||
@keydown="${this.handleSearchKeydown}"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
: null}
|
||||
<div class="options-container">
|
||||
${this.filteredOptions.length === 0
|
||||
? html`<div class="no-options">No options found</div>`
|
||||
: this.filteredOptions.map((option, index) => {
|
||||
const isHighlighted = this.highlightedIndex === index;
|
||||
return html`
|
||||
<div
|
||||
class="option ${isHighlighted ? 'highlighted' : ''}"
|
||||
@click="${() => this.selectOption(option)}"
|
||||
@mouseenter="${() => (this.highlightedIndex = index)}"
|
||||
>
|
||||
${option.option}
|
||||
</div>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private computePositionStyle(): string {
|
||||
const rect = this.triggerRect!;
|
||||
const left = rect.left;
|
||||
const width = rect.width;
|
||||
|
||||
if (this.opensToTop) {
|
||||
const bottom = window.innerHeight - rect.top + 4;
|
||||
return `left: ${left}px; width: ${width}px; bottom: ${bottom}px; top: auto`;
|
||||
} else {
|
||||
const top = rect.bottom + 4;
|
||||
return `left: ${left}px; width: ${width}px; top: ${top}px`;
|
||||
}
|
||||
}
|
||||
|
||||
public async show(): Promise<void> {
|
||||
this.filteredOptions = this.options;
|
||||
this.highlightedIndex = 0;
|
||||
this.searchValue = '';
|
||||
|
||||
// Create window layer (transparent, no blur)
|
||||
this.windowLayer = await DeesWindowLayer.createAndShow();
|
||||
this.windowLayer.addEventListener('click', () => {
|
||||
this.dispatchEvent(new CustomEvent('close-request'));
|
||||
});
|
||||
|
||||
// Set z-index above the window layer
|
||||
this.menuZIndex = zIndexRegistry.getNextZIndex();
|
||||
zIndexRegistry.register(this, this.menuZIndex);
|
||||
this.style.zIndex = this.menuZIndex.toString();
|
||||
|
||||
document.body.appendChild(this);
|
||||
|
||||
// Animate in on next frame
|
||||
await domtools.plugins.smartdelay.delayFor(0);
|
||||
this.visible = true;
|
||||
|
||||
// Add scroll/resize listeners for repositioning
|
||||
window.addEventListener('scroll', this.handleScrollOrResize, { capture: true, passive: true });
|
||||
window.addEventListener('resize', this.handleScrollOrResize, { passive: true });
|
||||
}
|
||||
|
||||
public async hide(): Promise<void> {
|
||||
// Guard against double-destruction
|
||||
if (this.isDestroying) {
|
||||
return;
|
||||
}
|
||||
this.isDestroying = true;
|
||||
|
||||
// Remove scroll/resize listeners
|
||||
window.removeEventListener('scroll', this.handleScrollOrResize, { capture: true } as EventListenerOptions);
|
||||
window.removeEventListener('resize', this.handleScrollOrResize);
|
||||
|
||||
zIndexRegistry.unregister(this);
|
||||
|
||||
this.searchValue = '';
|
||||
this.filteredOptions = this.options;
|
||||
this.highlightedIndex = 0;
|
||||
|
||||
// Don't await - let window layer cleanup happen in background for instant visual feedback
|
||||
if (this.windowLayer) {
|
||||
this.windowLayer.destroy();
|
||||
this.windowLayer = null;
|
||||
}
|
||||
|
||||
// Animate out via CSS transition
|
||||
this.visible = false;
|
||||
await domtools.plugins.smartdelay.delayFor(150);
|
||||
|
||||
if (this.parentElement) {
|
||||
this.parentElement.removeChild(this);
|
||||
}
|
||||
|
||||
this.isDestroying = false;
|
||||
}
|
||||
|
||||
public async focusSearchInput(): Promise<void> {
|
||||
await this.updateComplete;
|
||||
const input = this.shadowRoot!.querySelector('.search input') as HTMLInputElement;
|
||||
if (input) input.focus();
|
||||
}
|
||||
|
||||
public updateOptions(options: { option: string; key: string; payload?: any }[]): void {
|
||||
this.options = options;
|
||||
// Re-filter with current search value
|
||||
if (this.searchValue) {
|
||||
const searchLower = this.searchValue.toLowerCase();
|
||||
this.filteredOptions = this.options.filter((opt) =>
|
||||
opt.option.toLowerCase().includes(searchLower)
|
||||
);
|
||||
} else {
|
||||
this.filteredOptions = this.options;
|
||||
}
|
||||
this.highlightedIndex = 0;
|
||||
}
|
||||
|
||||
private selectOption(option: { option: string; key: string; payload?: any }): void {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('option-selected', {
|
||||
detail: option,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private handleSearch = (event: Event): void => {
|
||||
const searchTerm = (event.target as HTMLInputElement).value;
|
||||
this.searchValue = searchTerm;
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
this.filteredOptions = this.options.filter((option) =>
|
||||
option.option.toLowerCase().includes(searchLower)
|
||||
);
|
||||
this.highlightedIndex = 0;
|
||||
};
|
||||
|
||||
private handleSearchKeydown = (event: KeyboardEvent): void => {
|
||||
const key = event.key;
|
||||
const maxIndex = this.filteredOptions.length - 1;
|
||||
|
||||
if (key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
this.highlightedIndex = this.highlightedIndex + 1 > maxIndex ? 0 : this.highlightedIndex + 1;
|
||||
} else if (key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
this.highlightedIndex = this.highlightedIndex - 1 < 0 ? maxIndex : this.highlightedIndex - 1;
|
||||
} else if (key === 'Enter') {
|
||||
event.preventDefault();
|
||||
if (this.filteredOptions[this.highlightedIndex]) {
|
||||
this.selectOption(this.filteredOptions[this.highlightedIndex]);
|
||||
}
|
||||
} else if (key === 'Escape') {
|
||||
event.preventDefault();
|
||||
this.dispatchEvent(new CustomEvent('close-request'));
|
||||
}
|
||||
};
|
||||
|
||||
private handleScrollOrResize = (): void => {
|
||||
this.dispatchEvent(new CustomEvent('reposition-request'));
|
||||
};
|
||||
|
||||
async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
window.removeEventListener('scroll', this.handleScrollOrResize, { capture: true } as EventListenerOptions);
|
||||
window.removeEventListener('resize', this.handleScrollOrResize);
|
||||
zIndexRegistry.unregister(this);
|
||||
}
|
||||
}
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { demoFunc } from './dees-input-dropdown.demo.js';
|
||||
import { DeesInputBase } from '../dees-input-base/dees-input-base.js';
|
||||
import { cssGeistFontFamily } from '../../00fonts.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import { DeesInputDropdownPopup } from './dees-input-dropdown-popup.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -46,27 +46,16 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
||||
})
|
||||
accessor enableSearch: boolean = true;
|
||||
|
||||
@state()
|
||||
accessor opensToTop: boolean = false;
|
||||
|
||||
@state()
|
||||
accessor filteredOptions: { option: string; key: string; payload?: any }[] = [];
|
||||
|
||||
@state()
|
||||
accessor highlightedIndex: number = 0;
|
||||
|
||||
@state()
|
||||
accessor isOpened = false;
|
||||
|
||||
@state()
|
||||
accessor searchValue: string = '';
|
||||
private popupInstance: DeesInputDropdownPopup | null = null;
|
||||
|
||||
public static styles = [
|
||||
themeDefaultStyles,
|
||||
...DeesInputBase.baseStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
@@ -137,137 +126,6 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
||||
.selectedBox.open::after {
|
||||
transform: translateY(-50%) rotate(180deg);
|
||||
}
|
||||
|
||||
.selectionBox {
|
||||
will-change: transform, opacity;
|
||||
pointer-events: none;
|
||||
transition: all 0.15s ease;
|
||||
opacity: 0;
|
||||
transform: translateY(-8px) scale(0.98);
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
box-shadow: 0 4px 6px -1px hsl(0 0% 0% / 0.1), 0 2px 4px -2px hsl(0 0% 0% / 0.1);
|
||||
min-height: 40px;
|
||||
max-height: 300px;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
position: absolute;
|
||||
user-select: none;
|
||||
margin-top: 4px;
|
||||
z-index: 50;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.selectionBox.top {
|
||||
bottom: calc(100% + 4px);
|
||||
top: auto;
|
||||
margin-top: 0;
|
||||
margin-bottom: 4px;
|
||||
transform: translateY(8px) scale(0.98);
|
||||
}
|
||||
|
||||
.selectionBox.bottom {
|
||||
top: 100%;
|
||||
}
|
||||
|
||||
.selectionBox.show {
|
||||
pointer-events: all;
|
||||
transform: translateY(0) scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Options container */
|
||||
.options-container {
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
/* Options */
|
||||
.option {
|
||||
transition: all 0.15s ease;
|
||||
line-height: 32px;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
margin: 2px 0;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||
}
|
||||
|
||||
.option.highlighted {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
}
|
||||
|
||||
.option:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
}
|
||||
|
||||
/* No options message */
|
||||
.no-options {
|
||||
padding: 8px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Search */
|
||||
.search {
|
||||
padding: 4px;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.search.bottom {
|
||||
border-bottom: none;
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
margin-bottom: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.search input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 32px;
|
||||
padding: 0 8px;
|
||||
background: transparent;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-radius: 4px;
|
||||
color: inherit;
|
||||
font-size: 14px;
|
||||
font-family: inherit;
|
||||
outline: none;
|
||||
transition: border-color 0.15s ease;
|
||||
}
|
||||
|
||||
.search input::placeholder {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 63.9%)', 'hsl(0 0% 45.1%)')};
|
||||
}
|
||||
|
||||
.search input:focus {
|
||||
border-color: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2%)', 'hsl(217.2 91.2% 59.8%)')};
|
||||
}
|
||||
|
||||
/* Scrollbar styling */
|
||||
.options-container::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.options-container::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.options-container::-webkit-scrollbar-thumb {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.options-container::-webkit-scrollbar-thumb:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -284,68 +142,26 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
||||
>
|
||||
${this.selectedOption?.option || 'Select an option'}
|
||||
</div>
|
||||
<div class="selectionBox ${this.isOpened ? 'show' : ''} ${this.opensToTop ? 'top' : 'bottom'}">
|
||||
${this.enableSearch
|
||||
? html`
|
||||
<div class="search">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search options..."
|
||||
.value="${this.searchValue}"
|
||||
@input="${this.handleSearch}"
|
||||
@click="${(e: Event) => e.stopPropagation()}"
|
||||
@keydown="${this.handleSearchKeydown}"
|
||||
/>
|
||||
</div>
|
||||
`
|
||||
: null}
|
||||
<div class="options-container">
|
||||
${this.filteredOptions.length === 0
|
||||
? html`<div class="no-options">No options found</div>`
|
||||
: this.filteredOptions.map((option, index) => {
|
||||
const isHighlighted = this.highlightedIndex === index;
|
||||
return html`
|
||||
<div
|
||||
class="option ${isHighlighted ? 'highlighted' : ''}"
|
||||
@click="${() => this.updateSelection(option)}"
|
||||
@mouseenter="${() => this.highlightedIndex = index}"
|
||||
>
|
||||
${option.option}
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.selectedOption = this.selectedOption || null;
|
||||
this.filteredOptions = this.options;
|
||||
}
|
||||
|
||||
updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('options')) {
|
||||
this.filteredOptions = this.options;
|
||||
|
||||
if (changedProperties.has('options') && this.popupInstance && this.isOpened) {
|
||||
this.popupInstance.updateOptions(this.options);
|
||||
}
|
||||
}
|
||||
|
||||
public async updateSelection(selectedOption: { option: string; key: string; payload?: any }) {
|
||||
this.selectedOption = selectedOption;
|
||||
this.isOpened = false;
|
||||
this.searchValue = '';
|
||||
this.filteredOptions = this.options;
|
||||
this.highlightedIndex = 0;
|
||||
this.closePopup();
|
||||
|
||||
this.dispatchEvent(
|
||||
new CustomEvent('selectedOption', {
|
||||
@@ -353,92 +169,95 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
||||
bubbles: true,
|
||||
})
|
||||
);
|
||||
|
||||
|
||||
this.changeSubject.next(this);
|
||||
}
|
||||
|
||||
private handleClickOutside = (event: MouseEvent) => {
|
||||
const path = event.composedPath();
|
||||
if (!path.includes(this)) {
|
||||
this.isOpened = false;
|
||||
this.searchValue = '';
|
||||
this.filteredOptions = this.options;
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
}
|
||||
};
|
||||
|
||||
public async toggleSelectionBox() {
|
||||
this.isOpened = !this.isOpened;
|
||||
|
||||
if (this.isOpened) {
|
||||
// Check available space and set position
|
||||
const selectedBox = this.shadowRoot!.querySelector('.selectedBox') as HTMLElement;
|
||||
const rect = selectedBox.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const spaceAbove = rect.top;
|
||||
|
||||
// Determine if we should open upwards
|
||||
this.opensToTop = spaceBelow < 300 && spaceAbove > spaceBelow;
|
||||
|
||||
// Focus search input if present
|
||||
await this.updateComplete;
|
||||
const searchInput = this.shadowRoot!.querySelector('.search input') as HTMLInputElement;
|
||||
if (searchInput) {
|
||||
searchInput.focus();
|
||||
}
|
||||
|
||||
// Add click outside listener
|
||||
setTimeout(() => {
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
}, 0);
|
||||
} else {
|
||||
// Cleanup
|
||||
this.searchValue = '';
|
||||
this.filteredOptions = this.options;
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
this.closePopup();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isOpened = true;
|
||||
|
||||
// Get trigger position
|
||||
const selectedBox = this.shadowRoot!.querySelector('.selectedBox') as HTMLElement;
|
||||
const rect = selectedBox.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const spaceAbove = rect.top;
|
||||
const opensToTop = spaceBelow < 300 && spaceAbove > spaceBelow;
|
||||
|
||||
// Create popup if needed
|
||||
if (!this.popupInstance) {
|
||||
this.popupInstance = new DeesInputDropdownPopup();
|
||||
}
|
||||
|
||||
// Configure popup
|
||||
this.popupInstance.options = this.options;
|
||||
this.popupInstance.enableSearch = this.enableSearch;
|
||||
this.popupInstance.opensToTop = opensToTop;
|
||||
this.popupInstance.triggerRect = rect;
|
||||
this.popupInstance.ownerComponent = this;
|
||||
|
||||
// Listen for popup events
|
||||
this.popupInstance.addEventListener('option-selected', this.handleOptionSelected);
|
||||
this.popupInstance.addEventListener('close-request', this.handleCloseRequest);
|
||||
this.popupInstance.addEventListener('reposition-request', this.handleRepositionRequest);
|
||||
|
||||
// Show popup (creates window layer, appends to document.body)
|
||||
await this.popupInstance.show();
|
||||
|
||||
// Focus search input
|
||||
if (this.enableSearch) {
|
||||
await this.popupInstance.focusSearchInput();
|
||||
}
|
||||
}
|
||||
|
||||
private handleSearch(event: Event): void {
|
||||
const searchTerm = (event.target as HTMLInputElement).value;
|
||||
this.searchValue = searchTerm;
|
||||
const searchLower = searchTerm.toLowerCase();
|
||||
this.filteredOptions = this.options.filter((option) =>
|
||||
option.option.toLowerCase().includes(searchLower)
|
||||
);
|
||||
this.highlightedIndex = 0;
|
||||
}
|
||||
private closePopup(): void {
|
||||
this.isOpened = false;
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent): void {
|
||||
const key = event.key;
|
||||
const maxIndex = this.filteredOptions.length - 1;
|
||||
|
||||
if (key === 'ArrowDown') {
|
||||
event.preventDefault();
|
||||
this.highlightedIndex = this.highlightedIndex + 1 > maxIndex ? 0 : this.highlightedIndex + 1;
|
||||
} else if (key === 'ArrowUp') {
|
||||
event.preventDefault();
|
||||
this.highlightedIndex = this.highlightedIndex - 1 < 0 ? maxIndex : this.highlightedIndex - 1;
|
||||
} else if (key === 'Enter') {
|
||||
event.preventDefault();
|
||||
if (this.filteredOptions[this.highlightedIndex]) {
|
||||
this.updateSelection(this.filteredOptions[this.highlightedIndex]);
|
||||
}
|
||||
} else if (key === 'Escape') {
|
||||
event.preventDefault();
|
||||
this.isOpened = false;
|
||||
if (this.popupInstance) {
|
||||
this.popupInstance.removeEventListener('option-selected', this.handleOptionSelected);
|
||||
this.popupInstance.removeEventListener('close-request', this.handleCloseRequest);
|
||||
this.popupInstance.removeEventListener('reposition-request', this.handleRepositionRequest);
|
||||
this.popupInstance.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private handleSearchKeydown(event: KeyboardEvent): void {
|
||||
if (event.key === 'ArrowDown' || event.key === 'ArrowUp' || event.key === 'Enter') {
|
||||
this.handleKeyDown(event);
|
||||
private handleOptionSelected = (event: Event): void => {
|
||||
const detail = (event as CustomEvent).detail;
|
||||
this.updateSelection(detail);
|
||||
};
|
||||
|
||||
private handleCloseRequest = (): void => {
|
||||
this.closePopup();
|
||||
};
|
||||
|
||||
private handleRepositionRequest = (): void => {
|
||||
if (!this.popupInstance || !this.isOpened) return;
|
||||
|
||||
const selectedBox = this.shadowRoot!.querySelector('.selectedBox') as HTMLElement;
|
||||
if (!selectedBox) return;
|
||||
|
||||
const rect = selectedBox.getBoundingClientRect();
|
||||
|
||||
// Close if trigger scrolled off-screen
|
||||
if (rect.bottom < 0 || rect.top > window.innerHeight) {
|
||||
this.closePopup();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update position
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const spaceAbove = rect.top;
|
||||
this.popupInstance.opensToTop = spaceBelow < 300 && spaceAbove > spaceBelow;
|
||||
this.popupInstance.triggerRect = rect;
|
||||
};
|
||||
|
||||
private handleSelectedBoxKeydown(event: KeyboardEvent) {
|
||||
if (this.disabled) return;
|
||||
|
||||
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
this.toggleSelectionBox();
|
||||
@@ -450,7 +269,7 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
||||
} else if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
if (this.isOpened) {
|
||||
this.isOpened = false;
|
||||
this.closePopup();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -462,9 +281,15 @@ export class DeesInputDropdown extends DeesInputBase<DeesInputDropdown> {
|
||||
public setValue(value: { option: string; key: string; payload?: any }): void {
|
||||
this.selectedOption = value;
|
||||
}
|
||||
|
||||
|
||||
async disconnectedCallback() {
|
||||
await super.disconnectedCallback();
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
if (this.popupInstance) {
|
||||
this.popupInstance.removeEventListener('option-selected', this.handleOptionSelected);
|
||||
this.popupInstance.removeEventListener('close-request', this.handleCloseRequest);
|
||||
this.popupInstance.removeEventListener('reposition-request', this.handleRepositionRequest);
|
||||
this.popupInstance.hide();
|
||||
this.popupInstance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export * from './dees-input-dropdown.js';
|
||||
export * from './dees-input-dropdown-popup.js';
|
||||
|
||||
@@ -3,6 +3,7 @@ import { demoFunc } from './demo.js';
|
||||
import { fileuploadStyles } from './styles.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import '../../00group-layout/dees-label/dees-label.js';
|
||||
import '../../00group-layout/dees-tile/dees-tile.js';
|
||||
|
||||
import {
|
||||
customElement,
|
||||
@@ -75,14 +76,13 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
.description=${this.description}
|
||||
.required=${this.required}
|
||||
></dees-label>
|
||||
<div
|
||||
class="dropzone ${this.state === 'dragOver' ? 'dropzone--active' : ''} ${this.disabled ? 'dropzone--disabled' : ''} ${this.value.length > 0 ? 'dropzone--has-files' : ''}"
|
||||
role="button"
|
||||
<dees-tile
|
||||
class="${this.state === 'dragOver' ? 'dragover' : ''}"
|
||||
@click=${this.handleDropzoneClick}
|
||||
@keydown=${this.handleDropzoneKeydown}
|
||||
tabindex=${this.disabled ? -1 : 0}
|
||||
aria-disabled=${this.disabled}
|
||||
aria-label=${`Select files${acceptedSummary ? ` (${acceptedSummary})` : ''}`}
|
||||
@click=${this.handleDropzoneClick}
|
||||
@keydown=${this.handleDropzoneKeydown}
|
||||
>
|
||||
<input
|
||||
class="file-input"
|
||||
@@ -94,32 +94,23 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
@change=${this.handleFileInputChange}
|
||||
tabindex="-1"
|
||||
/>
|
||||
<div class="dropzone__body">
|
||||
<div class="dropzone__icon">
|
||||
${this.isLoading
|
||||
? html`<span class="dropzone__loader" aria-hidden="true"></span>`
|
||||
: html`<dees-icon icon="lucide:FolderOpen"></dees-icon>`}
|
||||
</div>
|
||||
<div class="dropzone__content">
|
||||
<span class="dropzone__headline">${this.buttonText || 'Select files'}</span>
|
||||
<span class="dropzone__subline">
|
||||
Drag and drop files here or
|
||||
<button
|
||||
type="button"
|
||||
class="dropzone__browse"
|
||||
@click=${this.handleBrowseClick}
|
||||
?disabled=${this.disabled}
|
||||
>
|
||||
browse
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropzone__meta">
|
||||
${metaEntries.map((entry) => html`<span>${entry}</span>`)}
|
||||
<div slot="header" class="dropzone-header">
|
||||
${this.isLoading
|
||||
? html`<span class="dropzone-loader" aria-hidden="true"></span>`
|
||||
: html`<dees-icon icon="lucide:Upload"></dees-icon>`}
|
||||
<span class="dropzone-title">Drop files here or</span>
|
||||
<button
|
||||
type="button"
|
||||
class="dropzone-browse"
|
||||
@click=${(e: MouseEvent) => { e.stopPropagation(); this.openFileSelector(); }}
|
||||
?disabled=${this.disabled}
|
||||
>browse</button>
|
||||
</div>
|
||||
${this.renderFileList()}
|
||||
</div>
|
||||
<div slot="footer" class="dropzone-footer">
|
||||
${metaEntries.map((entry) => html`<span class="meta-chip">${entry}</span>`)}
|
||||
</div>
|
||||
</dees-tile>
|
||||
${this.validationMessage
|
||||
? html`<div class="validation-message" aria-live="polite">${this.validationMessage}</div>`
|
||||
: html``}
|
||||
@@ -129,20 +120,21 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
|
||||
private renderFileList(): TemplateResult {
|
||||
if (this.value.length === 0) {
|
||||
return html``;
|
||||
return html`
|
||||
<div class="file-list-empty">
|
||||
<dees-icon icon="lucide:FileStack"></dees-icon>
|
||||
<span>No files selected</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return html`
|
||||
<div class="file-list">
|
||||
<div class="file-list__header">
|
||||
<div class="file-list-header">
|
||||
<span>${this.value.length} file${this.value.length === 1 ? '' : 's'} selected</span>
|
||||
${this.value.length > 0
|
||||
? html`<button type="button" class="file-list__clear" @click=${this.handleClearAll}>Clear ${this.value.length > 1 ? 'all' : ''}</button>`
|
||||
: html``}
|
||||
</div>
|
||||
<div class="file-list__items">
|
||||
${this.value.map((file) => this.renderFileRow(file))}
|
||||
<button type="button" class="file-list-clear" @click=${this.handleClearAll}>Clear ${this.value.length > 1 ? 'all' : ''}</button>
|
||||
</div>
|
||||
${this.value.map((file) => this.renderFileRow(file))}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -193,21 +185,14 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
// Don't open file selector if clicking on the browse button or file list
|
||||
if ((event.target as HTMLElement).closest('.dropzone__browse, .file-list')) {
|
||||
// Don't open file selector when clicking file list items or actions
|
||||
const target = event.target as HTMLElement;
|
||||
if (target.closest('.file-list, .dropzone-header, .dropzone-footer')) {
|
||||
return;
|
||||
}
|
||||
this.openFileSelector();
|
||||
};
|
||||
|
||||
private handleBrowseClick = (event: MouseEvent) => {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
event.stopPropagation(); // Stop propagation to prevent double trigger
|
||||
this.openFileSelector();
|
||||
};
|
||||
|
||||
private handleDropzoneKeydown = (event: KeyboardEvent) => {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
@@ -280,7 +265,7 @@ export class DeesInputFileupload extends DeesInputBase<DeesInputFileupload> {
|
||||
}
|
||||
|
||||
private rebindInteractiveElements(): void {
|
||||
const newDropArea = this.shadowRoot?.querySelector('.dropzone') as HTMLElement | null;
|
||||
const newDropArea = this.shadowRoot?.querySelector('dees-tile') as HTMLElement | null;
|
||||
|
||||
if (newDropArea !== this.dropArea) {
|
||||
this.detachDropListeners();
|
||||
|
||||
@@ -1,201 +1,158 @@
|
||||
import { css, cssManager } from '@design.estate/dees-element';
|
||||
import { DeesInputBase } from '../dees-input-base/dees-input-base.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
export const fileuploadStyles = [
|
||||
cssManager.defaultStyles,
|
||||
themeDefaultStyles,
|
||||
...DeesInputBase.baseStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
position: relative;
|
||||
padding: 20px;
|
||||
border-radius: 12px;
|
||||
border: 1.5px dashed ${cssManager.bdTheme('hsl(215 16% 80%)', 'hsl(217 20% 25%)')};
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 20% 12%)')};
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
/* ── Tile integration ── */
|
||||
dees-tile {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.dropzone:focus-visible {
|
||||
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 20% 12%)')},
|
||||
0 0 0 4px ${cssManager.bdTheme('hsl(217 91% 60% / 0.5)', 'hsl(213 93% 68% / 0.4)')};
|
||||
border-color: ${cssManager.bdTheme('hsl(217 91% 60%)', 'hsl(213 93% 68%)')};
|
||||
dees-tile:hover::part(outer) {
|
||||
border-color: var(--dees-color-border-strong);
|
||||
}
|
||||
|
||||
.dropzone--active {
|
||||
border-color: ${cssManager.bdTheme('hsl(217 91% 60%)', 'hsl(213 93% 68%)')};
|
||||
box-shadow: 0 12px 32px ${cssManager.bdTheme('rgba(15, 23, 42, 0.12)', 'rgba(0, 0, 0, 0.35)')};
|
||||
background: ${cssManager.bdTheme('hsl(217 91% 60% / 0.06)', 'hsl(213 93% 68% / 0.12)')};
|
||||
dees-tile.dragover::part(outer) {
|
||||
border-color: var(--dees-color-accent-primary);
|
||||
box-shadow: 0 0 0 2px ${cssManager.bdTheme('hsl(217 91% 60% / 0.15)', 'hsl(213 93% 68% / 0.15)')};
|
||||
}
|
||||
|
||||
.dropzone--has-files {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 99%)', 'hsl(215 20% 11%)')};
|
||||
}
|
||||
|
||||
.dropzone--disabled {
|
||||
:host([disabled]) dees-tile {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dropzone__body {
|
||||
/* ── Header slot: sleek toolbar ── */
|
||||
.dropzone-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
gap: 8px;
|
||||
height: 32px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.dropzone__icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: ${cssManager.bdTheme('hsl(217 91% 60%)', 'hsl(213 93% 68%)')};
|
||||
background: ${cssManager.bdTheme('hsl(217 91% 60% / 0.12)', 'hsl(213 93% 68% / 0.12)')};
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
.dropzone-header dees-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.dropzone__icon dees-icon {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.dropzone__loader {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 999px;
|
||||
.dropzone-loader {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: var(--dees-radius-full);
|
||||
border: 2px solid ${cssManager.bdTheme('rgba(15, 23, 42, 0.15)', 'rgba(255, 255, 255, 0.15)')};
|
||||
border-top-color: ${cssManager.bdTheme('hsl(217 91% 60%)', 'hsl(213 93% 68%)')};
|
||||
border-top-color: var(--dees-color-accent-primary);
|
||||
animation: loader-spin 0.6s linear infinite;
|
||||
}
|
||||
|
||||
.dropzone__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dropzone__headline {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('hsl(222 47% 11%)', 'hsl(210 20% 96%)')};
|
||||
}
|
||||
|
||||
.dropzone__subline {
|
||||
.dropzone-title {
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('hsl(215 16% 46%)', 'hsl(215 16% 70%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.dropzone__browse {
|
||||
.dropzone-browse {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
padding: 0;
|
||||
margin-left: 4px;
|
||||
color: ${cssManager.bdTheme('hsl(217 91% 60%)', 'hsl(213 93% 68%)')};
|
||||
font-size: 13px;
|
||||
font-family: inherit;
|
||||
font-weight: 600;
|
||||
color: var(--dees-color-accent-primary);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.dropzone__browse:hover {
|
||||
.dropzone-browse:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.dropzone__browse:disabled {
|
||||
.dropzone-browse:disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.dropzone__meta {
|
||||
margin-top: 14px;
|
||||
/* ── Content slot: file list in rounded inset ── */
|
||||
.file-list-empty {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('hsl(215 16% 50%)', 'hsl(215 16% 72%)')};
|
||||
padding: 24px 16px;
|
||||
color: var(--dees-color-text-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.dropzone__meta span {
|
||||
padding: 4px 10px;
|
||||
border-radius: 999px;
|
||||
background: ${cssManager.bdTheme('hsl(217 91% 95%)', 'hsl(213 93% 18%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(217 91% 90%)', 'hsl(213 93% 24%)')};
|
||||
.file-list-empty dees-icon {
|
||||
font-size: 24px;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(217 91% 90%)', 'hsl(213 93% 24%)')};
|
||||
}
|
||||
|
||||
.file-list__header {
|
||||
.file-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 13px;
|
||||
padding: 8px 12px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('hsl(215 16% 45%)', 'hsl(215 16% 68%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.file-list__clear {
|
||||
.file-list-clear {
|
||||
appearance: none;
|
||||
border: none;
|
||||
background: none;
|
||||
color: ${cssManager.bdTheme('hsl(217 91% 60%)', 'hsl(213 93% 68%)')};
|
||||
color: var(--dees-color-accent-primary);
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
font-size: 12px;
|
||||
padding: 0;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.file-list__clear:hover {
|
||||
.file-list-clear:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.file-list__items {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.file-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 10px 12px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100% / 0.5)', 'hsl(215 20% 16% / 0.5)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(213 27% 92%)', 'hsl(217 25% 26%)')};
|
||||
border-radius: 8px;
|
||||
transition: background 0.15s ease;
|
||||
padding: 6px 12px;
|
||||
transition: background var(--dees-transition-fast) ease;
|
||||
}
|
||||
|
||||
.file-row:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100% / 0.8)', 'hsl(215 20% 16% / 0.8)')};
|
||||
background: var(--dees-color-row-hover);
|
||||
}
|
||||
|
||||
.file-thumb {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
background: ${cssManager.bdTheme('hsl(214 31% 92%)', 'hsl(217 32% 18%)')};
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: var(--dees-radius-sm);
|
||||
background: var(--dees-color-bg-tertiary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
@@ -204,16 +161,15 @@ export const fileuploadStyles = [
|
||||
}
|
||||
|
||||
.file-thumb dees-icon {
|
||||
font-size: 18px;
|
||||
color: ${cssManager.bdTheme('hsl(215 16% 45%)', 'hsl(215 16% 70%)')};
|
||||
font-size: 16px;
|
||||
color: var(--dees-color-text-muted);
|
||||
display: block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
line-height: 1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
|
||||
.thumb-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
@@ -223,14 +179,14 @@ export const fileuploadStyles = [
|
||||
.file-meta {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('hsl(222 47% 11%)', 'hsl(210 20% 96%)')};
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
color: var(--dees-color-text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -241,8 +197,8 @@ export const fileuploadStyles = [
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('hsl(215 16% 46%)', 'hsl(215 16% 70%)')};
|
||||
font-size: 11px;
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.file-size {
|
||||
@@ -250,39 +206,40 @@ export const fileuploadStyles = [
|
||||
}
|
||||
|
||||
.file-type {
|
||||
padding: 2px 8px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(214 31% 86%)', 'hsl(217 32% 28%)')};
|
||||
color: ${cssManager.bdTheme('hsl(215 16% 46%)', 'hsl(215 16% 70%)')};
|
||||
padding: 1px 6px;
|
||||
border-radius: var(--dees-radius-full);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
color: var(--dees-color-text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
line-height: 1;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.file-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.remove-button {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 6px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: var(--dees-radius-xs);
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background 0.15s ease, transform 0.15s ease, color 0.15s ease;
|
||||
color: ${cssManager.bdTheme('hsl(215 16% 52%)', 'hsl(215 16% 68%)')};
|
||||
transition: background var(--dees-transition-fast) ease,
|
||||
color var(--dees-transition-fast) ease;
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.remove-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 72% 50% / 0.08)', 'hsl(0 62% 32% / 0.15)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 72% 46%)', 'hsl(0 70% 70%)')};
|
||||
color: var(--dees-color-accent-error);
|
||||
}
|
||||
|
||||
.remove-button:active {
|
||||
@@ -298,9 +255,28 @@ export const fileuploadStyles = [
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* ── Footer slot: meta chips ── */
|
||||
.dropzone-footer {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.meta-chip {
|
||||
font-size: 11px;
|
||||
padding: 2px 8px;
|
||||
border-radius: var(--dees-radius-full);
|
||||
color: var(--dees-color-text-muted);
|
||||
background: var(--dees-color-bg-tertiary);
|
||||
border: 1px solid var(--dees-color-border-subtle);
|
||||
}
|
||||
|
||||
/* ── Validation ── */
|
||||
.validation-message {
|
||||
font-size: 13px;
|
||||
color: ${cssManager.bdTheme('hsl(0 72% 40%)', 'hsl(0 70% 68%)')};
|
||||
color: var(--dees-color-accent-error);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
@@ -239,6 +239,7 @@ export class DeesInputRichtext extends DeesInputBase<string> {
|
||||
this.editorElement = this.shadowRoot!.querySelector('.editor-content')!;
|
||||
this.linkInputElement = this.shadowRoot!.querySelector('.link-input input')!;
|
||||
this.initializeEditor();
|
||||
this.requestUpdate();
|
||||
}
|
||||
|
||||
private initializeEditor(): void {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { css, cssManager } from '@design.estate/dees-element';
|
||||
import { DeesInputBase } from '../dees-input-base/dees-input-base.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
export const richtextStyles = [
|
||||
themeDefaultStyles,
|
||||
...DeesInputBase.baseStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
@@ -20,7 +22,7 @@ export const richtextStyles = [
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
dees-tile {
|
||||
@@ -28,7 +30,7 @@ export const richtextStyles = [
|
||||
}
|
||||
|
||||
dees-tile:hover::part(outer) {
|
||||
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||
border-color: var(--dees-color-border-strong);
|
||||
}
|
||||
|
||||
dees-tile.focused::part(outer) {
|
||||
@@ -68,8 +70,8 @@ export const richtextStyles = [
|
||||
}
|
||||
|
||||
.toolbar-button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.toolbar-button.active {
|
||||
@@ -85,22 +87,24 @@ export const richtextStyles = [
|
||||
.toolbar-divider {
|
||||
width: 1px;
|
||||
height: 24px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
background: var(--dees-color-border-default);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.editor-content {
|
||||
flex: 1;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
min-height: var(--min-height, 200px);
|
||||
}
|
||||
|
||||
.editor-content .ProseMirror {
|
||||
outline: none;
|
||||
line-height: 1.6;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
min-height: 100%;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.editor-content .ProseMirror p {
|
||||
@@ -147,7 +151,7 @@ export const richtextStyles = [
|
||||
}
|
||||
|
||||
.editor-content .ProseMirror blockquote {
|
||||
border-left: 4px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border-left: 4px solid var(--dees-color-border-default);
|
||||
margin: 1em 0;
|
||||
padding-left: 1em;
|
||||
color: ${cssManager.bdTheme('hsl(215.4 16.3% 46.9%)', 'hsl(215 20.2% 65.1%)')};
|
||||
@@ -155,12 +159,12 @@ export const richtextStyles = [
|
||||
}
|
||||
|
||||
.editor-content .ProseMirror code {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
background: var(--dees-color-bg-tertiary);
|
||||
border-radius: 3px;
|
||||
padding: 0.2em 0.4em;
|
||||
font-family: 'Intel One Mono', 'Fira Code', 'SF Mono', Monaco, 'Cascadia Code', 'Roboto Mono', Consolas, 'Courier New', monospace;
|
||||
font-size: 0.9em;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 93.9%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.editor-content .ProseMirror pre {
|
||||
@@ -193,7 +197,7 @@ export const richtextStyles = [
|
||||
padding: 0 12px;
|
||||
height: 28px;
|
||||
font-size: 11px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45%)', 'hsl(0 0% 55%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
@@ -211,8 +215,8 @@ export const richtextStyles = [
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||
padding: 12px;
|
||||
@@ -226,12 +230,12 @@ export const richtextStyles = [
|
||||
.link-input input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 6px;
|
||||
outline: none;
|
||||
font-size: 14px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
color: var(--dees-color-text-primary);
|
||||
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
@@ -248,19 +252,19 @@ export const richtextStyles = [
|
||||
|
||||
.link-input-buttons button {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 4px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
transition: all 0.15s ease;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.link-input-buttons button:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 95.1%)', 'hsl(0 0% 14.9%)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.link-input-buttons button.primary {
|
||||
@@ -281,7 +285,7 @@ export const richtextStyles = [
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:host([disabled]) .editor-container {
|
||||
:host([disabled]) dees-tile {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export class DeesHeading extends DeesElement {
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
margin: 16px 0;
|
||||
color: ${cssManager.bdTheme('#000', '#fff')};
|
||||
color: ${cssManager.bdTheme('#999', '#555')};
|
||||
}
|
||||
/* Fade lines toward and away from text for hr style */
|
||||
.heading-hr::before {
|
||||
|
||||
@@ -56,7 +56,7 @@ export class DeesTile extends DeesElement {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: ${cssGeistFontFamily};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
/* --- The frame --- */
|
||||
@@ -64,8 +64,8 @@ export class DeesTile extends DeesElement {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
@@ -84,7 +84,7 @@ export class DeesTile extends DeesElement {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.01em;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
|
||||
/* --- Content: the rounded inset --- */
|
||||
@@ -92,8 +92,8 @@ export class DeesTile extends DeesElement {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 93%)', 'hsl(0 0% 11%)')};
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 93%)', 'hsl(0 0% 11%)')};
|
||||
border-top: 1px solid var(--dees-color-border-subtle);
|
||||
border-bottom: 1px solid var(--dees-color-border-subtle);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import { PdfManager } from '../dees-pdf-shared/PdfManager.js';
|
||||
import { viewerStyles } from './styles.js';
|
||||
import { demo as demoFunc } from './demo.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import '../../00group-layout/dees-tile/dees-tile.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
@@ -33,6 +34,9 @@ export class DeesPdfViewer extends DeesElement {
|
||||
@property({ type: Boolean })
|
||||
accessor showSidebar: boolean = false;
|
||||
|
||||
@property({ type: String })
|
||||
accessor sidebarPosition: 'left' | 'right' = 'left';
|
||||
|
||||
@property({ type: Number })
|
||||
accessor currentPage: number = 1;
|
||||
|
||||
@@ -54,6 +58,9 @@ export class DeesPdfViewer extends DeesElement {
|
||||
@property({ type: Array })
|
||||
accessor pageData: Array<{page: number, rendered: boolean, rendering: boolean, textLayerRendered: boolean}> = [];
|
||||
|
||||
@property({ type: Number })
|
||||
accessor pdfFileSize: number = 0;
|
||||
|
||||
private pdfDocument: any;
|
||||
private renderState: RenderState = 'idle';
|
||||
private renderAbortController: AbortController | null = null;
|
||||
@@ -85,9 +92,9 @@ export class DeesPdfViewer extends DeesElement {
|
||||
|
||||
public render(): TemplateResult {
|
||||
return html`
|
||||
<div class="pdf-viewer ${this.showSidebar ? 'with-sidebar' : ''}">
|
||||
<dees-tile class="${this.showSidebar ? 'with-sidebar' : ''} sidebar-${this.sidebarPosition}">
|
||||
${this.showToolbar ? html`
|
||||
<div class="toolbar">
|
||||
<div slot="header" class="toolbar">
|
||||
<div class="toolbar-group">
|
||||
<button
|
||||
class="toolbar-button"
|
||||
@@ -240,7 +247,23 @@ export class DeesPdfViewer extends DeesElement {
|
||||
`}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer" class="pdf-footer">
|
||||
<div class="pdf-footer-left">
|
||||
<span class="pdf-footer-item">Zoom ${Math.round(this.currentZoom * 100)}%</span>
|
||||
${this.pdfFileSize > 0 ? html`
|
||||
<span class="pdf-footer-item">${this.formatFileSize(this.pdfFileSize)}</span>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="pdf-footer-center" style="margin-left: ${this.showSidebar && this.sidebarPosition === 'left' ? '100px' : this.showSidebar && this.sidebarPosition === 'right' ? '-100px' : '0'}">
|
||||
<span>Page ${this.currentPage} of ${this.totalPages}</span>
|
||||
</div>
|
||||
<div class="pdf-footer-right">
|
||||
${this.pdfUrl ? html`
|
||||
<span class="pdf-footer-filename">${this.pdfUrl.split('/').pop()}</span>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
</dees-tile>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -305,6 +328,12 @@ export class DeesPdfViewer extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
private formatFileSize(bytes: number): string {
|
||||
if (bytes < 1024) return `${bytes} B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
||||
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
private async loadPdf() {
|
||||
this.loading = true;
|
||||
this.renderState = 'loading';
|
||||
@@ -323,6 +352,14 @@ export class DeesPdfViewer extends DeesElement {
|
||||
this.currentPage = this.initialPage;
|
||||
this.resolveInitialViewportMode();
|
||||
|
||||
// Get file size
|
||||
try {
|
||||
const data = await this.pdfDocument.getData();
|
||||
this.pdfFileSize = data.length;
|
||||
} catch (e) {
|
||||
this.pdfFileSize = 0;
|
||||
}
|
||||
|
||||
// Initialize thumbnail and page data arrays
|
||||
this.thumbnailData = Array.from({length: this.totalPages}, (_, i) => ({
|
||||
page: i + 1,
|
||||
@@ -699,8 +736,8 @@ export class DeesPdfViewer extends DeesElement {
|
||||
const viewerRect = this.viewerMain.getBoundingClientRect();
|
||||
const currentScrollTop = this.viewerMain.scrollTop;
|
||||
|
||||
// Calculate the target scroll position
|
||||
const targetScrollTop = currentScrollTop + (pageRect.top - viewerRect.top) - this.viewerMain.clientTop;
|
||||
// Calculate the target scroll position (offset by 16px so page doesn't touch the top edge)
|
||||
const targetScrollTop = currentScrollTop + (pageRect.top - viewerRect.top) - this.viewerMain.clientTop - 16;
|
||||
|
||||
// Scroll to the calculated position
|
||||
if (smooth) {
|
||||
|
||||
@@ -3,8 +3,8 @@ import { html } from '@design.estate/dees-element';
|
||||
export const demo = () => html`
|
||||
<style>
|
||||
.demo-container {
|
||||
padding: 40px;
|
||||
background: #f5f5f5;
|
||||
padding: 20px;
|
||||
background: #000000;
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
@@ -15,6 +15,7 @@ export const demo = () => html`
|
||||
margin-bottom: 20px;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #fafafa;
|
||||
}
|
||||
|
||||
dees-pdf-viewer {
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { css, cssManager } from '@design.estate/dees-element';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
export const viewerStyles = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
@@ -12,25 +14,47 @@ export const viewerStyles = [
|
||||
contain: layout style;
|
||||
}
|
||||
|
||||
.pdf-viewer {
|
||||
width: 100%;
|
||||
dees-tile {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(215 20% 10%)')};
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.viewer-container::before,
|
||||
.viewer-container::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 8px;
|
||||
z-index: 5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.viewer-container::before {
|
||||
top: 0;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
${cssManager.bdTheme('hsl(0 0% 0% / 0.08)', 'hsl(0 0% 0% / 0.4)')},
|
||||
${cssManager.bdTheme('hsl(0 0% 0% / 0.03)', 'hsl(0 0% 0% / 0.12)')},
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.viewer-container::after {
|
||||
bottom: 0;
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
${cssManager.bdTheme('hsl(0 0% 0% / 0.08)', 'hsl(0 0% 0% / 0.4)')},
|
||||
${cssManager.bdTheme('hsl(0 0% 0% / 0.03)', 'hsl(0 0% 0% / 0.12)')},
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
height: 48px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 20% 15%)')};
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(214 31% 91%)', 'hsl(217 25% 22%)')};
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 16px;
|
||||
gap: 16px;
|
||||
flex-shrink: 0;
|
||||
padding: 0 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.toolbar-group {
|
||||
@@ -108,21 +132,27 @@ export const viewerStyles = [
|
||||
}
|
||||
|
||||
.viewer-container {
|
||||
flex: 1;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 200px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(215 20% 15%)')};
|
||||
background: transparent;
|
||||
border-right: 1px solid ${cssManager.bdTheme('hsl(214 31% 91%)', 'hsl(217 25% 22%)')};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
order: 0;
|
||||
}
|
||||
|
||||
.sidebar-right .sidebar {
|
||||
border-right: none;
|
||||
border-left: 1px solid ${cssManager.bdTheme('hsl(214 31% 91%)', 'hsl(217 25% 22%)')};
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
@@ -334,5 +364,52 @@ export const viewerStyles = [
|
||||
.pdf-viewer.with-sidebar .viewer-main {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.pdf-footer {
|
||||
height: 28px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 11px;
|
||||
color: var(--dees-color-text-muted);
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.pdf-footer-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.pdf-footer-left .pdf-footer-item + .pdf-footer-item {
|
||||
padding-left: 12px;
|
||||
border-left: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
.pdf-footer-center {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
font-weight: 500;
|
||||
transition: margin-left 0.15s ease;
|
||||
}
|
||||
|
||||
.pdf-footer-right {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.pdf-footer-item {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pdf-footer-filename {
|
||||
font-family: 'Intel One Mono', 'Geist Mono', monospace;
|
||||
opacity: 0.7;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
`,
|
||||
];
|
||||
@@ -8,22 +8,22 @@ import {
|
||||
type TemplateResult,
|
||||
type CSSResult,
|
||||
} from '@design.estate/dees-element';
|
||||
import { DeesTileBase } from '../dees-tile-shared/DeesTileBase.js';
|
||||
import { tileBaseStyles } from '../dees-tile-shared/styles.js';
|
||||
import { DeesThumbnailBase } from '../dees-thumbnail-shared/DeesThumbnailBase.js';
|
||||
import { thumbnailBaseStyles } from '../dees-thumbnail-shared/styles.js';
|
||||
import { demo } from './demo.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-tile-audio': DeesTileAudio;
|
||||
'dees-thumbnail-audio': DeesThumbnailAudio;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-tile-audio')
|
||||
export class DeesTileAudio extends DeesTileBase {
|
||||
@customElement('dees-thumbnail-audio')
|
||||
export class DeesThumbnailAudio extends DeesThumbnailBase {
|
||||
public static demo = demo;
|
||||
public static demoGroups = ['Media'];
|
||||
public static styles = [
|
||||
...tileBaseStyles,
|
||||
...thumbnailBaseStyles,
|
||||
css`
|
||||
.audio-content {
|
||||
position: relative;
|
||||
@@ -26,51 +26,51 @@ export const demo = () => html`
|
||||
<div class="demo-section">
|
||||
<h3>Audio Tiles</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-audio
|
||||
<dees-thumbnail-audio
|
||||
src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
|
||||
title="SoundHelix Song 1"
|
||||
artist="T. Schuerger"
|
||||
label="soundhelix-1.mp3"
|
||||
@tile-click=${(e: CustomEvent) => console.log('Audio clicked:', e.detail)}
|
||||
></dees-tile-audio>
|
||||
></dees-thumbnail-audio>
|
||||
|
||||
<dees-tile-audio
|
||||
<dees-thumbnail-audio
|
||||
src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3"
|
||||
title="SoundHelix Song 2"
|
||||
artist="T. Schuerger"
|
||||
label="soundhelix-2.mp3"
|
||||
></dees-tile-audio>
|
||||
></dees-thumbnail-audio>
|
||||
|
||||
<dees-tile-audio
|
||||
<dees-thumbnail-audio
|
||||
src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3"
|
||||
title="SoundHelix Song 3"
|
||||
label="soundhelix-3.mp3"
|
||||
></dees-tile-audio>
|
||||
></dees-thumbnail-audio>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Size Variants</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-audio
|
||||
<dees-thumbnail-audio
|
||||
size="small"
|
||||
src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
|
||||
title="Small"
|
||||
label="small.mp3"
|
||||
></dees-tile-audio>
|
||||
></dees-thumbnail-audio>
|
||||
|
||||
<dees-tile-audio
|
||||
<dees-thumbnail-audio
|
||||
src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
|
||||
title="Default"
|
||||
label="default.mp3"
|
||||
></dees-tile-audio>
|
||||
></dees-thumbnail-audio>
|
||||
|
||||
<dees-tile-audio
|
||||
<dees-thumbnail-audio
|
||||
size="large"
|
||||
src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"
|
||||
title="Large"
|
||||
label="large.mp3"
|
||||
></dees-tile-audio>
|
||||
></dees-thumbnail-audio>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
type TemplateResult,
|
||||
type CSSResult,
|
||||
} from '@design.estate/dees-element';
|
||||
import { DeesTileBase } from '../dees-tile-shared/DeesTileBase.js';
|
||||
import { tileBaseStyles } from '../dees-tile-shared/styles.js';
|
||||
import { DeesThumbnailBase } from '../dees-thumbnail-shared/DeesThumbnailBase.js';
|
||||
import { thumbnailBaseStyles } from '../dees-thumbnail-shared/styles.js';
|
||||
import { demo } from './demo.js';
|
||||
|
||||
export interface ITileFolderItem {
|
||||
export interface IThumbnailFolderItem {
|
||||
type: 'pdf' | 'image' | 'audio' | 'video' | 'note' | 'folder' | 'unknown';
|
||||
thumbnailSrc?: string;
|
||||
name: string;
|
||||
@@ -29,16 +29,16 @@ const TYPE_ICON_MAP: Record<string, string> = {
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-tile-folder': DeesTileFolder;
|
||||
'dees-thumbnail-folder': DeesThumbnailFolder;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-tile-folder')
|
||||
export class DeesTileFolder extends DeesTileBase {
|
||||
@customElement('dees-thumbnail-folder')
|
||||
export class DeesThumbnailFolder extends DeesThumbnailBase {
|
||||
public static demo = demo;
|
||||
public static demoGroups = ['Media'];
|
||||
public static styles = [
|
||||
...tileBaseStyles,
|
||||
...thumbnailBaseStyles,
|
||||
css`
|
||||
.folder-content {
|
||||
position: relative;
|
||||
@@ -116,7 +116,7 @@ export class DeesTileFolder extends DeesTileBase {
|
||||
accessor name: string = '';
|
||||
|
||||
@property({ attribute: false })
|
||||
accessor items: ITileFolderItem[] = [];
|
||||
accessor items: IThumbnailFolderItem[] = [];
|
||||
|
||||
protected renderTileContent(): TemplateResult {
|
||||
const previewItems = this.items.slice(0, 4);
|
||||
@@ -1,8 +1,8 @@
|
||||
import { html } from '@design.estate/dees-element';
|
||||
import type { ITileFolderItem } from './component.js';
|
||||
import type { IThumbnailFolderItem } from './component.js';
|
||||
|
||||
export const demo = () => {
|
||||
const photosFolder: ITileFolderItem[] = [
|
||||
const photosFolder: IThumbnailFolderItem[] = [
|
||||
{ type: 'image', name: 'sunset.jpg', thumbnailSrc: 'https://picsum.photos/200/200?random=1' },
|
||||
{ type: 'image', name: 'mountain.jpg', thumbnailSrc: 'https://picsum.photos/200/200?random=2' },
|
||||
{ type: 'image', name: 'ocean.jpg', thumbnailSrc: 'https://picsum.photos/200/200?random=3' },
|
||||
@@ -11,7 +11,7 @@ export const demo = () => {
|
||||
{ type: 'image', name: 'desert.jpg', thumbnailSrc: 'https://picsum.photos/200/200?random=6' },
|
||||
];
|
||||
|
||||
const projectFolder: ITileFolderItem[] = [
|
||||
const projectFolder: IThumbnailFolderItem[] = [
|
||||
{ type: 'note', name: 'README.md' },
|
||||
{ type: 'note', name: 'package.json' },
|
||||
{ type: 'folder', name: 'src' },
|
||||
@@ -21,16 +21,16 @@ export const demo = () => {
|
||||
{ type: 'image', name: 'logo.png', thumbnailSrc: 'https://picsum.photos/100/100?random=10' },
|
||||
];
|
||||
|
||||
const mediaFolder: ITileFolderItem[] = [
|
||||
const mediaFolder: IThumbnailFolderItem[] = [
|
||||
{ type: 'video', name: 'intro.mp4' },
|
||||
{ type: 'audio', name: 'background.mp3' },
|
||||
{ type: 'image', name: 'thumbnail.jpg', thumbnailSrc: 'https://picsum.photos/200/200?random=20' },
|
||||
{ type: 'pdf', name: 'storyboard.pdf' },
|
||||
];
|
||||
|
||||
const emptyFolder: ITileFolderItem[] = [];
|
||||
const emptyFolder: IThumbnailFolderItem[] = [];
|
||||
|
||||
const singleItemFolder: ITileFolderItem[] = [
|
||||
const singleItemFolder: IThumbnailFolderItem[] = [
|
||||
{ type: 'pdf', name: 'report.pdf' },
|
||||
];
|
||||
|
||||
@@ -60,61 +60,61 @@ export const demo = () => {
|
||||
<div class="demo-section">
|
||||
<h3>Folder Tiles</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-folder
|
||||
<dees-thumbnail-folder
|
||||
name="Photos"
|
||||
.items=${photosFolder}
|
||||
label="6 photos"
|
||||
@tile-click=${(e: CustomEvent) => console.log('Folder clicked:', e.detail)}
|
||||
></dees-tile-folder>
|
||||
></dees-thumbnail-folder>
|
||||
|
||||
<dees-tile-folder
|
||||
<dees-thumbnail-folder
|
||||
name="my-project"
|
||||
.items=${projectFolder}
|
||||
label="Project files"
|
||||
></dees-tile-folder>
|
||||
></dees-thumbnail-folder>
|
||||
|
||||
<dees-tile-folder
|
||||
<dees-thumbnail-folder
|
||||
name="Media Assets"
|
||||
.items=${mediaFolder}
|
||||
label="Mixed media"
|
||||
></dees-tile-folder>
|
||||
></dees-thumbnail-folder>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Edge Cases</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-folder
|
||||
<dees-thumbnail-folder
|
||||
name="Empty Folder"
|
||||
.items=${emptyFolder}
|
||||
></dees-tile-folder>
|
||||
></dees-thumbnail-folder>
|
||||
|
||||
<dees-tile-folder
|
||||
<dees-thumbnail-folder
|
||||
name="Single Item"
|
||||
.items=${singleItemFolder}
|
||||
></dees-tile-folder>
|
||||
></dees-thumbnail-folder>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Size Variants</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-folder
|
||||
<dees-thumbnail-folder
|
||||
size="small"
|
||||
name="Small"
|
||||
.items=${photosFolder}
|
||||
></dees-tile-folder>
|
||||
></dees-thumbnail-folder>
|
||||
|
||||
<dees-tile-folder
|
||||
<dees-thumbnail-folder
|
||||
name="Default"
|
||||
.items=${photosFolder}
|
||||
></dees-tile-folder>
|
||||
></dees-thumbnail-folder>
|
||||
|
||||
<dees-tile-folder
|
||||
<dees-thumbnail-folder
|
||||
size="large"
|
||||
name="Large"
|
||||
.items=${photosFolder}
|
||||
></dees-tile-folder>
|
||||
></dees-thumbnail-folder>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -8,22 +8,22 @@ import {
|
||||
type TemplateResult,
|
||||
type CSSResult,
|
||||
} from '@design.estate/dees-element';
|
||||
import { DeesTileBase } from '../dees-tile-shared/DeesTileBase.js';
|
||||
import { tileBaseStyles } from '../dees-tile-shared/styles.js';
|
||||
import { DeesThumbnailBase } from '../dees-thumbnail-shared/DeesThumbnailBase.js';
|
||||
import { thumbnailBaseStyles } from '../dees-thumbnail-shared/styles.js';
|
||||
import { demo } from './demo.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-tile-image': DeesTileImage;
|
||||
'dees-thumbnail-image': DeesThumbnailImage;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-tile-image')
|
||||
export class DeesTileImage extends DeesTileBase {
|
||||
@customElement('dees-thumbnail-image')
|
||||
export class DeesThumbnailImage extends DeesThumbnailBase {
|
||||
public static demo = demo;
|
||||
public static demoGroups = ['Media'];
|
||||
public static styles = [
|
||||
...tileBaseStyles,
|
||||
...thumbnailBaseStyles,
|
||||
css`
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
@@ -26,59 +26,59 @@ export const demo = () => html`
|
||||
<div class="demo-section">
|
||||
<h3>Image Tiles</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-image
|
||||
<dees-thumbnail-image
|
||||
src="https://picsum.photos/800/600"
|
||||
alt="Landscape photo"
|
||||
label="landscape.jpg"
|
||||
@tile-click=${(e: CustomEvent) => console.log('Image clicked:', e.detail)}
|
||||
></dees-tile-image>
|
||||
></dees-thumbnail-image>
|
||||
|
||||
<dees-tile-image
|
||||
<dees-thumbnail-image
|
||||
src="https://picsum.photos/400/400"
|
||||
alt="Square photo"
|
||||
label="square.png"
|
||||
></dees-tile-image>
|
||||
></dees-thumbnail-image>
|
||||
|
||||
<dees-tile-image
|
||||
<dees-thumbnail-image
|
||||
src="https://picsum.photos/300/900"
|
||||
alt="Portrait photo"
|
||||
label="portrait.webp"
|
||||
></dees-tile-image>
|
||||
></dees-thumbnail-image>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Size Variants</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-image
|
||||
<dees-thumbnail-image
|
||||
size="small"
|
||||
src="https://picsum.photos/200/200"
|
||||
alt="Small"
|
||||
label="small.jpg"
|
||||
></dees-tile-image>
|
||||
></dees-thumbnail-image>
|
||||
|
||||
<dees-tile-image
|
||||
<dees-thumbnail-image
|
||||
src="https://picsum.photos/600/400"
|
||||
alt="Default"
|
||||
label="default.jpg"
|
||||
></dees-tile-image>
|
||||
></dees-thumbnail-image>
|
||||
|
||||
<dees-tile-image
|
||||
<dees-thumbnail-image
|
||||
size="large"
|
||||
src="https://picsum.photos/1200/800"
|
||||
alt="Large"
|
||||
label="large.jpg"
|
||||
></dees-tile-image>
|
||||
></dees-thumbnail-image>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Error State (broken URL)</h3>
|
||||
<dees-tile-image
|
||||
<dees-thumbnail-image
|
||||
src="https://invalid-url-that-does-not-exist.example/image.png"
|
||||
alt="Broken"
|
||||
label="broken.png"
|
||||
></dees-tile-image>
|
||||
></dees-thumbnail-image>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './component.js';
|
||||
@@ -8,22 +8,22 @@ import {
|
||||
type TemplateResult,
|
||||
type CSSResult,
|
||||
} from '@design.estate/dees-element';
|
||||
import { DeesTileBase } from '../dees-tile-shared/DeesTileBase.js';
|
||||
import { tileBaseStyles } from '../dees-tile-shared/styles.js';
|
||||
import { DeesThumbnailBase } from '../dees-thumbnail-shared/DeesThumbnailBase.js';
|
||||
import { thumbnailBaseStyles } from '../dees-thumbnail-shared/styles.js';
|
||||
import { demo } from './demo.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-tile-note': DeesTileNote;
|
||||
'dees-thumbnail-note': DeesThumbnailNote;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-tile-note')
|
||||
export class DeesTileNote extends DeesTileBase {
|
||||
@customElement('dees-thumbnail-note')
|
||||
export class DeesThumbnailNote extends DeesThumbnailBase {
|
||||
public static demo = demo;
|
||||
public static demoGroups = ['Media'];
|
||||
public static styles = [
|
||||
...tileBaseStyles,
|
||||
...thumbnailBaseStyles,
|
||||
css`
|
||||
.note-content {
|
||||
position: relative;
|
||||
@@ -72,63 +72,63 @@ Action Items:
|
||||
<div class="demo-section">
|
||||
<h3>Note Tiles</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-note
|
||||
<dees-thumbnail-note
|
||||
title="component.ts"
|
||||
.content=${sampleCode}
|
||||
language="typescript"
|
||||
label="component.ts"
|
||||
@tile-click=${(e: CustomEvent) => console.log('Note clicked:', e.detail)}
|
||||
></dees-tile-note>
|
||||
></dees-thumbnail-note>
|
||||
|
||||
<dees-tile-note
|
||||
<dees-thumbnail-note
|
||||
title="Meeting Notes"
|
||||
.content=${sampleText}
|
||||
label="meeting-notes.txt"
|
||||
></dees-tile-note>
|
||||
></dees-thumbnail-note>
|
||||
|
||||
<dees-tile-note
|
||||
<dees-thumbnail-note
|
||||
title="package.json"
|
||||
.content=${sampleJson}
|
||||
language="json"
|
||||
label="package.json"
|
||||
></dees-tile-note>
|
||||
></dees-thumbnail-note>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Size Variants</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-note
|
||||
<dees-thumbnail-note
|
||||
size="small"
|
||||
title="small.ts"
|
||||
.content=${sampleCode}
|
||||
language="ts"
|
||||
label="small.ts"
|
||||
></dees-tile-note>
|
||||
></dees-thumbnail-note>
|
||||
|
||||
<dees-tile-note
|
||||
<dees-thumbnail-note
|
||||
title="default.ts"
|
||||
.content=${sampleCode}
|
||||
language="ts"
|
||||
label="default.ts"
|
||||
></dees-tile-note>
|
||||
></dees-thumbnail-note>
|
||||
|
||||
<dees-tile-note
|
||||
<dees-thumbnail-note
|
||||
size="large"
|
||||
title="large.ts"
|
||||
.content=${sampleCode}
|
||||
language="ts"
|
||||
label="large.ts"
|
||||
></dees-tile-note>
|
||||
></dees-thumbnail-note>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Without Title</h3>
|
||||
<dees-tile-note
|
||||
<dees-thumbnail-note
|
||||
.content=${sampleText}
|
||||
label="untitled.txt"
|
||||
></dees-tile-note>
|
||||
></dees-thumbnail-note>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './component.js';
|
||||
@@ -1,6 +1,6 @@
|
||||
import { property, state, html, customElement, type TemplateResult, type CSSResult } from '@design.estate/dees-element';
|
||||
import { DeesTileBase } from '../dees-tile-shared/DeesTileBase.js';
|
||||
import { tileBaseStyles } from '../dees-tile-shared/styles.js';
|
||||
import { DeesThumbnailBase } from '../dees-thumbnail-shared/DeesThumbnailBase.js';
|
||||
import { thumbnailBaseStyles } from '../dees-thumbnail-shared/styles.js';
|
||||
import { PdfManager } from '../dees-pdf-shared/PdfManager.js';
|
||||
import { CanvasPool, type PooledCanvas } from '../dees-pdf-shared/CanvasPool.js';
|
||||
import { PerformanceMonitor, throttle, formatFileSize } from '../dees-pdf-shared/utils.js';
|
||||
@@ -9,15 +9,15 @@ import { demo as demoFunc } from './demo.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-tile-pdf': DeesTilePdf;
|
||||
'dees-thumbnail-pdf': DeesThumbnailPdf;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-tile-pdf')
|
||||
export class DeesTilePdf extends DeesTileBase {
|
||||
@customElement('dees-thumbnail-pdf')
|
||||
export class DeesThumbnailPdf extends DeesThumbnailBase {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroups = ['Media', 'PDF'];
|
||||
public static styles = [...tileBaseStyles, tilePdfStyles] as any;
|
||||
public static styles = [...thumbnailBaseStyles, tilePdfStyles] as any;
|
||||
|
||||
@property({ type: String })
|
||||
accessor pdfUrl: string = '';
|
||||
@@ -11,7 +11,7 @@ export const demo = () => {
|
||||
for (let i = 0; i < count; i++) {
|
||||
const pdfUrl = samplePdfs[i % samplePdfs.length];
|
||||
items.push(html`
|
||||
<dees-tile-pdf
|
||||
<dees-thumbnail-pdf
|
||||
pdfUrl="${pdfUrl}"
|
||||
clickable="true"
|
||||
grid-mode
|
||||
@@ -19,7 +19,7 @@ export const demo = () => {
|
||||
console.log('PDF Tile clicked:', e.detail);
|
||||
alert(`PDF clicked: ${e.detail.pageCount} pages`);
|
||||
}}
|
||||
></dees-tile-pdf>
|
||||
></dees-thumbnail-pdf>
|
||||
`);
|
||||
}
|
||||
return items;
|
||||
@@ -67,56 +67,56 @@ export const demo = () => {
|
||||
<div class="demo-container">
|
||||
<div class="demo-section">
|
||||
<h3>Single PDF Tile</h3>
|
||||
<dees-tile-pdf
|
||||
<dees-thumbnail-pdf
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
|
||||
clickable="true"
|
||||
></dees-tile-pdf>
|
||||
></dees-thumbnail-pdf>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Different Sizes</h3>
|
||||
<div class="preview-row">
|
||||
<div class="preview-label">Small:</div>
|
||||
<dees-tile-pdf
|
||||
<dees-thumbnail-pdf
|
||||
size="small"
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf"
|
||||
clickable="true"
|
||||
></dees-tile-pdf>
|
||||
></dees-thumbnail-pdf>
|
||||
</div>
|
||||
|
||||
<div class="preview-row">
|
||||
<div class="preview-label">Default:</div>
|
||||
<dees-tile-pdf
|
||||
<dees-thumbnail-pdf
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf"
|
||||
clickable="true"
|
||||
></dees-tile-pdf>
|
||||
></dees-thumbnail-pdf>
|
||||
</div>
|
||||
|
||||
<div class="preview-row">
|
||||
<div class="preview-label">Large:</div>
|
||||
<dees-tile-pdf
|
||||
<dees-thumbnail-pdf
|
||||
size="large"
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf"
|
||||
clickable="true"
|
||||
></dees-tile-pdf>
|
||||
></dees-thumbnail-pdf>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>With Label</h3>
|
||||
<dees-tile-pdf
|
||||
<dees-thumbnail-pdf
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
|
||||
clickable="true"
|
||||
label="Research Paper.pdf"
|
||||
></dees-tile-pdf>
|
||||
></dees-thumbnail-pdf>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Non-Clickable</h3>
|
||||
<dees-tile-pdf
|
||||
<dees-thumbnail-pdf
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/examples/learning/helloworld.pdf"
|
||||
clickable="false"
|
||||
></dees-tile-pdf>
|
||||
></dees-thumbnail-pdf>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
@@ -0,0 +1 @@
|
||||
export * from './component.js';
|
||||
@@ -5,11 +5,11 @@ import {
|
||||
type TemplateResult,
|
||||
type CSSResult,
|
||||
} from '@design.estate/dees-element';
|
||||
import { tileBaseStyles } from './styles.js';
|
||||
import { thumbnailBaseStyles } from './styles.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
|
||||
export abstract class DeesTileBase extends DeesElement {
|
||||
public static styles: CSSResult[] = tileBaseStyles as any;
|
||||
export abstract class DeesThumbnailBase extends DeesElement {
|
||||
public static styles: CSSResult[] = thumbnailBaseStyles as any;
|
||||
|
||||
@property({ type: Boolean })
|
||||
accessor clickable: boolean = true;
|
||||
@@ -0,0 +1,2 @@
|
||||
export { DeesThumbnailBase } from './DeesThumbnailBase.js';
|
||||
export { thumbnailBaseStyles } from './styles.js';
|
||||
@@ -1,6 +1,6 @@
|
||||
import { css, cssManager } from '@design.estate/dees-element';
|
||||
|
||||
export const tileBaseStyles = [
|
||||
export const thumbnailBaseStyles = [
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
:host {
|
||||
@@ -8,22 +8,22 @@ import {
|
||||
type TemplateResult,
|
||||
type CSSResult,
|
||||
} from '@design.estate/dees-element';
|
||||
import { DeesTileBase } from '../dees-tile-shared/DeesTileBase.js';
|
||||
import { tileBaseStyles } from '../dees-tile-shared/styles.js';
|
||||
import { DeesThumbnailBase } from '../dees-thumbnail-shared/DeesThumbnailBase.js';
|
||||
import { thumbnailBaseStyles } from '../dees-thumbnail-shared/styles.js';
|
||||
import { demo } from './demo.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'dees-tile-video': DeesTileVideo;
|
||||
'dees-thumbnail-video': DeesThumbnailVideo;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('dees-tile-video')
|
||||
export class DeesTileVideo extends DeesTileBase {
|
||||
@customElement('dees-thumbnail-video')
|
||||
export class DeesThumbnailVideo extends DeesThumbnailBase {
|
||||
public static demo = demo;
|
||||
public static demoGroups = ['Media'];
|
||||
public static styles = [
|
||||
...tileBaseStyles,
|
||||
...thumbnailBaseStyles,
|
||||
css`
|
||||
.video-wrapper {
|
||||
position: relative;
|
||||
@@ -26,54 +26,54 @@ export const demo = () => html`
|
||||
<div class="demo-section">
|
||||
<h3>Video Tiles</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-video
|
||||
<dees-thumbnail-video
|
||||
src="https://www.w3schools.com/html/mov_bbb.mp4"
|
||||
label="bunny.mp4"
|
||||
@tile-click=${(e: CustomEvent) => console.log('Video clicked:', e.detail)}
|
||||
></dees-tile-video>
|
||||
></dees-thumbnail-video>
|
||||
|
||||
<dees-tile-video
|
||||
<dees-thumbnail-video
|
||||
src="https://www.w3schools.com/html/movie.mp4"
|
||||
poster="https://picsum.photos/400/300"
|
||||
label="movie.mp4"
|
||||
></dees-tile-video>
|
||||
></dees-thumbnail-video>
|
||||
|
||||
<dees-tile-video
|
||||
<dees-thumbnail-video
|
||||
src="https://www.w3schools.com/html/mov_bbb.mp4"
|
||||
label="another-video.mp4"
|
||||
></dees-tile-video>
|
||||
></dees-thumbnail-video>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>Size Variants</h3>
|
||||
<div class="tile-row">
|
||||
<dees-tile-video
|
||||
<dees-thumbnail-video
|
||||
size="small"
|
||||
src="https://www.w3schools.com/html/mov_bbb.mp4"
|
||||
label="small.mp4"
|
||||
></dees-tile-video>
|
||||
></dees-thumbnail-video>
|
||||
|
||||
<dees-tile-video
|
||||
<dees-thumbnail-video
|
||||
src="https://www.w3schools.com/html/mov_bbb.mp4"
|
||||
label="default.mp4"
|
||||
></dees-tile-video>
|
||||
></dees-thumbnail-video>
|
||||
|
||||
<dees-tile-video
|
||||
<dees-thumbnail-video
|
||||
size="large"
|
||||
src="https://www.w3schools.com/html/mov_bbb.mp4"
|
||||
label="large.mp4"
|
||||
></dees-tile-video>
|
||||
></dees-thumbnail-video>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>With Poster Image</h3>
|
||||
<dees-tile-video
|
||||
<dees-thumbnail-video
|
||||
src="https://www.w3schools.com/html/movie.mp4"
|
||||
poster="https://picsum.photos/600/400"
|
||||
label="poster-video.mp4"
|
||||
></dees-tile-video>
|
||||
></dees-thumbnail-video>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './component.js';
|
||||
@@ -1,2 +0,0 @@
|
||||
export { DeesTileBase } from './DeesTileBase.js';
|
||||
export { tileBaseStyles } from './styles.js';
|
||||
@@ -8,11 +8,11 @@ export * from './dees-preview/index.js';
|
||||
export * from './dees-pdf-shared/index.js';
|
||||
export * from './dees-pdf-viewer/index.js';
|
||||
|
||||
// Tile Components
|
||||
export * from './dees-tile-shared/index.js';
|
||||
export * from './dees-tile-pdf/index.js';
|
||||
export * from './dees-tile-image/index.js';
|
||||
export * from './dees-tile-audio/index.js';
|
||||
export * from './dees-tile-video/index.js';
|
||||
export * from './dees-tile-note/index.js';
|
||||
export * from './dees-tile-folder/index.js';
|
||||
// Thumbnail Components
|
||||
export * from './dees-thumbnail-shared/index.js';
|
||||
export * from './dees-thumbnail-pdf/index.js';
|
||||
export * from './dees-thumbnail-image/index.js';
|
||||
export * from './dees-thumbnail-audio/index.js';
|
||||
export * from './dees-thumbnail-video/index.js';
|
||||
export * from './dees-thumbnail-note/index.js';
|
||||
export * from './dees-thumbnail-folder/index.js';
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { DeesWindowLayer } from '../dees-windowlayer/dees-windowlayer.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import '../../00group-layout/dees-tile/dees-tile.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
declare global {
|
||||
@@ -128,8 +129,7 @@ export class DeesModal extends DeesElement {
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
:host {
|
||||
font-family: ${cssGeistFontFamily};
|
||||
color: ${cssManager.bdTheme('#333', '#fff')};
|
||||
will-change: transform;
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
.modalContainer {
|
||||
display: flex;
|
||||
@@ -142,106 +142,112 @@ export class DeesModal extends DeesElement {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.modal {
|
||||
will-change: transform;
|
||||
transform: translateY(0px) scale(0.95);
|
||||
dees-tile {
|
||||
will-change: transform, opacity;
|
||||
transform: translateY(8px) scale(0.98);
|
||||
opacity: 0;
|
||||
min-height: 120px;
|
||||
max-height: calc(100vh - 40px);
|
||||
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
|
||||
border-radius: 6px;
|
||||
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
transition: all 0.2s ease;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
margin: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: calc(100vh - 80px);
|
||||
transition: transform 0.25s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.2s ease;
|
||||
margin: 40px;
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
dees-tile::part(outer) {
|
||||
box-shadow:
|
||||
0 0 0 1px ${cssManager.bdTheme('hsl(0 0% 0% / 0.03)', 'hsl(0 0% 100% / 0.03)')},
|
||||
0 8px 40px ${cssManager.bdTheme('hsl(0 0% 0% / 0.12)', 'hsl(0 0% 0% / 0.5)')},
|
||||
0 2px 8px ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 0% / 0.25)')};
|
||||
}
|
||||
|
||||
/* Width variations */
|
||||
.modal.width-small {
|
||||
dees-tile.width-small {
|
||||
width: 380px;
|
||||
}
|
||||
|
||||
.modal.width-medium {
|
||||
dees-tile.width-medium {
|
||||
width: 560px;
|
||||
}
|
||||
|
||||
.modal.width-large {
|
||||
dees-tile.width-large {
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
.modal.width-fullscreen {
|
||||
dees-tile.width-fullscreen {
|
||||
width: calc(100vw - 40px);
|
||||
height: calc(100vh - 40px);
|
||||
max-height: calc(100vh - 40px);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.modal {
|
||||
dees-tile {
|
||||
width: calc(100vw - 40px) !important;
|
||||
max-width: none !important;
|
||||
}
|
||||
|
||||
/* Allow full height on mobile when content needs it */
|
||||
|
||||
.modalContainer {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.modal {
|
||||
|
||||
dees-tile {
|
||||
margin: 10px;
|
||||
max-height: calc(100vh - 20px);
|
||||
}
|
||||
|
||||
/* Full screen mode on mobile */
|
||||
.modal.mobile-fullscreen {
|
||||
|
||||
dees-tile.mobile-fullscreen {
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
max-height: 100vh !important;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
dees-tile.mobile-fullscreen::part(outer) {
|
||||
border-radius: 0;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modal.show {
|
||||
dees-tile.show {
|
||||
opacity: 1;
|
||||
transform: translateY(0px) scale(1);
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
.modal.show.predestroy {
|
||||
dees-tile.show.predestroy {
|
||||
opacity: 0;
|
||||
transform: translateY(10px) scale(1);
|
||||
transform: translateY(6px) scale(0.98);
|
||||
transition: transform 0.15s ease-in, opacity 0.15s ease-in;
|
||||
}
|
||||
|
||||
.modal .heading {
|
||||
height: 40px;
|
||||
min-height: 40px;
|
||||
font-family: ${cssGeistFontFamily};
|
||||
.heading {
|
||||
height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
padding: 0 8px 0 16px;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal .heading .header-buttons {
|
||||
.heading .heading-text {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
font-size: 13px;
|
||||
letter-spacing: -0.01em;
|
||||
color: var(--dees-color-text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.heading .header-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.modal .heading .header-button {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
.heading .header-button {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -249,89 +255,85 @@ export class DeesModal extends DeesElement {
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
background: transparent;
|
||||
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.modal .heading .header-button:hover {
|
||||
background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
.heading .header-button:hover {
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
|
||||
.modal .heading .header-button:active {
|
||||
background: ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
|
||||
.heading .header-button:active {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 15%)')};
|
||||
}
|
||||
|
||||
.modal .heading .header-button dees-icon {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
.heading .header-button dees-icon {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.modal .heading .heading-text {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 40px;
|
||||
padding: 0 40px;
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
}
|
||||
|
||||
.modal .content {
|
||||
flex: 1;
|
||||
.content {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
|
||||
}
|
||||
.modal .bottomButtons {
|
||||
.bottomButtons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
height: 36px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.modal .bottomButtons .bottomButton {
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
line-height: 16px;
|
||||
.bottomButtons .bottomButton {
|
||||
padding: 0 16px;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
transition: all 0.15s ease;
|
||||
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
|
||||
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#3f3f46')};
|
||||
color: ${cssManager.bdTheme('#09090b', '#fafafa')};
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-left: 1px solid var(--dees-color-border-subtle);
|
||||
color: var(--dees-color-text-muted);
|
||||
white-space: nowrap;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal .bottomButtons .bottomButton:hover {
|
||||
background: ${cssManager.bdTheme('#f4f4f5', '#3f3f46')};
|
||||
border-color: ${cssManager.bdTheme('#d1d5db', '#52525b')};
|
||||
}
|
||||
.modal .bottomButtons .bottomButton:active {
|
||||
background: ${cssManager.bdTheme('#e5e7eb', '#52525b')};
|
||||
}
|
||||
.modal .bottomButtons .bottomButton:last-child {
|
||||
border-right: none;
|
||||
.bottomButtons .bottomButton:first-child {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.modal .bottomButtons .bottomButton.primary {
|
||||
background: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
|
||||
border-color: ${cssManager.bdTheme('#3b82f6', '#3b82f6')};
|
||||
color: #ffffff;
|
||||
.bottomButtons .bottomButton:hover {
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
.modal .bottomButtons .bottomButton.primary:hover {
|
||||
background: ${cssManager.bdTheme('#2563eb', '#2563eb')};
|
||||
border-color: ${cssManager.bdTheme('#2563eb', '#2563eb')};
|
||||
|
||||
.bottomButtons .bottomButton:active {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 92%)', 'hsl(0 0% 13%)')};
|
||||
}
|
||||
.modal .bottomButtons .bottomButton.primary:active {
|
||||
background: ${cssManager.bdTheme('#1d4ed8', '#1d4ed8')};
|
||||
border-color: ${cssManager.bdTheme('#1d4ed8', '#1d4ed8')};
|
||||
|
||||
.bottomButtons .bottomButton.primary {
|
||||
color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bottomButtons .bottomButton.primary:hover {
|
||||
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.08)', 'hsl(213.1 93.9% 67.8% / 0.08)')};
|
||||
color: ${cssManager.bdTheme('hsl(217.2 91.2% 50%)', 'hsl(213.1 93.9% 75%)')};
|
||||
}
|
||||
|
||||
.bottomButtons .bottomButton.primary:active {
|
||||
background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.12)', 'hsl(213.1 93.9% 67.8% / 0.12)')};
|
||||
}
|
||||
`,
|
||||
];
|
||||
@@ -345,13 +347,13 @@ export class DeesModal extends DeesElement {
|
||||
|
||||
return html`
|
||||
<style>
|
||||
${customWidth ? `.modal { width: ${customWidth}; }` : ''}
|
||||
${maxWidthStyle ? `.modal { max-width: ${maxWidthStyle}; }` : ''}
|
||||
${minWidthStyle ? `.modal { min-width: ${minWidthStyle}; }` : ''}
|
||||
${customWidth ? `dees-tile { width: ${customWidth}; }` : ''}
|
||||
${maxWidthStyle ? `dees-tile { max-width: ${maxWidthStyle}; }` : ''}
|
||||
${minWidthStyle ? `dees-tile { min-width: ${minWidthStyle}; }` : ''}
|
||||
</style>
|
||||
<div class="modalContainer" @click=${this.handleOutsideClick} style="z-index: ${this.modalZIndex}">
|
||||
<div class="modal ${widthClass} ${mobileFullscreenClass}">
|
||||
<div class="heading">
|
||||
<dees-tile class="${widthClass} ${mobileFullscreenClass}">
|
||||
<div slot="header" class="heading">
|
||||
<div class="heading-text">${this.heading}</div>
|
||||
<div class="header-buttons">
|
||||
${this.showHelpButton ? html`
|
||||
@@ -368,7 +370,7 @@ export class DeesModal extends DeesElement {
|
||||
</div>
|
||||
<div class="content" style="padding: ${this.contentPadding}px;">${this.content}</div>
|
||||
${this.menuOptions.length > 0 ? html`
|
||||
<div class="bottomButtons">
|
||||
<div slot="footer" class="bottomButtons">
|
||||
${this.menuOptions.map(
|
||||
(actionArg, index) => html`
|
||||
<div class="bottomButton ${index === this.menuOptions.length - 1 ? 'primary' : ''} ${actionArg.name === 'OK' ? 'ok' : ''}" @click=${() => {
|
||||
@@ -378,7 +380,7 @@ export class DeesModal extends DeesElement {
|
||||
)}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
</dees-tile>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -388,8 +390,8 @@ export class DeesModal extends DeesElement {
|
||||
super.firstUpdated(_changedProperties);
|
||||
const domtools = await this.domtoolsPromise;
|
||||
await domtools.convenience.smartdelay.delayFor(30);
|
||||
const modal = this.shadowRoot!.querySelector('.modal');
|
||||
modal!.classList.add('show');
|
||||
const tile = this.shadowRoot!.querySelector('dees-tile');
|
||||
tile!.classList.add('show');
|
||||
}
|
||||
|
||||
public async handleOutsideClick(eventArg: MouseEvent) {
|
||||
@@ -402,8 +404,8 @@ export class DeesModal extends DeesElement {
|
||||
|
||||
public async destroy() {
|
||||
const domtools = await this.domtoolsPromise;
|
||||
const modal = this.shadowRoot!.querySelector('.modal');
|
||||
modal!.classList.add('predestroy');
|
||||
const tile = this.shadowRoot!.querySelector('dees-tile');
|
||||
tile!.classList.add('predestroy');
|
||||
await domtools.convenience.smartdelay.delayFor(200);
|
||||
document.body.removeChild(this);
|
||||
await this.windowLayer.destroy();
|
||||
|
||||
@@ -67,7 +67,7 @@ export class DeesShoppingProductcard extends DeesElement {
|
||||
}
|
||||
|
||||
dees-tile:hover::part(outer) {
|
||||
border-color: ${cssManager.bdTheme('hsl(0 0% 79.8%)', 'hsl(0 0% 20.9%)')};
|
||||
border-color: var(--dees-color-border-strong);
|
||||
box-shadow: 0 4px 6px -1px hsl(0 0% 0% / 0.1), 0 2px 4px -2px hsl(0 0% 0% / 0.1);
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ export class DeesShoppingProductcard extends DeesElement {
|
||||
.product-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -204,7 +204,7 @@ export class DeesShoppingProductcard extends DeesElement {
|
||||
.price-current {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 95%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.price-original {
|
||||
|
||||
@@ -110,6 +110,48 @@ class DemoViewDashboard extends DeesElement {
|
||||
console.log('Tile action:', e.detail);
|
||||
}}
|
||||
></dees-statsgrid>
|
||||
|
||||
<h2 style="margin-top: 40px;">Recent Activity</h2>
|
||||
<p>Below is a log of recent system events and user activity to demonstrate scrollable content.</p>
|
||||
|
||||
${[
|
||||
{ time: '2 min ago', event: 'User john@example.com logged in from 192.168.1.42', type: 'info' },
|
||||
{ time: '5 min ago', event: 'Deployment v3.52.1 completed successfully on production', type: 'success' },
|
||||
{ time: '12 min ago', event: 'Database backup finished — 2.4 GB compressed', type: 'info' },
|
||||
{ time: '18 min ago', event: 'SSL certificate renewed for api.example.com (expires 2027-04-03)', type: 'success' },
|
||||
{ time: '25 min ago', event: 'Memory usage spike on worker-03 (92%) — auto-scaled to 4 instances', type: 'warning' },
|
||||
{ time: '31 min ago', event: 'New user registration: sarah@company.io', type: 'info' },
|
||||
{ time: '45 min ago', event: 'Scheduled job "cleanup-temp-files" completed — removed 1,247 files', type: 'info' },
|
||||
{ time: '1 hour ago', event: 'API rate limit reached for client app-mobile-ios (429 responses)', type: 'warning' },
|
||||
{ time: '1.5 hours ago', event: 'CDN cache purged for /assets/* — 340 objects invalidated', type: 'info' },
|
||||
{ time: '2 hours ago', event: 'Failed login attempt for admin@example.com from 203.0.113.50 (blocked)', type: 'error' },
|
||||
{ time: '2.5 hours ago', event: 'Webhook delivery to https://hooks.slack.com succeeded (200 OK)', type: 'info' },
|
||||
{ time: '3 hours ago', event: 'Cron job "generate-reports" started — processing Q1 2026 data', type: 'info' },
|
||||
{ time: '3.5 hours ago', event: 'Load balancer health check: all 8 nodes healthy', type: 'success' },
|
||||
{ time: '4 hours ago', event: 'DNS propagation complete for new subdomain staging.example.com', type: 'success' },
|
||||
].map(item => html`
|
||||
<div style="
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 12px;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 90%)', 'hsl(0 0% 12%)')};
|
||||
font-size: 13px;
|
||||
">
|
||||
<span style="
|
||||
flex-shrink: 0;
|
||||
width: 100px;
|
||||
font-size: 11px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 55%)', 'hsl(0 0% 45%)')};
|
||||
">${item.time}</span>
|
||||
<span style="
|
||||
color: ${item.type === 'error' ? cssManager.bdTheme('hsl(0 72% 50%)', 'hsl(0 72% 65%)') :
|
||||
item.type === 'warning' ? cssManager.bdTheme('hsl(25 95% 50%)', 'hsl(25 95% 63%)') :
|
||||
item.type === 'success' ? cssManager.bdTheme('hsl(142 70% 40%)', 'hsl(142 70% 55%)') :
|
||||
cssManager.bdTheme('hsl(0 0% 30%)', 'hsl(0 0% 75%)')};
|
||||
">${item.event}</span>
|
||||
</div>
|
||||
`)}
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,9 +76,8 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
:host {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
user-select: none;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
@@ -102,8 +101,8 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
left: 0px;
|
||||
height: calc(100% - 24px);
|
||||
width: 240px;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 99%)', 'hsl(0 0% 7%)')};
|
||||
border-right: 1px solid ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 13%)')};
|
||||
background: var(--dees-color-bg-secondary);
|
||||
border-right: 1px solid var(--dees-color-border-default);
|
||||
font-size: 13px;
|
||||
font-family: 'Geist Sans', sans-serif;
|
||||
z-index: 2;
|
||||
@@ -114,7 +113,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
|
||||
.sidebar-header {
|
||||
padding: 20px 16px;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 13%)')};
|
||||
border-bottom: 1px solid var(--dees-color-border-default);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
@@ -127,25 +126,22 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 10px;
|
||||
background: ${cssManager.bdTheme(
|
||||
'linear-gradient(135deg, hsl(215 20% 95%) 0%, hsl(215 20% 90%) 100%)',
|
||||
'linear-gradient(135deg, hsl(215 20% 18%) 0%, hsl(215 20% 14%) 100%)'
|
||||
)};
|
||||
background: var(--dees-color-bg-tertiary);
|
||||
box-shadow: ${cssManager.bdTheme(
|
||||
'0 1px 2px rgb(0 0 0 / 0.05), inset 0 1px 0 rgb(255 255 255 / 0.5)',
|
||||
'0 1px 2px rgb(0 0 0 / 0.2), inset 0 1px 0 rgb(255 255 255 / 0.05)'
|
||||
'0 1px 2px rgb(0 0 0 / 0.05)',
|
||||
'0 1px 2px rgb(0 0 0 / 0.2)'
|
||||
)};
|
||||
}
|
||||
|
||||
.header-icon-wrapper dees-icon {
|
||||
font-size: 18px;
|
||||
color: ${cssManager.bdTheme('hsl(215 20% 40%)', 'hsl(215 20% 70%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.appName {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -156,7 +152,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
overflow-y: auto;
|
||||
padding: 12px 8px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')} transparent;
|
||||
scrollbar-color: var(--dees-color-scrollbar-thumb) transparent;
|
||||
}
|
||||
|
||||
.viewTabs-container::-webkit-scrollbar {
|
||||
@@ -168,12 +164,12 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
}
|
||||
|
||||
.viewTabs-container::-webkit-scrollbar-thumb {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 85%)', 'hsl(0 0% 20%)')};
|
||||
background: var(--dees-color-scrollbar-thumb);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.viewTabs-container::-webkit-scrollbar-thumb:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 75%)', 'hsl(0 0% 30%)')};
|
||||
background: var(--dees-color-scrollbar-thumb-hover);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
@@ -181,7 +177,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 50%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
padding: 8px 12px 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
@@ -199,25 +195,25 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
padding: 10px 12px;
|
||||
cursor: default;
|
||||
transition: all 0.15s ease;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 65%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
user-select: none;
|
||||
position: relative;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.viewTab:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.04)', 'hsl(0 0% 100% / 0.05)')};
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 15%)', 'hsl(0 0% 90%)')};
|
||||
background: var(--dees-color-hover);
|
||||
color: ${cssManager.bdTheme('#262626', '#e5e5e5')};
|
||||
}
|
||||
|
||||
.viewTab:active {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 0% / 0.06)', 'hsl(0 0% 100% / 0.07)')};
|
||||
background: var(--dees-color-active);
|
||||
transform: scale(0.99);
|
||||
}
|
||||
|
||||
.viewTab.selected {
|
||||
background: ${cssManager.bdTheme('hsl(215 25% 95%)', 'hsl(215 20% 15%)')};
|
||||
color: ${cssManager.bdTheme('hsl(215 25% 30%)', 'hsl(215 25% 85%)')};
|
||||
background: var(--dees-color-active);
|
||||
color: var(--dees-color-text-primary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@@ -229,7 +225,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
bottom: 8px;
|
||||
width: 3px;
|
||||
border-radius: 0 2px 2px 0;
|
||||
background: ${cssManager.bdTheme('hsl(215 70% 50%)', 'hsl(215 70% 60%)')};
|
||||
background: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.viewTab dees-icon {
|
||||
@@ -243,8 +239,8 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
}
|
||||
|
||||
.viewTab.selected dees-icon {
|
||||
opacity: 0.9;
|
||||
color: ${cssManager.bdTheme('hsl(215 70% 45%)', 'hsl(215 70% 65%)')};
|
||||
opacity: 1;
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.viewTab span {
|
||||
@@ -256,7 +252,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
|
||||
.appActions {
|
||||
padding: 12px 8px;
|
||||
border-top: 1px solid ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 13%)')};
|
||||
border-top: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
.action {
|
||||
@@ -267,7 +263,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
border-radius: 8px;
|
||||
cursor: default;
|
||||
transition: all 0.15s ease;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 65%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
|
||||
.action:hover {
|
||||
@@ -295,19 +291,55 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
bottom: 24px;
|
||||
width: calc(100% - 240px);
|
||||
overflow: auto;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 97%)', 'hsl(0 0% 5%)')};
|
||||
background: var(--dees-color-bg-secondary);
|
||||
overscroll-behavior: contain;
|
||||
}
|
||||
|
||||
.appcontent::before {
|
||||
content: '';
|
||||
position: sticky;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: block;
|
||||
height: 8px;
|
||||
margin-bottom: -8px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
${cssManager.bdTheme('hsl(0 0% 0% / 0.08)', 'hsl(0 0% 0% / 0.4)')},
|
||||
${cssManager.bdTheme('hsl(0 0% 0% / 0.03)', 'hsl(0 0% 0% / 0.12)')},
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.controlbar::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
left: 240px;
|
||||
right: 0;
|
||||
height: 8px;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
to top,
|
||||
${cssManager.bdTheme('hsl(0 0% 0% / 0.08)', 'hsl(0 0% 0% / 0.4)')},
|
||||
${cssManager.bdTheme('hsl(0 0% 0% / 0.03)', 'hsl(0 0% 0% / 0.12)')},
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.controlbar {
|
||||
color: #fff;
|
||||
color: var(--dees-color-text-muted);
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
background: ${cssManager.bdTheme('hsl(220 13% 18%)', 'hsl(220 13% 12%)')};
|
||||
z-index: 2;
|
||||
background: var(--dees-color-bg-tertiary);
|
||||
border-top: 1px solid var(--dees-color-border-default);
|
||||
z-index: 11;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
@@ -325,9 +357,10 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
height: 100%;
|
||||
white-space: nowrap;
|
||||
cursor: default;
|
||||
color: hsl(0 0% 70%);
|
||||
color: var(--dees-color-text-muted);
|
||||
transition: all 0.15s ease;
|
||||
border-left: 1px solid hsl(0 0% 100% / 0.08);
|
||||
border-left: 1px solid var(--dees-color-border-strong);
|
||||
|
||||
}
|
||||
|
||||
.control:first-child {
|
||||
@@ -335,8 +368,8 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
}
|
||||
|
||||
.control:hover {
|
||||
background: hsl(0 0% 100% / 0.06);
|
||||
color: hsl(0 0% 95%);
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.control dees-icon {
|
||||
@@ -344,11 +377,11 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
}
|
||||
|
||||
.control.status-connected dees-icon {
|
||||
color: hsl(142 70% 50%);
|
||||
color: ${cssManager.bdTheme('hsl(142 70% 35%)', 'hsl(142 70% 50%)')};
|
||||
}
|
||||
|
||||
.control.status-terminal dees-icon {
|
||||
color: hsl(45 90% 55%);
|
||||
color: ${cssManager.bdTheme('hsl(38 92% 45%)', 'hsl(38 92% 55%)')};
|
||||
}
|
||||
|
||||
/* Global Message Banners */
|
||||
@@ -371,7 +404,7 @@ export class DeesSimpleAppDash extends DeesElement {
|
||||
font-size: 13px;
|
||||
font-family: 'Geist Sans', sans-serif;
|
||||
font-weight: 500;
|
||||
border-bottom: 1px solid ${cssManager.bdTheme('hsl(0 0% 91%)', 'hsl(0 0% 13%)')};
|
||||
border-bottom: 1px solid var(--dees-color-border-default);
|
||||
animation: bannerSlideDown 0.25s cubic-bezier(0.4, 0, 0.2, 1) forwards;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,9 +31,8 @@ export class DeesSimpleLogin extends DeesElement {
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
css`
|
||||
/* TODO: Migrate hardcoded values to --dees-* CSS variables */
|
||||
:host {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 3.9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
user-select: none;
|
||||
display: block;
|
||||
width: 100%;
|
||||
@@ -50,7 +49,7 @@ export class DeesSimpleLogin extends DeesElement {
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 3.9%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
}
|
||||
|
||||
.slotContainer {
|
||||
@@ -83,17 +82,17 @@ export class DeesSimpleLogin extends DeesElement {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.025em;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 9%)', 'hsl(0 0% 98%)')};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.subheader {
|
||||
font-size: 14px;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 45.1%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 100%)', 'hsl(0 0% 9%)')};
|
||||
border: 1px solid ${cssManager.bdTheme('hsl(0 0% 89.8%)', 'hsl(0 0% 14.9%)')};
|
||||
background: var(--dees-color-bg-primary);
|
||||
border: 1px solid var(--dees-color-border-default);
|
||||
border-radius: 8px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
@@ -81,21 +81,21 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
||||
height: 32px;
|
||||
font-size: 12px;
|
||||
font-family: 'SF Mono', 'Monaco', 'Inconsolata', 'Fira Code', monospace;
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 40%)', 'hsl(0 0% 60%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.terminal-header-icon {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 50%)', 'hsl(0 0% 50%)')};
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.terminal-header-command {
|
||||
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 80%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.terminal-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
@@ -243,12 +243,12 @@ export class DeesWorkspaceTerminalPreview extends DeesElement {
|
||||
}
|
||||
|
||||
.xterm .xterm-viewport::-webkit-scrollbar-thumb {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 80%)', 'hsl(0 0% 25%)')};
|
||||
background: var(--dees-color-scrollbar-thumb);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover {
|
||||
background: ${cssManager.bdTheme('hsl(0 0% 70%)', 'hsl(0 0% 35%)')};
|
||||
background: var(--dees-color-scrollbar-thumb-hover);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { css, type CSSResult } from '@design.estate/dees-element';
|
||||
import { css, cssManager, unsafeCSS, type CSSResult } from '@design.estate/dees-element';
|
||||
|
||||
// ============================================
|
||||
// Theme Token Type Definitions
|
||||
@@ -11,6 +11,7 @@ export interface IThemeColors {
|
||||
textPrimary: string;
|
||||
textSecondary: string;
|
||||
textMuted: string;
|
||||
textWarm: string;
|
||||
borderDefault: string;
|
||||
borderSubtle: string;
|
||||
borderStrong: string;
|
||||
@@ -18,6 +19,14 @@ export interface IThemeColors {
|
||||
accentSuccess: string;
|
||||
accentWarning: string;
|
||||
accentError: string;
|
||||
badgeDefaultBg: string;
|
||||
badgeDefaultFg: string;
|
||||
badgeSuccessBg: string;
|
||||
badgeSuccessFg: string;
|
||||
badgeWarningBg: string;
|
||||
badgeWarningFg: string;
|
||||
badgeErrorBg: string;
|
||||
badgeErrorFg: string;
|
||||
}
|
||||
|
||||
export interface IThemeSpacing {
|
||||
@@ -79,34 +88,52 @@ export interface ITheme {
|
||||
export const themeDefaults: ITheme = {
|
||||
colors: {
|
||||
light: {
|
||||
bgPrimary: '#ffffff',
|
||||
bgSecondary: '#fafafa',
|
||||
bgTertiary: '#f4f4f5',
|
||||
textPrimary: '#09090b',
|
||||
textSecondary: '#374151',
|
||||
textMuted: '#71717a',
|
||||
borderDefault: '#e5e7eb',
|
||||
borderSubtle: '#f4f4f5',
|
||||
borderStrong: '#d1d5db',
|
||||
bgPrimary: 'hsl(0 0% 100%)', // #ffffff
|
||||
bgSecondary: 'hsl(0 0% 98%)', // #fafafa
|
||||
bgTertiary: 'hsl(0 0% 96%)', // #f5f5f5
|
||||
textPrimary: 'hsl(0 0% 3.9%)', // #0a0a0a
|
||||
textSecondary: 'hsl(0 0% 20%)', // #333333
|
||||
textMuted: 'hsl(0 0% 45%)', // #737373
|
||||
textWarm: '#78716c', // warm stone
|
||||
borderDefault: 'hsl(0 0% 89.8%)', // #e5e5e5
|
||||
borderSubtle: 'hsl(0 0% 93%)', // #ededed
|
||||
borderStrong: 'hsl(0 0% 80%)', // #cccccc
|
||||
accentPrimary: '#3b82f6',
|
||||
accentSuccess: '#22c55e',
|
||||
accentWarning: '#f59e0b',
|
||||
accentError: '#ef4444',
|
||||
badgeDefaultBg: '#f4f4f5',
|
||||
badgeDefaultFg: '#3f3f46',
|
||||
badgeSuccessBg: '#dcfce7',
|
||||
badgeSuccessFg: '#166534',
|
||||
badgeWarningBg: '#fef3c7',
|
||||
badgeWarningFg: '#92400e',
|
||||
badgeErrorBg: '#fee2e2',
|
||||
badgeErrorFg: '#991b1b',
|
||||
},
|
||||
dark: {
|
||||
bgPrimary: '#09090b',
|
||||
bgSecondary: '#0a0a0a',
|
||||
bgTertiary: '#18181b',
|
||||
textPrimary: '#fafafa',
|
||||
textSecondary: '#d4d4d8',
|
||||
textMuted: '#a1a1aa',
|
||||
borderDefault: '#27272a',
|
||||
borderSubtle: '#1a1a1a',
|
||||
borderStrong: '#3f3f46',
|
||||
bgPrimary: 'hsl(0 0% 3.9%)', // #0a0a0a
|
||||
bgSecondary: 'hsl(0 0% 3.9%)', // #0a0a0a (matches sidebar/tile in dark)
|
||||
bgTertiary: 'hsl(0 0% 7%)', // #121212
|
||||
textPrimary: 'hsl(0 0% 98%)', // #fafafa
|
||||
textSecondary: 'hsl(0 0% 63.9%)', // #a3a3a3
|
||||
textMuted: 'hsl(0 0% 55%)', // #8c8c8c
|
||||
textWarm: '#b5a99a', // warm stone
|
||||
borderDefault: 'hsl(0 0% 14.9%)', // #262626
|
||||
borderSubtle: 'hsl(0 0% 11%)', // #1c1c1c
|
||||
borderStrong: 'hsl(0 0% 20%)', // #333333
|
||||
accentPrimary: '#3b82f6',
|
||||
accentSuccess: '#22c55e',
|
||||
accentWarning: '#f59e0b',
|
||||
accentError: '#ef4444',
|
||||
badgeDefaultBg: '#27272a',
|
||||
badgeDefaultFg: '#a1a1aa',
|
||||
badgeSuccessBg: '#14532d',
|
||||
badgeSuccessFg: '#4ade80',
|
||||
badgeWarningBg: '#451a03',
|
||||
badgeWarningFg: '#fbbf24',
|
||||
badgeErrorBg: '#450a0a',
|
||||
badgeErrorFg: '#f87171',
|
||||
},
|
||||
},
|
||||
spacing: {
|
||||
@@ -146,13 +173,17 @@ export const themeDefaults: ITheme = {
|
||||
},
|
||||
};
|
||||
|
||||
// Shorthand aliases for CSS template usage
|
||||
const l = themeDefaults.colors.light;
|
||||
const d = themeDefaults.colors.dark;
|
||||
|
||||
// ============================================
|
||||
// CSS Block for Component Import
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Default theme styles to be imported into every component's static styles array.
|
||||
* Provides CSS custom properties for spacing, radius, shadows, transitions, and control heights.
|
||||
* Provides CSS custom properties for colors, spacing, radius, shadows, transitions, and control heights.
|
||||
*
|
||||
* Usage:
|
||||
* ```typescript
|
||||
@@ -170,6 +201,94 @@ export const themeDefaults: ITheme = {
|
||||
*/
|
||||
export const themeDefaultStyles: CSSResult = css`
|
||||
:host {
|
||||
/* ========================================
|
||||
* Colors — Background (from themeDefaults)
|
||||
* ======================================== */
|
||||
--dees-color-bg-primary: ${cssManager.bdTheme(l.bgPrimary, d.bgPrimary)};
|
||||
--dees-color-bg-secondary: ${cssManager.bdTheme(l.bgSecondary, d.bgSecondary)};
|
||||
--dees-color-bg-tertiary: ${cssManager.bdTheme(l.bgTertiary, d.bgTertiary)};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Text (from themeDefaults)
|
||||
* ======================================== */
|
||||
--dees-color-text-primary: ${cssManager.bdTheme(l.textPrimary, d.textPrimary)};
|
||||
--dees-color-text-secondary: ${cssManager.bdTheme(l.textSecondary, d.textSecondary)};
|
||||
--dees-color-text-muted: ${cssManager.bdTheme(l.textMuted, d.textMuted)};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Border (from themeDefaults)
|
||||
* ======================================== */
|
||||
--dees-color-border-default: ${cssManager.bdTheme(l.borderDefault, d.borderDefault)};
|
||||
--dees-color-border-subtle: ${cssManager.bdTheme(l.borderSubtle, d.borderSubtle)};
|
||||
--dees-color-border-strong: ${cssManager.bdTheme(l.borderStrong, d.borderStrong)};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Warm Text (from themeDefaults)
|
||||
* ======================================== */
|
||||
--dees-color-text-warm: ${cssManager.bdTheme(l.textWarm, d.textWarm)};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Accent (from themeDefaults)
|
||||
* ======================================== */
|
||||
--dees-color-accent-primary: ${cssManager.bdTheme(l.accentPrimary, d.accentPrimary)};
|
||||
--dees-color-accent-success: ${cssManager.bdTheme(l.accentSuccess, d.accentSuccess)};
|
||||
--dees-color-accent-warning: ${cssManager.bdTheme(l.accentWarning, d.accentWarning)};
|
||||
--dees-color-accent-error: ${cssManager.bdTheme(l.accentError, d.accentError)};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Badge (from themeDefaults)
|
||||
* ======================================== */
|
||||
--dees-color-badge-default-bg: ${cssManager.bdTheme(l.badgeDefaultBg, d.badgeDefaultBg)};
|
||||
--dees-color-badge-default-fg: ${cssManager.bdTheme(l.badgeDefaultFg, d.badgeDefaultFg)};
|
||||
--dees-color-badge-success-bg: ${cssManager.bdTheme(l.badgeSuccessBg, d.badgeSuccessBg)};
|
||||
--dees-color-badge-success-fg: ${cssManager.bdTheme(l.badgeSuccessFg, d.badgeSuccessFg)};
|
||||
--dees-color-badge-warning-bg: ${cssManager.bdTheme(l.badgeWarningBg, d.badgeWarningBg)};
|
||||
--dees-color-badge-warning-fg: ${cssManager.bdTheme(l.badgeWarningFg, d.badgeWarningFg)};
|
||||
--dees-color-badge-error-bg: ${cssManager.bdTheme(l.badgeErrorBg, d.badgeErrorBg)};
|
||||
--dees-color-badge-error-fg: ${cssManager.bdTheme(l.badgeErrorFg, d.badgeErrorFg)};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Interactive States
|
||||
* ======================================== */
|
||||
--dees-color-hover: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.06)')};
|
||||
--dees-color-active: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
|
||||
--dees-color-pressed: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.12)')};
|
||||
--dees-color-row-hover: ${cssManager.bdTheme('hsl(222.2 47.4% 51.2% / 0.06)', 'hsl(217.2 91.2% 59.8% / 0.08)')};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Focus Ring
|
||||
* ======================================== */
|
||||
--dees-color-focus-ring: ${cssManager.bdTheme('rgba(59, 130, 246, 0.4)', 'rgba(59, 130, 246, 0.4)')};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Tooltip (inverted contrast)
|
||||
* ======================================== */
|
||||
--dees-color-tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
|
||||
--dees-color-tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Link
|
||||
* ======================================== */
|
||||
--dees-color-link: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
--dees-color-link-hover: ${cssManager.bdTheme('#2563eb', '#93bbfd')};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Code
|
||||
* ======================================== */
|
||||
--dees-color-code-bg: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.1)')};
|
||||
--dees-color-code-block-bg: ${cssManager.bdTheme('#f4f4f5', '#18181b')};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Selection
|
||||
* ======================================== */
|
||||
--dees-color-selection: ${cssManager.bdTheme('rgba(59, 130, 246, 0.2)', 'rgba(59, 130, 246, 0.3)')};
|
||||
|
||||
/* ========================================
|
||||
* Colors — Scrollbar
|
||||
* ======================================== */
|
||||
--dees-color-scrollbar-thumb: ${cssManager.bdTheme('rgba(0, 0, 0, 0.15)', 'rgba(255, 255, 255, 0.15)')};
|
||||
--dees-color-scrollbar-thumb-hover: ${cssManager.bdTheme('rgba(0, 0, 0, 0.25)', 'rgba(255, 255, 255, 0.25)')};
|
||||
|
||||
/* ========================================
|
||||
* Spacing Scale
|
||||
* ======================================== */
|
||||
|
||||
@@ -74,6 +74,28 @@ export interface ILightweightChartsBundle {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal type for an ECharts instance (loaded from CDN)
|
||||
*/
|
||||
export interface IEchartsInstance {
|
||||
setOption(option: Record<string, any>, notMerge?: boolean): void;
|
||||
resize(opts?: { width?: number; height?: number }): void;
|
||||
dispose(): void;
|
||||
on(eventName: string, handler: (...args: any[]) => void): void;
|
||||
off(eventName: string, handler?: (...args: any[]) => void): void;
|
||||
getOption(): Record<string, any>;
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle type for Apache ECharts
|
||||
*/
|
||||
export interface IEchartsBundle {
|
||||
init: (dom: HTMLElement, theme?: string | object | null, opts?: Record<string, any>) => IEchartsInstance;
|
||||
dispose: (chart: IEchartsInstance | HTMLElement | string) => void;
|
||||
getInstanceByDom: (dom: HTMLElement) => IEchartsInstance | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundle type for Tiptap editor and extensions
|
||||
*/
|
||||
@@ -108,6 +130,7 @@ export class DeesServiceLibLoader {
|
||||
private xtermSearchAddonLib: IXtermSearchAddonBundle | null = null;
|
||||
private highlightJsLib: HLJSApi | null = null;
|
||||
private lightweightChartsLib: ILightweightChartsBundle | null = null;
|
||||
private echartsLib: IEchartsBundle | null = null;
|
||||
private tiptapLib: ITiptapBundle | null = null;
|
||||
|
||||
// Loading promises to prevent duplicate concurrent loads
|
||||
@@ -116,6 +139,7 @@ export class DeesServiceLibLoader {
|
||||
private xtermSearchAddonLoadingPromise: Promise<IXtermSearchAddonBundle> | null = null;
|
||||
private highlightJsLoadingPromise: Promise<HLJSApi> | null = null;
|
||||
private lightweightChartsLoadingPromise: Promise<ILightweightChartsBundle> | null = null;
|
||||
private echartsLoadingPromise: Promise<IEchartsBundle> | null = null;
|
||||
private tiptapLoadingPromise: Promise<ITiptapBundle> | null = null;
|
||||
|
||||
private constructor() {}
|
||||
@@ -296,6 +320,36 @@ body > div[style*="top: -50000px"][style*="width: 50000px"] {
|
||||
return this.lightweightChartsLoadingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Apache ECharts from CDN
|
||||
* @returns Promise resolving to ECharts bundle
|
||||
*/
|
||||
public async loadEcharts(): Promise<IEchartsBundle> {
|
||||
if (this.echartsLib) {
|
||||
return this.echartsLib;
|
||||
}
|
||||
|
||||
if (this.echartsLoadingPromise) {
|
||||
return this.echartsLoadingPromise;
|
||||
}
|
||||
|
||||
this.echartsLoadingPromise = (async () => {
|
||||
// Use the pre-built ESM bundle which includes all chart types and components.
|
||||
// The +esm wrapper only exports the core without auto-registered chart types.
|
||||
const url = `${CDN_BASE}/echarts@${CDN_VERSIONS.echarts}/dist/echarts.esm.min.js`;
|
||||
const module = await import(/* @vite-ignore */ url);
|
||||
|
||||
this.echartsLib = {
|
||||
init: module.init,
|
||||
dispose: module.dispose,
|
||||
getInstanceByDom: module.getInstanceByDom,
|
||||
};
|
||||
return this.echartsLib;
|
||||
})();
|
||||
|
||||
return this.echartsLoadingPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load Tiptap rich text editor and extensions from CDN
|
||||
* @returns Promise resolving to Tiptap bundle with Editor and extensions
|
||||
@@ -310,23 +364,16 @@ body > div[style*="top: -50000px"][style*="width: 50000px"] {
|
||||
}
|
||||
|
||||
this.tiptapLoadingPromise = (async () => {
|
||||
const version = CDN_VERSIONS.tiptap;
|
||||
|
||||
// Load all Tiptap modules in parallel
|
||||
const [
|
||||
coreModule,
|
||||
starterKitModule,
|
||||
underlineModule,
|
||||
textAlignModule,
|
||||
linkModule,
|
||||
typographyModule,
|
||||
] = await Promise.all([
|
||||
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/core@${version}/+esm`),
|
||||
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/starter-kit@${version}/+esm`),
|
||||
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-underline@${version}/+esm`),
|
||||
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-text-align@${version}/+esm`),
|
||||
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-link@${version}/+esm`),
|
||||
import(/* @vite-ignore */ `${CDN_BASE}/@tiptap/extension-typography@${version}/+esm`),
|
||||
// Import directly from npm packages — bundled by esbuild into a single
|
||||
// module graph. CDN loading caused duplicate ProseMirror instances because
|
||||
// jsdelivr resolves each package's dependencies independently.
|
||||
const [coreModule, starterKitModule, underlineModule, textAlignModule, linkModule, typographyModule] = await Promise.all([
|
||||
import('@tiptap/core'),
|
||||
import('@tiptap/starter-kit'),
|
||||
import('@tiptap/extension-underline'),
|
||||
import('@tiptap/extension-text-align'),
|
||||
import('@tiptap/extension-link'),
|
||||
import('@tiptap/extension-typography'),
|
||||
]);
|
||||
|
||||
this.tiptapLib = {
|
||||
@@ -355,6 +402,7 @@ body > div[style*="top: -50000px"][style*="width: 50000px"] {
|
||||
this.loadXtermSearchAddon(),
|
||||
this.loadHighlightJs(),
|
||||
this.loadLightweightCharts(),
|
||||
this.loadEcharts(),
|
||||
this.loadTiptap(),
|
||||
]);
|
||||
}
|
||||
@@ -362,7 +410,7 @@ body > div[style*="top: -50000px"][style*="width: 50000px"] {
|
||||
/**
|
||||
* Check if a specific library is already loaded
|
||||
*/
|
||||
public isLoaded(library: 'xterm' | 'xtermFitAddon' | 'xtermSearchAddon' | 'highlightJs' | 'lightweightCharts' | 'tiptap'): boolean {
|
||||
public isLoaded(library: 'xterm' | 'xtermFitAddon' | 'xtermSearchAddon' | 'highlightJs' | 'lightweightCharts' | 'echarts' | 'tiptap'): boolean {
|
||||
switch (library) {
|
||||
case 'xterm':
|
||||
return this.xtermLib !== null;
|
||||
@@ -374,6 +422,8 @@ body > div[style*="top: -50000px"][style*="width: 50000px"] {
|
||||
return this.highlightJsLib !== null;
|
||||
case 'lightweightCharts':
|
||||
return this.lightweightChartsLib !== null;
|
||||
case 'echarts':
|
||||
return this.echartsLib !== null;
|
||||
case 'tiptap':
|
||||
return this.tiptapLib !== null;
|
||||
default:
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { DeesServiceLibLoader } from './DeesServiceLibLoader.js';
|
||||
export type { IXtermBundle, IXtermFitAddonBundle, IXtermSearchAddonBundle, IXtermSearchAddon, ITiptapBundle, ILightweightChartsBundle } from './DeesServiceLibLoader.js';
|
||||
export type { IXtermBundle, IXtermFitAddonBundle, IXtermSearchAddonBundle, IXtermSearchAddon, ITiptapBundle, ILightweightChartsBundle, IEchartsBundle, IEchartsInstance } from './DeesServiceLibLoader.js';
|
||||
export { CDN_BASE, CDN_VERSIONS } from './versions.js';
|
||||
|
||||
@@ -7,6 +7,7 @@ export const CDN_VERSIONS = {
|
||||
xtermAddonFit: '0.8.0',
|
||||
xtermAddonSearch: '0.13.0',
|
||||
highlightJs: '11.11.1',
|
||||
echarts: '5.6.0',
|
||||
lightweightCharts: '5.1.0',
|
||||
tiptap: '2.27.2',
|
||||
fontawesome: '7.2.0',
|
||||
|
||||
Reference in New Issue
Block a user