Compare commits
224 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6047705e7d | |||
| ab19b561c4 | |||
| 7ef3613e91 | |||
| 940eebe29f | |||
| 8ecaffe165 | |||
| e5cb31ffb1 | |||
| 7c2635fc13 | |||
| eb3d396c68 | |||
| 13ba5670f0 | |||
| 961b811b7a | |||
| cd491e1517 | |||
| b8a03def79 | |||
| 2b6798083d | |||
| 3c7b5dc690 | |||
| 2f4afddf73 | |||
| 212a46894e | |||
| 653ef109be | |||
| a0b17132ad | |||
| 486ec11ce6 | |||
| a24d28d4e0 | |||
| 8cc45a53e9 | |||
| edf7a86f07 | |||
| 8b8a8ff943 | |||
| 59610f463e | |||
| c1672bb8ae | |||
| 3e101840a6 | |||
| e87898ab82 | |||
| 8a1be59a51 | |||
| a3b2ace88d | |||
| c34037265e | |||
| 8c230fe3af | |||
| a695d60770 | |||
| ea30cbd381 | |||
| 39a4bf0dd3 | |||
| d5c9bc69b3 | |||
| 27f8ab752d | |||
| 5948fc83ea | |||
| 72e3d6a09e | |||
| de6f4a3ac5 | |||
| eecdc51557 | |||
| c841c49e1e | |||
| 2595d822d0 | |||
| 3ae0541065 | |||
| 4b735b768a | |||
| 9422edbfa1 | |||
| 37c5e92d6d | |||
| c7503de11e | |||
| 408362f3be | |||
| b3f5ab3d31 | |||
| 8d954b17ad | |||
| ac9cc8cfed | |||
| a1e808345e | |||
| efea2d62d9 | |||
| 2f95979cc6 | |||
| b3f098b41e | |||
| a0d5462ff1 | |||
| f1c204f790 | |||
| e806c9bce6 | |||
| fa56c7cce8 | |||
| 3601651e10 | |||
| b5055b0696 | |||
| a5f7a7ecee | |||
| dede3216fb | |||
| 5ee89b31b1 | |||
| 2d354ace55 | |||
| 34f5239607 | |||
| d17bdcbaad | |||
| 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 | |||
| 2e24d77f6a | |||
| 10b67adfe1 | |||
| 0d7f68086d | |||
| 9d0f6da905 | |||
| 42fd0b276e | |||
| 23d672040c | |||
| 24f96788d5 | |||
| 6e5def5708 | |||
| 472132e8cf | |||
| e062f5046e | |||
| f2d3fc28f8 | |||
| 7295bfcf92 | |||
| ad732a3e68 | |||
| b38bd28360 | |||
| 99a531ee74 | |||
| 1a3a5e5454 | |||
| 5cf8161735 | |||
| 46d9cdc741 | |||
| c13f319474 | |||
| c0ac8f593a | |||
| c52854f902 | |||
| c3b0f0df1f | |||
| cbc0bbcad4 | |||
| 8d3a1783fd | |||
| af1f660486 | |||
| b1c8a7446e | |||
| 7e991396e9 | |||
| 25cbf9bfdd | |||
| 4d8ba1fefc | |||
| 42317459ff | |||
| 932db338c6 | |||
| bc4b87b83a | |||
| eb055e7214 | |||
| c55eb948fe | |||
| 35779209ea | |||
| 8c6738ea15 | |||
| e7da1d8b44 | |||
| 358d82e7fa | |||
| 6452e05e1d | |||
| 07b536ea9a | |||
| 3fcb0cbf89 | |||
| 3285cbf0e7 | |||
| a2d750b2f6 | |||
| d4276710e6 | |||
| 66d64bf476 | |||
| 2504251707 | |||
| fed130f291 | |||
| 4f05b5907b | |||
| e517320dcd | |||
| ade5a25b3a | |||
| a396dfea12 | |||
| d0105e1b80 | |||
| 1eeebb35e6 | |||
| 14e8b8c533 | |||
| eaf327ea75 | |||
| 09741e0b37 | |||
| 5cadd1fc7f | |||
| 1795235c6d | |||
| ba7d387acb | |||
| 26ca16a284 | |||
| 3ab3eb5e5e | |||
| da5dbc70e2 | |||
| 19b7981542 | |||
| bad105074e | |||
| f124091784 | |||
| 68790a26ed | |||
| 2abf7e356d | |||
| ecd35683e6 | |||
| 93cb632448 | |||
| 047e42c6a3 | |||
| 59efa8cff0 | |||
| 09f0aa97dd | |||
| 7c62f45d77 | |||
| b123768474 | |||
| f292e7a7f4 | |||
| d82e5603a7 | |||
| 7e2386bcdf | |||
| eba2a03355 | |||
| 06c01f0690 | |||
| 91e03eb9c4 | |||
| b7f3f47c61 | |||
| 0a83f0e136 | |||
| 2b048cf34f | |||
| 7e50b8cb3f | |||
| b97601a876 | |||
| 5ddeb8fe7c | |||
| 25f46162c5 | |||
| b84b0e7ce6 | |||
| 69840de3a6 | |||
| 85badfbd21 | |||
| 264e460365 | |||
| bfda6b75e7 | |||
| 825a74b810 | |||
| f6bf0f8a45 | |||
| 66661e05a9 | |||
| 162688cdb5 | |||
| 8158b791c7 | |||
| ed8167385f | |||
| b472057e9d | |||
| 1bbf853043 | |||
| 8ff52fc562 | |||
| 5dd0367df0 | |||
| 1982c40337 | |||
| d2925871fd | |||
| 13ed06872a | |||
| 909e49dbd7 | |||
| 13923d9feb | |||
| e981ddf2d6 | |||
| b478ae3071 | |||
| d329d0b171 |
3
.gitignore
vendored
@@ -16,4 +16,5 @@ node_modules/
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
||||
# custom
|
||||
.playwright-mcp/
|
||||
|
Before Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 89 KiB |
|
Before Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 51 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 125 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 53 KiB |
@@ -49,5 +49,18 @@
|
||||
},
|
||||
"@ship.zone/szci": {
|
||||
"npmGlobalTools": []
|
||||
},
|
||||
"@git.zone/tsbundle": {
|
||||
"bundles": [
|
||||
{
|
||||
"from": "./ts_web/index.ts",
|
||||
"to": "./dist_bundle/bundle.js",
|
||||
"outputMode": "bundle",
|
||||
"bundler": "esbuild"
|
||||
}
|
||||
]
|
||||
},
|
||||
"@git.zone/tswatch": {
|
||||
"preset": "element"
|
||||
}
|
||||
}
|
||||
2
.vscode/settings.json
vendored
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"json.schemas": [
|
||||
{
|
||||
"fileMatch": ["/npmextra.json"],
|
||||
"fileMatch": ["/.smartconfig.json"],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
711
changelog.md
@@ -1,5 +1,716 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-04-12 - 3.78.0 - feat(dees-settings)
|
||||
add dees-settings layout component for displaying read-only settings with footer actions
|
||||
|
||||
- introduces a new dees-settings element with heading, description, settings field grid, and footer action support
|
||||
- exports dees-settings from the 00group-layout module index
|
||||
- adds demo examples covering populated, empty, and multi-action states
|
||||
|
||||
## 2026-04-12 - 3.77.0 - feat(dees-table)
|
||||
add configurable cell flash comparison and border highlight mode
|
||||
|
||||
- adds column-level flashCompare support so update highlighting can detect meaningful changes for custom cell values
|
||||
- adds flashBorder styling for cells with badges, icons, or custom rendered content where text-color flashing is not visible
|
||||
- avoids false-positive flash animations for non-primitive cell values unless a custom comparator is provided
|
||||
|
||||
## 2026-04-12 - 3.76.1 - fix(demo-inputs)
|
||||
wrap input component demos in dees-form containers for consistent form integration
|
||||
|
||||
- Adds dees-form wrappers across multiple input demo pages including checkbox, dropdown, file upload, iban, list, multitoggle, phone, quantity selector, radio group, tags, text, toggle, and typelist demos
|
||||
- Keeps horizontal and grid example layouts intact while nesting them inside form containers
|
||||
- Cleans up file upload and text demo markup to better match expected dees-form structure
|
||||
|
||||
## 2026-04-12 - 3.76.0 - feat(input)
|
||||
separate label info tooltips from description text across input components
|
||||
|
||||
- adds a dedicated infoText property for dees-label tooltips while keeping description available for helper text rendered below inputs
|
||||
- introduces a shared renderDescription() helper in the input base component and updates multiple input components to use the unified description styling
|
||||
- updates demos and consuming components to migrate tooltip content from description to infoText where appropriate
|
||||
|
||||
## 2026-04-12 - 3.75.0 - feat(dees-tile)
|
||||
add configurable overscroll handling to tile content and use it in modals
|
||||
|
||||
- introduces a reflected overscroll property on dees-tile with contain, auto, and none options
|
||||
- moves tile content scrolling and scrollbar styling into dees-tile instead of modal-specific styling
|
||||
- updates dees-modal to enable contained overscroll through the new dees-tile API
|
||||
|
||||
## 2026-04-12 - 3.74.2 - fix(modal,tile,input-text)
|
||||
move scroll handling from tile content to modal and update input text demo to use changeSubject subscriptions
|
||||
|
||||
- bump @design.estate/dees-wcctools from ^3.8.2 to ^3.8.4
|
||||
- set dees-tile content overflow to hidden and apply scroll styling through dees-modal part selectors
|
||||
- simplify the interactive dees-input-text demo by subscribing directly to changeSubject for live value updates
|
||||
|
||||
## 2026-04-12 - 3.74.1 - fix(dees-input-text)
|
||||
adjust password toggle and validation icon alignment in text input
|
||||
|
||||
- positions the password toggle and validation icon with fixed top offsets for improved vertical alignment
|
||||
- updates the validation icon styling to use a larger themed icon without the circular background
|
||||
|
||||
## 2026-04-12 - 3.74.0 - feat(input-text)
|
||||
add validated success state and text editing context menu to text inputs
|
||||
|
||||
- show a delayed checkmark confirmation for successful validation and hide inline validation text afterward
|
||||
- move IBAN validation handling into the shared text input validation function
|
||||
- improve the email validation demo to use a stricter regex-based check
|
||||
- add cut, copy, paste, and select-all context menu actions for text inputs
|
||||
|
||||
## 2026-04-12 - 3.73.2 - fix(input,label)
|
||||
correct validation state attribute handling in text inputs and refine label description icon styling
|
||||
|
||||
- Change dees-input-text validationState to reflect as a string attribute and align validation selectors with the emitted host attribute
|
||||
- Wrap the dees-label description icon in a dedicated container to improve sizing, hover feedback, and alignment
|
||||
|
||||
## 2026-04-12 - 3.73.1 - fix(dees-label)
|
||||
align label content and icon consistently using inline flex layout
|
||||
|
||||
- change the label container from inline-block to inline-flex with centered alignment
|
||||
- remove icon-specific vertical transform in favor of layout-based alignment
|
||||
|
||||
## 2026-04-12 - 3.73.0 - feat(dees-label)
|
||||
expand dees-label demo coverage and update supporting dependencies
|
||||
|
||||
- replace the minimal dees-label demo with a structured showcase for basic, required, description, combined, and empty-label states
|
||||
- add themed demo styling and inline annotations to better document component behavior
|
||||
- update @design.estate/dees-wcctools, lucide, and @types/node dependency versions
|
||||
|
||||
## 2026-04-12 - 3.72.1 - fix(dees-stepper)
|
||||
improve stepper exit animation timing for cancel confirmation flow
|
||||
|
||||
- Animate step tiles downward with fade-out during teardown instead of only fading the container
|
||||
- Delay stepper destruction briefly after dismissing the confirmation modal so both exit transitions render smoothly
|
||||
- Increase teardown delay to match the updated exit animation duration
|
||||
|
||||
## 2026-04-11 - 3.72.0 - feat(dees-stepper)
|
||||
add configurable cancellation flow with confirmation modal
|
||||
|
||||
- adds a cancelable option to control whether steppers can be dismissed
|
||||
- shows a confirmation modal when canceling via the new Cancel button or overlay backdrop
|
||||
- updates footer button rendering and separators to support the new cancel action consistently
|
||||
|
||||
## 2026-04-11 - 3.71.1 - fix(dees-modal)
|
||||
move modal content scrolling into dees-tile so long content stays scrollable with pinned header and actions
|
||||
|
||||
- Update dees-tile content area to use vertical scrolling when constrained by a max-height while keeping horizontal overflow clipped.
|
||||
- Remove duplicate scrolling styles from dees-modal and rely on the shared tile container behavior.
|
||||
- Add modal demo cases for long article, list, and form content to verify internal scrolling.
|
||||
|
||||
## 2026-04-11 - 3.71.0 - feat(dees-stepper)
|
||||
add footer menu actions with form-aware step validation
|
||||
|
||||
- replace step footer submit handling with configurable menuOptions actions
|
||||
- disable the primary footer action until required form fields are completed and show a completion hint
|
||||
- dispatch form data before running primary step actions and clean up form subscriptions on destroy
|
||||
- adjust overlay host positioning so the stepper container controls viewport layering correctly
|
||||
|
||||
## 2026-04-11 - 3.70.1 - fix(dees-modal)
|
||||
use icon font sizing for modal header buttons
|
||||
|
||||
- replace fixed width and height on header button icons with font-size to align dees-icon rendering
|
||||
|
||||
## 2026-04-08 - 3.70.0 - feat(dees-table)
|
||||
add opt-in flash highlighting for updated table cells
|
||||
|
||||
- introduces highlight-updates and highlight-duration properties for diff-based cell update highlighting
|
||||
- adds a warning banner when flash highlighting is enabled without rowKey
|
||||
- keeps selection stable across data refreshes and avoids flashing user-edited cells
|
||||
- includes a live demo showcasing flashing updates and reduced-motion support
|
||||
|
||||
## 2026-04-08 - 3.69.1 - fix(ui)
|
||||
refine heading emphasis and animate app dashboard subview expansion
|
||||
|
||||
- Adjust heading color hierarchy so h1-h2 use primary text while h3-h6 use secondary text, and reduce h1 font weight for better visual balance
|
||||
- Replace app dashboard subview conditional rendering with animated expand/collapse behavior using grid transitions and inert state handling
|
||||
|
||||
## 2026-04-08 - 3.69.0 - feat(dees-heading)
|
||||
add numeric aliases for horizontal rule heading levels and refine heading spacing styles
|
||||
|
||||
- Support level="7" as an alias for "hr" and level="8" as an alias for "hr-small".
|
||||
- Update heading and hr variant styles to use design tokens for spacing and colors, with per-level margin tuning.
|
||||
- Extend the demo to show both named and numeric hr heading level variants.
|
||||
|
||||
## 2026-04-08 - 3.68.0 - feat(dees-simple-appdash)
|
||||
add nested sidebar subviews and preserve submit labels from slotted text
|
||||
|
||||
- support grouped navigation items with expandable subviews and parent-to-first-subview fallback in the app dashboard
|
||||
- allow dees-form-submit to derive its button text from light DOM content when no explicit text property is set
|
||||
|
||||
## 2026-04-07 - 3.67.1 - fix(repo)
|
||||
no changes to commit
|
||||
|
||||
|
||||
## 2026-04-07 - 3.67.0 - feat(dees-table)
|
||||
improve inline cell editors with integrated input styling and auto-open dropdowns
|
||||
|
||||
- add a visually integrated mode to dees-input-text and dees-input-dropdown for table cell editing
|
||||
- auto-open dropdown editors when a table cell enters edit mode
|
||||
- refine table editing cell outline and dropdown value matching for inline editors
|
||||
|
||||
## 2026-04-07 - 3.66.0 - feat(dees-table)
|
||||
add virtualized row rendering for large tables and optimize table rendering performance
|
||||
|
||||
- add a virtualized mode with configurable overscan to render only visible rows while preserving scroll height
|
||||
- improve table render performance with memoized column and view-data computation plus deferred floating header rendering
|
||||
- update the dees-table demo to showcase virtualized scrolling in the fixed-height example
|
||||
|
||||
## 2026-04-07 - 3.65.0 - feat(dees-table)
|
||||
add schema-based in-cell editing with keyboard navigation and cell edit events
|
||||
|
||||
- replace editableFields with per-column editor configuration for text, number, checkbox, dropdown, date, and tags inputs
|
||||
- add focused/editing cell state with arrow key navigation plus Enter, Tab, Shift+Tab, F2, and Escape editing controls
|
||||
- dispatch cellEdit and cellEditError events with typed payloads and support column-level format, parse, validate, and editorOptions hooks
|
||||
- update table styles and demos to reflect editable cell behavior and rename sticky header usage to fixedHeight
|
||||
|
||||
## 2026-04-07 - 3.64.0 - feat(dees-table)
|
||||
add file-manager style row selection and JSON copy support
|
||||
|
||||
- adds optional selection checkbox rendering via the show-selection-checkbox property
|
||||
- supports plain, ctrl/cmd, and shift-click row selection with range selection behavior
|
||||
- adds Ctrl/Cmd+C and context menu actions to copy selected rows as formatted JSON
|
||||
- updates row selection styling to prevent native text selection during range selection
|
||||
|
||||
## 2026-04-07 - 3.63.0 - feat(dees-table)
|
||||
add floating header support with fixed-height table mode
|
||||
|
||||
- replace the sticky-header option with a fixed-height mode for internal scrolling
|
||||
- add a JS-managed floating header so column headers remain visible when tables scroll inside ancestor containers
|
||||
- sync floating header column widths and filter rows with the rendered table
|
||||
|
||||
## 2026-04-07 - 3.62.0 - feat(dees-table)
|
||||
add multi-column sorting with header menu controls and priority indicators
|
||||
|
||||
- replace single-column sort state with ordered sort descriptors for cascading client-side sorting
|
||||
- add Shift+click header sorting, context menu actions, and confirmation before replacing an active sort cascade
|
||||
- show multi-sort direction and priority badges in table headers and add a demo showcasing the new behavior
|
||||
|
||||
## 2026-04-06 - 3.61.2 - fix(dees-input-list,dees-icon)
|
||||
preserve input focus after list updates and make icons ignore pointer events
|
||||
|
||||
- Delays refocusing the add input in dees-input-list until after Lit re-renders complete when adding or removing entries.
|
||||
- Adds pointer-events: none to dees-icon so icons do not block click interactions on surrounding controls.
|
||||
|
||||
## 2026-04-05 - 3.61.1 - fix(dees-input-list)
|
||||
align list input with dees-tile styling and improve item affordances
|
||||
|
||||
- wrap the list in dees-tile with a dynamic item count heading and move the add-item controls into the tile footer
|
||||
- replace custom container styling with shared tile and theme tokens for hover, focus, row, and disabled states
|
||||
- show a bullet icon for non-sortable or disabled items when no candidate state icon is present
|
||||
|
||||
## 2026-04-05 - 3.61.0 - feat(dees-input-list)
|
||||
allow freeform entries alongside candidate suggestions in dees-input-list
|
||||
|
||||
- adds an allowFreeform option so Enter can add values that do not exactly match the candidate list
|
||||
- shows check and question-mark indicators to distinguish known candidates from custom freeform items
|
||||
- updates the component demo with a freeform-plus-candidates example
|
||||
|
||||
## 2026-04-05 - 3.60.0 - feat(dees-input-list)
|
||||
add candidate autocomplete with tab completion and payload retrieval
|
||||
|
||||
- Adds terminal-style inline autocomplete with ghost text, Tab accept, Shift+Tab cycling, and Escape clearing for candidate-based input.
|
||||
- Introduces candidate payload support with APIs to retrieve selected candidate objects after items are added.
|
||||
- Updates the dees-input-list demo with candidate selection examples for team members and technology stacks.
|
||||
|
||||
## 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
|
||||
|
||||
- adds a header action to expand and collapse the chart area into a full page view
|
||||
- updates chart area styling for the custom header, label, and expand button
|
||||
- resizes the chart after toggling full page mode by fitting the visible time scale
|
||||
|
||||
## 2026-04-03 - 3.51.2 - fix(ui)
|
||||
standardize tile-based layouts across input, product card, and terminal preview components
|
||||
|
||||
- Replace custom card containers with dees-tile in code input, richtext editor, shopping product card, and terminal preview components
|
||||
- Move toolbars and footers into dees-tile header/footer slots for more consistent structure and spacing
|
||||
- Update hover, focus, and selected state styling to target dees-tile parts while preserving existing component behavior
|
||||
|
||||
## 2026-04-03 - 3.51.1 - fix(repo)
|
||||
no changes to commit
|
||||
|
||||
|
||||
## 2026-04-03 - 3.51.0 - feat(chart-area)
|
||||
replace ApexCharts with Lightweight Charts for area chart rendering
|
||||
|
||||
- switch chart dependency and CDN loader from apexcharts to lightweight-charts
|
||||
- update dees-chart-area styling and tooltip support for the new chart engine
|
||||
- adjust appdash terminal overlay sizing with automatic height
|
||||
|
||||
## 2026-04-02 - 3.50.2 - fix(chart,dashboardgrid demos)
|
||||
use dees-button text property consistently in demo interactions and update dataset button highlighting reliably
|
||||
|
||||
- Replaces textContent reads with the component text property when wiring demo button handlers.
|
||||
- Updates the chart area demo to refresh dataset button highlight states directly on click without redundant listener replacement logic.
|
||||
- Uses the button text property when toggling dashboard edit mode labels.
|
||||
|
||||
## 2026-04-02 - 3.50.1 - fix(appdash)
|
||||
use banner height CSS variable for terminal layout and expose terminal resize handling
|
||||
|
||||
- Removes manual terminal top offset updates in the app dashboard and relies on the shared --banner-area-height CSS variable instead.
|
||||
- Drops fixed max-width and max-height calculations so the terminal can size more reliably within its container.
|
||||
- Makes the workspace terminal resize handler public to support external resize coordination.
|
||||
|
||||
## 2026-04-02 - 3.50.0 - feat(dees-simple-appdash)
|
||||
add global message banners with actions and dismissal support
|
||||
|
||||
- introduces typed global message APIs and public methods to add, remove, and clear banners
|
||||
- renders info, success, warning, and error banners with optional icons and action buttons
|
||||
- adjusts app content and terminal positioning to account for banner height dynamically
|
||||
- updates the demo to showcase dismissible and actionable global messages
|
||||
|
||||
## 2026-04-02 - 3.49.2 - fix(dees-input-list)
|
||||
refine dees-input-list spacing and simplify the add item action button
|
||||
|
||||
- reduce list item, input, helper text, and empty state sizing for a more compact layout
|
||||
- replace the add action from dees-button to a native icon-only button and remove the unused button import
|
||||
- simplify add-input styling by removing bordered focus treatment and using a minimal inline input appearance
|
||||
|
||||
## 2026-04-01 - 3.49.1 - fix(ts_web)
|
||||
resolve TypeScript nullability and event typing issues across web components
|
||||
|
||||
- adds explicit non-null assertions and nullable types to component properties, shadowRoot queries, and modal references
|
||||
- normalizes demo and component event listeners by casting generic Event objects to CustomEvent where needed
|
||||
- guards optional form, dropdown, menu, and DOM interactions to prevent invalid access under stricter TypeScript checks
|
||||
- updates tsconfig to include Node types for the web build environment
|
||||
|
||||
## 2026-03-18 - 3.49.0 - feat(dataview-statusobject)
|
||||
add last updated footer to status object and refresh demo data
|
||||
|
||||
- Render a bottom bar that shows the status object's lastUpdated timestamp when available.
|
||||
- Adjust detail row padding to keep spacing consistent with the new footer layout.
|
||||
- Update demo status objects to include lastUpdated examples for current, hourly, and daily timestamps.
|
||||
- Bump @tsclass/tsclass from ^9.3.0 to ^9.5.0.
|
||||
|
||||
## 2026-03-14 - 3.48.5 - fix(repo)
|
||||
no changes to commit
|
||||
|
||||
|
||||
## 2026-03-14 - 3.48.4 - fix(storage-browser)
|
||||
rename S3-specific storage browser interfaces to generic storage types
|
||||
|
||||
- Replaces IS3DataProvider, IS3Object, and IS3ChangeEvent with generic storage interface names across storage browser components
|
||||
- Updates demo provider naming and user-facing demo text from S3 browser to Storage browser
|
||||
- Aligns interface and utility comments with storage-agnostic terminology
|
||||
|
||||
## 2026-03-14 - 3.48.3 - fix(dataview)
|
||||
rename dees-s3-browser exports and custom elements to dees-storage-browser
|
||||
|
||||
- Replaces the dees-s3-browser module path with dees-storage-browser in dataview exports
|
||||
- Renames the main custom element from dees-s3-browser to dees-storage-browser
|
||||
- Renames related columns, keys, preview, demo, interfaces, and utility entry points under the new storage-browser module
|
||||
|
||||
## 2026-03-12 - 3.48.2 - fix(repo)
|
||||
no changes to commit
|
||||
|
||||
|
||||
## 2026-03-12 - 3.48.1 - fix(repo)
|
||||
no changes to commit
|
||||
|
||||
|
||||
## 2026-03-12 - 3.48.0 - feat(dataview)
|
||||
add an S3 browser component with column and list views, file preview, editing, and object management
|
||||
|
||||
- introduces a new dees-s3-browser module with shared interfaces, utilities, demo, and exports
|
||||
- supports browsing S3-style prefixes in both column and list layouts with breadcrumb navigation
|
||||
- adds file preview with text editing, download, and delete actions
|
||||
- includes create, rename, move, delete, upload, and drag-and-drop handling for files and folders
|
||||
- adds optional live change stream integration with refresh indicators
|
||||
|
||||
## 2026-03-11 - 3.47.2 - fix(deps)
|
||||
bump @design.estate/dees-domtools and @design.estate/dees-element dependencies
|
||||
|
||||
- update @design.estate/dees-domtools from ^2.3.9 to ^2.5.1
|
||||
- update @design.estate/dees-element from ^2.2.1 to ^2.2.2
|
||||
|
||||
## 2026-03-11 - 3.47.1 - fix(dees-statsgrid)
|
||||
add tablet breakpoint to render stats grid as three columns
|
||||
|
||||
- Added cssManager.cssForTablet rule to set .stats-grid grid-template-columns: repeat(3, 1fr).
|
||||
- Improves responsive layout on tablet devices for dees-statsgrid tiles.
|
||||
- Change made in ts_web/elements/00group-dataview/dees-statsgrid/dees-statsgrid.ts
|
||||
|
||||
## 2026-03-11 - 3.47.0 - feat(dees-statsgrid)
|
||||
add container-responsive behavior and responsive CSS to dees-statsgrid; bump @design.estate/dees-element dependency to ^2.2.1
|
||||
|
||||
- Added @containerResponsive decorator and import to dees-statsgrid
|
||||
- Added cssManager.cssForPhablet and cssManager.cssForPhone responsive style blocks to adjust layout, spacing and font sizes on smaller viewports
|
||||
- Bumped dependency @design.estate/dees-element from ^2.1.6 to ^2.2.1
|
||||
|
||||
## 2026-03-10 - 3.46.1 - fix(dees-appui)
|
||||
add min-height: 0 to mainmenu and secondarymenu to prevent unintended container height and fix layout stacking
|
||||
|
||||
- Modified ts_web/elements/00group-appui/dees-appui/dees-appui.ts: added min-height: 0 to .maingrid > dees-appui-mainmenu and .maingrid > dees-appui-secondarymenu
|
||||
- Fixes layout issues where children or flexbox-derived min-height could cause menu containers to expand and interfere with z-index stacking
|
||||
|
||||
## 2026-03-10 - 3.46.0 - feat(dees-tile)
|
||||
unify tile metadata into a consistent bottom info bar and add PDF file-size display
|
||||
|
||||
- Introduce renderBottomBar() hook in DeesTileBase and remove per-component bottom badges/labels in favor of a unified info bar.
|
||||
- Implement renderBottomBar in audio, video, image, folder, note and pdf tiles to show label, counts, dimensions, duration, language/line info and page counts.
|
||||
- PDF tile: add fileSize state, attempt to read download info and display formatted file size in the info bar; show currentPreviewPage/pageCount when hovering.
|
||||
- Styling changes: replace legacy badges/labels with .tile-info-bar (.info-label, .info-detail, .info-spacer); adjust padding, font sizing, z-index, and remove hover translate for clickable tiles.
|
||||
- PDF demo and styles: use cssManager theming for demo colors and adjust preview padding.
|
||||
- Bump devDependencies: @git.zone/tswatch -> ^3.3.0 and @types/node -> ^25.4.0
|
||||
|
||||
## 2026-03-10 - 3.45.1 - fix(dees-appui)
|
||||
substitute route params into URL hash when navigating
|
||||
|
||||
- Replaces :param placeholders in view.route with provided params before updating the URL hash.
|
||||
- Ensures window.history.pushState is called with the resolved route so URLs do not contain literal parameter tokens.
|
||||
|
||||
## 2026-03-10 - 3.45.0 - feat(dees-form)
|
||||
register new input components (tags, list, wysiwyg, richtext) and emit change notification for richtext updates
|
||||
|
||||
- Added imports and registration of DeesInputTags, DeesInputList, DeesInputWysiwyg, and DeesInputRichtext in dees-form
|
||||
- Extended TFormInputElement union type to include the new input components
|
||||
- DeesInputRichtext now calls changeSubject.next(this.value) in the editor onUpdate handler to propagate changes
|
||||
|
||||
## 2026-03-10 - 3.44.0 - feat(appui-tabs)
|
||||
add support for left/right tab action buttons and content tab action APIs
|
||||
|
||||
- Introduce ITabAction interface and add actionsLeft/actionsRight properties to dees-appui-tabs, dees-appui-maincontent, and dees-appui.
|
||||
- Render action buttons with new styles and renderActions() helper, including disabled state and click handlers; wire actions into tab components.
|
||||
- Add public clear() on dees-appui-tabs and improve tab selection logic to reset selection when tabs become empty or when the selected tab is removed.
|
||||
- Expose setContentTabActionsLeft and setContentTabActionsRight on the DeesAppui programmatic API and update interfaces/appconfig accordingly.
|
||||
- Update demos to showcase action buttons, add clear-all behavior, and adjust layout/styling for action areas.
|
||||
|
||||
## 2026-03-09 - 3.43.4 - fix(media)
|
||||
remove deprecated dees-pdf and dees-pdf-preview components and bump several dependencies
|
||||
|
||||
- Removed deprecated PDF components and related demos/styles: ts_web/elements/00group-media/dees-pdf/* and ts_web/elements/00group-media/dees-pdf-preview/*
|
||||
- Removed exports for dees-pdf and dees-pdf-preview from ts_web/elements/00group-media/index.ts (public API removal)
|
||||
- Dependency upgrades: @design.estate/dees-domtools → ^2.3.9, apexcharts → ^5.10.3, lucide → ^0.577.0, @fortawesome/* → ^7.2.0
|
||||
- DevDependency upgrades: @git.zone/tsbuild → ^4.3.0, @git.zone/tsbundle → ^2.9.1, @git.zone/tstest → ^3.3.2, @git.zone/tswatch → ^3.2.5, @types/node → ^25.3.5
|
||||
- Updated ts_web/services/versions.ts to align CDN/version constants (apexcharts, tiptap → 2.27.2, fontawesome)
|
||||
|
||||
## 2026-02-24 - 3.43.3 - fix(dees-table)
|
||||
use lucide icon identifier for Search action in dees-table
|
||||
|
||||
- Replaced iconName 'magnifyingGlass' with 'lucide:Search' in ts_web/elements/00group-dataview/dees-table/dees-table.ts
|
||||
- Updates the icon identifier for the header 'Search' action; no functional behavior changed
|
||||
|
||||
## 2026-02-21 - 3.43.2 - fix(dees-chart-log)
|
||||
avoid duplicate log entries, optimize incremental updates, enforce maxEntries, and respect filters when writing logs
|
||||
|
||||
- Prevent property-bound logEntries from duplicating entries already present in logBuffer by deduplicating on timestamp|message
|
||||
- Call updateMetrics() when replaying or appending log entries so metrics stay accurate
|
||||
- Skip processing entirely when the incoming logEntries array is unchanged
|
||||
- Optimize append-only updates by writing only the new tail entries instead of full re-render
|
||||
- Enforce maxEntries when appending to the logBuffer to maintain buffer size
|
||||
- Respect filterMode and searchQuery when deciding whether to write appended entries to the terminal
|
||||
|
||||
## 2026-02-21 - 3.43.1 - fix(dees-chart-log)
|
||||
replay buffered log entries when terminal becomes ready and sync logEntries updates to re-render filtered logs
|
||||
|
||||
- Replay entries stored in logBuffer after terminal initialization to avoid losing entries that arrived early
|
||||
- Add updated() lifecycle hook to copy logEntries into logBuffer and call reRenderFilteredLogs when terminalReady
|
||||
- Call super.updated(changedProperties) to preserve base class behavior
|
||||
|
||||
## 2026-02-17 - 3.43.0 - feat(dees-form)
|
||||
add layout styles to dees-form and standardize demo input grouping
|
||||
|
||||
- Add static CSS to dees-form: default column layout with gap and support for [horizontal-layout] (row wrapping, alignment and gap).
|
||||
- Remove inline <style> from dees-form render to centralize styling.
|
||||
- Simplify dees-input-base styles by removing host margins and making spacing container-driven.
|
||||
- Update multiple demo files to wrap related inputs in a new .input-group container and include .input-group CSS for consistent vertical spacing.
|
||||
|
||||
## 2026-02-16 - 3.42.2 - fix(dees-chart-area)
|
||||
add ApexAxisChartSeries type to dees-chart-area component to improve typing for ApexCharts series data
|
||||
|
||||
- Introduces ApexAxisChartSeries type alias with support for number arrays, [x,y] tuples, and object {x,y,...} series entries
|
||||
- Type-only change — no runtime or API behavior modified
|
||||
- File changed: ts_web/elements/00group-chart/dees-chart-area/component.ts
|
||||
|
||||
## 2026-02-16 - 3.42.1 - fix(dees-table)
|
||||
Guard against undefined action.type in dees-table by using optional chaining and update several dependencies
|
||||
|
||||
- Use optional chaining (action.type?.includes(...)) in ts_web/elements/.../dees-table.ts to prevent runtime errors when action.type is undefined
|
||||
- Bump dependency apexcharts from ^5.3.6 to ^5.5.0
|
||||
- Bump dependency lucide from ^0.563.0 to ^0.564.0
|
||||
- Bump devDependency @git.zone/tswatch from ^3.0.1 to ^3.1.0
|
||||
- Bump devDependency @types/node from ^25.0.10 to ^25.2.3
|
||||
|
||||
## 2026-02-02 - 3.42.0 - feat(dees-form-submit)
|
||||
forward button properties to internal dees-button, use property bindings, add demo and styles
|
||||
|
||||
- Added forwarded properties: type, size, icon, iconPosition (with defaults) and preserved text/status/disabled
|
||||
- Changed template to use property bindings (.prop) for dees-button instead of string attributes
|
||||
- Switched internal event handler to listen for dees-button's @clicked event (was @click)
|
||||
- Added component styles (:host display and dees-button width:100%) and improved layout
|
||||
- Expanded demo with multiple usage examples (basic, icons, types, sizes, states, and a form context)
|
||||
|
||||
## 2026-02-02 - 3.41.6 - fix(dees-simple-appdash)
|
||||
respect selectedView when loading initial view, falling back to the first tab
|
||||
|
||||
- firstUpdated now loads this.selectedView if set, otherwise loads the first view tab
|
||||
- Prevents always loading the first tab and preserves a previously selected view on initial render
|
||||
|
||||
## 2026-02-01 - 3.41.5 - fix(dees-service-lib-loader)
|
||||
prevent horizontal scrollbar by offsetting xterm WidthCache measurement container
|
||||
|
||||
- Injects additional CSS into DeesServiceLibLoader to move xterm.js WidthCache measurement div off-screen horizontally (selector: body > div[style*="top: -50000px"][style*="width: 50000px"])
|
||||
- Fixes root cause where xterm creates a large-width measurement container (width: 50000px) on document.body that expands scrollWidth and causes a horizontal scrollbar
|
||||
- Change applied in ts_web/services/DeesServiceLibLoader.ts by concatenating the fix CSS into the injected stylesheet
|
||||
|
||||
## 2026-01-29 - 3.41.4 - fix()
|
||||
no changes
|
||||
|
||||
- No files changed in this commit; no code or documentation modified
|
||||
- No release required
|
||||
|
||||
## 2026-01-29 - 3.41.3 - fix(dees-pdf-viewer)
|
||||
use in-memory PDF data for download and print; add robust print wrapper, cleanup and error handling
|
||||
|
||||
- Download and Print now use pdfDocument.getData() to create Blob URLs so in-memory PDFs (pdf.js) can be saved/printed.
|
||||
- Print flow now opens an HTML wrapper with an iframe to allow onafterprint handling, auto-close, popup-fallback and timed cleanup of Blob URLs.
|
||||
- Added try/catch logging, URL.revokeObjectURL calls and safety timeouts to avoid resource leaks.
|
||||
- Removed context menu items that relied on the raw PDF URL (Open in New Tab, Copy PDF URL); Download/Print actions now await the async handlers.
|
||||
|
||||
## 2026-01-28 - 3.41.2 - fix(dees-pdf-viewer)
|
||||
account for devicePixelRatio when setting canvas dimensions and scale 2D context to render crisp PDF pages and thumbnails on high-DPI displays
|
||||
|
||||
- Multiply canvas.width and canvas.height by window.devicePixelRatio (dpr) and use Math.floor to set the actual pixel buffer size
|
||||
- Call ctx.scale(dpr, dpr) so drawing is rendered at device pixels while keeping CSS display size unchanged
|
||||
- Apply the same high-DPI adjustments to both main page rendering and thumbnail generation
|
||||
- Keep canvas.style.width and canvas.style.height set to viewport dimensions to preserve layout
|
||||
|
||||
## 2026-01-27 - 3.41.1 - fix(dataview-codebox)
|
||||
fix dees-dataview codebox layout to ensure full-height, proper flex behavior and scrolling; bump internal dependencies
|
||||
|
||||
- Updated CSS in ts_web/elements/00group-dataview/dees-dataview-codebox/dees-dataview-codebox.ts: added height:100%, box-sizing, display:flex and flex-direction:column on container, set flex-shrink on header elements, made code grid overflow:auto with flex:1 and min-height:0 to prevent overflow issues.
|
||||
- Bumped dependencies in package.json: @design.estate/dees-domtools from ^2.3.7 to ^2.3.8 and @design.estate/dees-element from ^2.1.5 to ^2.1.6.
|
||||
- Non-breaking visual/layout fix — suitable for a patch release.
|
||||
|
||||
## 2026-01-27 - 3.41.0 - feat(docs)
|
||||
document new media & tile components and expand Workspace/IDE component docs; update component count to 90+
|
||||
|
||||
- Updated README component count from 70+ to 90+.
|
||||
- Added Media & Tile components documentation (DeesTilePdf, DeesTileImage, DeesTileAudio, DeesTileVideo, DeesTileNote, DeesTileFolder, DeesPreview, DeesPdfViewer, DeesImageViewer, DeesAudioViewer, DeesVideoViewer).
|
||||
- Expanded Workspace/IDE documentation and introduced workspace subcomponents (DeesWorkspace, DeesWorkspaceMonaco, DeesWorkspaceDiffEditor, DeesWorkspaceFiletree, DeesWorkspaceTerminal, DeesWorkspaceTerminalPreview, DeesWorkspaceMarkdown, DeesWorkspaceMarkdownoutlet, DeesWorkspaceBottombar).
|
||||
- Enhanced Contextmenu docs to demonstrate nested submenus and programmatic API usage.
|
||||
- Added ITileFolderItem interface example for tile folder items.
|
||||
|
||||
## 2026-01-27 - 3.40.0 - feat(dees-tile)
|
||||
unify tile badge styling and markup; replace component-specific badge classes with shared tile-badge classes and update related imports/tests
|
||||
|
||||
- Add shared badge styles: .tile-badge-corner, .tile-badge-topright, .tile-badge and layout rules in dees-tile-shared/styles.ts
|
||||
- Update tile components (audio, video, image, folder, note, pdf) to use new badge markup and remove duplicated badge CSS
|
||||
- Shift badge positioning when a tile label is present (.tile-container:has(.tile-label))
|
||||
- Remove older per-component badge rules (duration-badge, item-count-badge, dimension-badge, note-language/note-line-indicator, preview-page-indicator) and replace with unified classes
|
||||
- Update tests to import dees-contextmenu and dees-dashboardgrid from new 00group-overlay / 00group-layout paths to reflect folder reorganization
|
||||
|
||||
## 2026-01-27 - 3.39.1 - fix(dees-tile-note)
|
||||
use horizontal pointer position to scroll note body by computing percentage from clientX and element width instead of clientY and height
|
||||
|
||||
- Changed ts_web/elements/00group-media/dees-tile-note/component.ts: use x = e.clientX - rect.left and percentage = x / rect.width to drive scrollTop calculation instead of using vertical coordinates
|
||||
- Fixes incorrect scroll mapping where vertical mouse position was used for horizontal scrolling interaction
|
||||
|
||||
## 2026-01-27 - 3.39.0 - feat(components)
|
||||
add large set of new UI components and demos, reorganize groups, and bump a few dependencies
|
||||
|
||||
- Add Media viewers: dees-image-viewer, dees-audio-viewer, dees-video-viewer, DeesPdf/DeesPdfViewer and related PDF utilities (CanvasPool, PdfManager) and demos
|
||||
- Introduce dees-preview composite that auto-detects content and delegates to appropriate viewers
|
||||
- New Tile system (DeesTileBase) and tile components: dees-tile-pdf, dees-tile-image, dees-tile-audio, dees-tile-video, dees-tile-note, dees-tile-folder plus demos
|
||||
- Add dashboard/grid system: dees-dashboardgrid with drag/resize, collision resolution, layout helpers, demos and utilities
|
||||
- Data view additions: dees-table with lucene-like query parser, column utilities, and dees-statsgrid enhancements (including cpuCores visualization)
|
||||
- New feedback & overlay components: dees-toast, dees-actionbar, dees-progressbar, dees-spinner, dees-badge and supporting demos/interfaces
|
||||
- Many layout and utility components added or improved: dees-panel, dees-chips, dees-heading, dees-label, dees-pagination, dees-stepper, and associated demos
|
||||
- Refactor project structure: components organized into 00group-* directories and demo metadata migrated from demoGroup to demoGroups (string[]); many import path updates to match new grouping
|
||||
- Deprecations/notes: dees-pdf-preview and legacy dees-pdf marked as deprecated in favor of new viewers/tiles
|
||||
- Dependency bumps in package.json: @design.estate/dees-wcctools -> ^3.8.0, @git.zone/tstest -> ^3.1.8
|
||||
|
||||
## 2026-01-26 - 3.38.0 - feat(appui)
|
||||
add app shell and bottom bar APIs, new input components, and update README component listings and docs
|
||||
|
||||
- README: updated components count (80+ → 70+), reorganized categories (added App Shell / Pre-built Templates, renamed Workspace/IDE) and improved typography
|
||||
- Document listing: added DeesActionbar, DeesInputToggle, DeesInputCode, DeesAppuiBottombar and other component entries
|
||||
- Menu item interface: added closeable and onClose properties for dismissible menu items
|
||||
- IViewDefinition: expanded content type to accept element constructors and async content, added badgeVariant and cache flags
|
||||
- New interfaces added: IBottomBarWidget, IBottomBarAction, IViewActivationContext to support bottom bar widgets/actions and view activation context
|
||||
|
||||
## 2026-01-25 - 3.37.1 - fix(editor)
|
||||
fix monaco/editor layout, update dev deps, simplify watch script and remove Playwright snapshots
|
||||
|
||||
- Make dees-input-code host and inner wrappers flex so Monaco editor grows to fill available height (set flex, min-height and height:100% where needed).
|
||||
- Set dees-workspace-monaco and editor-wrapper to stretch so the embedded Monaco editor fills the component.
|
||||
- Bump dev dependencies: @git.zone/tsbuild -> ^4.1.2, @git.zone/tsbundle -> ^2.8.3, @git.zone/tstest -> ^3.1.7, @git.zone/tswatch -> ^3.0.1; also bump @types/node -> ^25.0.10 and lucide -> ^0.563.0.
|
||||
- Change npm script: watch from "tswatch element" to "tswatch" and add @git.zone/tswatch preset in npmextra.json.
|
||||
- Add .playwright-mcp/ to .gitignore and remove many Playwright screenshot/test artifact PNGs to keep repo tidy.
|
||||
|
||||
## 2026-01-13 - 3.37.0 - feat(dees-button,dees-statsgrid)
|
||||
add unified icon property and icon-position support to dees-button; add partition and disk tile types to dees-statsgrid
|
||||
|
||||
- dees-button: introduce icon (string) and iconPosition ('left'|'right') properties; extractLightDom() migrates legacy <dees-icon> slotted usage into properties and supports icon-only buttons
|
||||
- dees-button: render left/right icon, hide text for icon-only size; preserve backward compatibility for old type mappings
|
||||
- Demo updates: convert iconFA attributes to new icon syntax (e.g. 'fa:plus', 'lucide:Search') and add new icon-via-property examples and event logging
|
||||
- dees-statsgrid: add IPartitionData and IDiskData interfaces and new tile types 'partition' and 'disk' with rendering, styles and thresholds (usage, filesystem, mountpoint, capacity, iops, health)
|
||||
- dees-statsgrid.demo: add Disk & Storage demo panel with sample partition and disk tiles and configuration notes
|
||||
- misc: added/updated many demo images and metadata to reflect new icon/tile features
|
||||
|
||||
## 2026-01-12 - 3.36.0 - feat(dees-chart-log)
|
||||
add xterm search addon support and enhance chart log demo with structured/raw (Docker-like) logs and themeable styles
|
||||
|
||||
- Add IXtermSearchAddon and IXtermSearchAddonBundle types and integrate xterm-addon-search loading in DeesServiceLibLoader with caching and preload support
|
||||
- Expose new types in services index and add xtermAddonSearch version to CDN_VERSIONS
|
||||
- Enhance dees-chart-log demo: separate structured and raw (docker) log panels, add Docker/ANSI log templates, start/stop controls for each simulation, and raw log writing
|
||||
- Switch demo styling to cssManager theme-aware CSS, and import css helpers from dees-element
|
||||
- Add many .playwright-mcp PNG assets used by demos/tests
|
||||
|
||||
## 2026-01-12 - 3.35.1 - fix(dees-statsgrid)
|
||||
center CPU core bars when they occupy less than ~66% of the tile and switch bar fills to absolute positioning for correct alignment and smoother transitions
|
||||
|
||||
- Add .cpu-cores-bars.centered CSS rule to horizontally center the bars when appropriate.
|
||||
- Compute shouldCenter by estimating max bars width (cores * 24px + gaps) and comparing to estimated tile content width (accounts for columnSpan, minTileWidth, gap and ~32px padding), using a 66.6% threshold.
|
||||
- Change .cpu-core-bar-container to position: relative and .cpu-core-bar-fill to position: absolute (bottom:0; left:0; right:0) to ensure correct vertical alignment and smoother height transitions.
|
||||
|
||||
## 2026-01-12 - 3.35.0 - feat(dees-statsgrid)
|
||||
add cpuCores tile type with column spanning, rendering, demo and docs
|
||||
|
||||
- Introduce ICpuCore and coresData on IStatsTile to represent per-core usage
|
||||
- Implement renderCpuCores with responsive vertical bars, color thresholds (low/medium/high) and average usage header
|
||||
- Add columnSpan support for tiles and apply it to grid items (style grid-column: span N)
|
||||
- Add demo entries (8/16/32 core examples), Randomize grid action and updated demo layout
|
||||
- Document StatsGrid enhancements in readme.hints.md (usage examples and available tile types)
|
||||
- Bump devDependencies (@git.zone/tsbuild, @git.zone/tsbundle, @types/node), simplify build script to use tsbundle, and add @git.zone/tsbundle config in npmextra.json
|
||||
|
||||
## 2026-01-12 - 3.34.1 - fix(deps)
|
||||
move @design.estate/dees-wcctools from devDependencies to dependencies
|
||||
|
||||
- Promoted @design.estate/dees-wcctools@^3.7.1 to runtime dependencies so it will be installed in production builds.
|
||||
|
||||
## 2026-01-07 - 3.34.0 - feat(dees-input-toggle)
|
||||
Add DeesInputToggle component (toggle switch) with demo and exports; integrate into inputs and DeesForm
|
||||
|
||||
|
||||
9
license
@@ -1,4 +1,4 @@
|
||||
Copyright (c) 2020 Lossless GmbH (hello@lossless.com)
|
||||
Copyright (c) 2020 Task Venture Capital GmbH (hello@task.vc)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -8,10 +8,7 @@ copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software. You agree to being mentioned
|
||||
as reference by Lossless GmbH. This includes the use of your entity logos
|
||||
or profile picture by Lossless GmbH on websites and readme's, also on third party
|
||||
pages like gitlab.com or github.com.
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
@@ -19,4 +16,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
||||
41
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@design.estate/dees-catalog",
|
||||
"version": "3.34.0",
|
||||
"version": "3.78.0",
|
||||
"private": false,
|
||||
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
||||
"main": "dist_ts_web/index.js",
|
||||
@@ -8,20 +8,21 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "tstest test/ --web --verbose --timeout 30 --logfile",
|
||||
"build": "tsbuild tsfolders --allowimplicitany && tsbundle element --production --bundler esbuild",
|
||||
"watch": "tswatch element",
|
||||
"build": "tsbuild tsfolders --allowimplicitany && tsbundle",
|
||||
"watch": "tswatch",
|
||||
"buildDocs": "tsdoc",
|
||||
"postinstall": "node scripts/update-monaco-version.cjs"
|
||||
},
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@design.estate/dees-domtools": "^2.3.7",
|
||||
"@design.estate/dees-element": "^2.1.5",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.1.0",
|
||||
"@design.estate/dees-domtools": "^2.5.4",
|
||||
"@design.estate/dees-element": "^2.2.4",
|
||||
"@design.estate/dees-wcctools": "^3.8.4",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.2.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.2.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^7.2.0",
|
||||
"@push.rocks/smarti18n": "^1.0.4",
|
||||
"@push.rocks/smartpromise": "^4.2.0",
|
||||
"@push.rocks/smartstring": "^4.1.0",
|
||||
@@ -32,24 +33,24 @@
|
||||
"@tiptap/extension-typography": "^2.23.0",
|
||||
"@tiptap/extension-underline": "^2.23.0",
|
||||
"@tiptap/starter-kit": "^2.23.0",
|
||||
"@tsclass/tsclass": "^9.3.0",
|
||||
"apexcharts": "^5.3.6",
|
||||
"@tsclass/tsclass": "^9.5.0",
|
||||
"echarts": "^5.6.0",
|
||||
"highlight.js": "11.11.1",
|
||||
"ibantools": "^4.5.1",
|
||||
"lucide": "^0.562.0",
|
||||
"lightweight-charts": "^5.1.0",
|
||||
"lucide": "^1.8.0",
|
||||
"monaco-editor": "0.55.1",
|
||||
"pdfjs-dist": "^4.10.38",
|
||||
"xterm": "^5.3.0",
|
||||
"xterm-addon-fit": "^0.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@design.estate/dees-wcctools": "^3.7.1",
|
||||
"@git.zone/tsbuild": "^4.0.2",
|
||||
"@git.zone/tsbundle": "^2.6.3",
|
||||
"@git.zone/tstest": "^3.1.4",
|
||||
"@git.zone/tswatch": "^2.3.13",
|
||||
"@push.rocks/projectinfo": "^5.0.2",
|
||||
"@types/node": "^25.0.3"
|
||||
"@git.zone/tsbuild": "^4.4.0",
|
||||
"@git.zone/tsbundle": "^2.10.0",
|
||||
"@git.zone/tstest": "^3.6.3",
|
||||
"@git.zone/tswatch": "^3.3.2",
|
||||
"@push.rocks/projectinfo": "^5.1.0",
|
||||
"@types/node": "^25.6.0"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@@ -60,7 +61,7 @@
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
".smartconfig.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
|
||||
6639
pnpm-lock.yaml
generated
199
readme.hints.md
@@ -910,4 +910,201 @@ appui.getBottomBarVisible();
|
||||
### Files:
|
||||
- `ts_web/elements/00group-appui/dees-appui-bottombar/dees-appui-bottombar.ts` - Main component
|
||||
- `ts_web/elements/00group-appui/dees-appui-bottombar/dees-appui-bottombar.demo.ts` - Demo
|
||||
- `ts_web/elements/interfaces/appconfig.ts` - New interfaces added
|
||||
- `ts_web/elements/interfaces/appconfig.ts` - New interfaces added
|
||||
|
||||
## Media Components (2026-01-26)
|
||||
|
||||
New media viewer components and a unified preview composite component.
|
||||
|
||||
### Directory: `ts_web/elements/00group-media/`
|
||||
|
||||
#### dees-image-viewer
|
||||
- Image display with zoom, pan, fit, and download controls
|
||||
- Properties: `src`, `alt`, `fit` ('contain'|'cover'|'actual'), `showToolbar`
|
||||
- Features: mouse wheel zoom, click-drag pan, double-click toggle, checkerboard transparency background
|
||||
- Toolbar matches PDF viewer pattern (48px height, 32px buttons, 16px icons, 6px border-radius)
|
||||
|
||||
#### dees-audio-viewer
|
||||
- Audio player with waveform visualization via Web Audio API
|
||||
- Properties: `src`, `title`, `artist`, `showWaveform`, `autoplay`, `loop`
|
||||
- Features: canvas waveform rendering, play/pause, seek, volume control, mute toggle, loop toggle
|
||||
- Uses `HTMLAudioElement` for playback, `AudioContext.decodeAudioData` for waveform data
|
||||
|
||||
#### dees-video-viewer
|
||||
- Video player with custom overlay controls
|
||||
- Properties: `src`, `poster`, `showControls`, `autoplay`, `loop`, `muted`
|
||||
- Features: custom controls bar with gradient, seekbar, volume slider, fullscreen toggle, auto-hide controls, 16:9 aspect ratio
|
||||
|
||||
### dees-preview (Composite Component)
|
||||
- Auto-detects content type and delegates to the appropriate viewer
|
||||
- Directory: `ts_web/elements/dees-preview/`
|
||||
- Properties: `url`, `file` (File object), `base64`, `textContent`, `contentType` (override), `language`, `mimeType`, `filename`, `showToolbar`, `showFilename`
|
||||
- Content type detection priority: explicit override → MIME type → file extension → fallback
|
||||
- Renders: image→DeesImageViewer, pdf→DeesPdfViewer, code→DeesDataviewCodebox, audio→DeesAudioViewer, video→DeesVideoViewer, text→pre, unknown→placeholder
|
||||
- Header bar with file type icon, filename, and type badge
|
||||
|
||||
### dees-dataview-codebox modification
|
||||
- Removed `<dees-windowcontrols>` elements from the appbar (Step 1 of the plan)
|
||||
- Now shows clean centered filename title bar without fake window buttons
|
||||
|
||||
### Icon Sizing Convention
|
||||
- All `dees-icon` elements in buttons need explicit `font-size: 16px` CSS rule
|
||||
- Toolbar buttons: 32px × 32px, border-radius: 6px
|
||||
- Placeholder/error icons: `font-size: 32px`
|
||||
- Pattern: `.button-class dees-icon { font-size: 16px; }`
|
||||
|
||||
## Tile Component System (2026-01-27)
|
||||
|
||||
A family of 200×260px content preview cards with a shared abstract base class. All tiles support lazy loading (IntersectionObserver with 200px margin), hover lift effect, click events, loading/error states, and three sizes (small: 150×195, default: 200×260, large: 250×325).
|
||||
|
||||
### Architecture
|
||||
|
||||
- **DeesTileBase** (`dees-tile-shared/DeesTileBase.ts`) — Abstract base class extending DeesElement
|
||||
- Common properties: `clickable`, `loading`, `error`, `size`, `label`
|
||||
- IntersectionObserver lazy loading via `onBecameVisible()` hook
|
||||
- Click dispatch via `tile-click` CustomEvent (detail from `getTileClickDetail()`)
|
||||
- Subclasses implement `renderTileContent(): TemplateResult`
|
||||
|
||||
### Components
|
||||
|
||||
| Tag | Class | Description |
|
||||
|-----|-------|-------------|
|
||||
| `dees-tile-pdf` | `DeesTilePdf` | PDF page thumbnail with hover-to-browse pages. Canvas-rendered via PDF.js/CanvasPool. |
|
||||
| `dees-tile-image` | `DeesTileImage` | Image thumbnail with `object-fit: cover`, dimension detection on load |
|
||||
| `dees-tile-audio` | `DeesTileAudio` | Music icon + mini waveform (AudioContext decode), duration badge |
|
||||
| `dees-tile-video` | `DeesTileVideo` | Auto-captured first frame, duration badge, hover muted auto-preview |
|
||||
| `dees-tile-note` | `DeesTileNote` | First ~12 lines of text in monospace, gradient fade, optional language badge |
|
||||
| `dees-tile-folder` | `DeesTileFolder` | 2×2 grid of mini-previews (thumbnails or type icons), item count badge |
|
||||
|
||||
### Deprecations
|
||||
|
||||
- `dees-pdf-preview` → Use `dees-tile-pdf` instead. Old tag still works as a thin wrapper with console warning.
|
||||
- `dees-pdf` deprecation comment updated to reference `DeesTilePdf`.
|
||||
|
||||
### File Structure
|
||||
|
||||
All tile components live in `ts_web/elements/00group-media/dees-tile-*/`:
|
||||
- `component.ts` — Main component class
|
||||
- `demo.ts` — Demo function
|
||||
- `index.ts` — Re-export
|
||||
- `styles.ts` — (PDF tile only) Component-specific styles
|
||||
- Shared base: `dees-tile-shared/{DeesTileBase,styles,index}.ts`
|
||||
|
||||
### Interface: ITileFolderItem
|
||||
```typescript
|
||||
interface ITileFolderItem {
|
||||
type: 'pdf' | 'image' | 'audio' | 'video' | 'note' | 'folder' | 'unknown';
|
||||
thumbnailSrc?: string;
|
||||
name: string;
|
||||
}
|
||||
```
|
||||
|
||||
## StatsGrid Enhancements (2026-01-12)
|
||||
|
||||
### Column Spanning
|
||||
|
||||
Tiles can now span multiple columns using the `columnSpan` property. This is useful for wider visualizations like the CPU cores tile.
|
||||
|
||||
```typescript
|
||||
const tile: IStatsTile = {
|
||||
id: 'wide-tile',
|
||||
title: 'Wide Tile',
|
||||
value: 100,
|
||||
type: 'cpuCores',
|
||||
columnSpan: 2, // Spans 2 columns
|
||||
coresData: [...]
|
||||
};
|
||||
```
|
||||
|
||||
Note: On smaller screens where only 1 column fits, tiles will automatically fall back to single column width.
|
||||
|
||||
### CPU Cores Tile Type
|
||||
|
||||
New tile type `cpuCores` for visualizing multi-core CPU usage with vertical bars:
|
||||
|
||||
```typescript
|
||||
interface ICpuCore {
|
||||
id: string | number;
|
||||
usage: number; // 0-100
|
||||
label?: string;
|
||||
}
|
||||
|
||||
const cpuTile: IStatsTile = {
|
||||
id: 'cpu-cores',
|
||||
title: 'CPU Cores',
|
||||
value: 0, // Not used, avg is calculated from coresData
|
||||
type: 'cpuCores',
|
||||
icon: 'lucide:cpu',
|
||||
columnSpan: 2, // Recommended for 8+ cores
|
||||
coresData: [
|
||||
{ id: 0, usage: 45, label: '0' },
|
||||
{ id: 1, usage: 72, label: '1' },
|
||||
// ... more cores
|
||||
],
|
||||
description: 'Intel i7 - 8 cores'
|
||||
};
|
||||
```
|
||||
|
||||
Features:
|
||||
- Vertical bars showing individual core usage
|
||||
- Color-coded: green (<50%), yellow (50-80%), red (>80%)
|
||||
- Shows average usage in header
|
||||
- Core labels shown for 16 or fewer cores
|
||||
- Tooltips show exact usage per core
|
||||
- Responsive: bars flex to fill available width
|
||||
|
||||
### Available Tile Types:
|
||||
- `number` - Simple numeric display
|
||||
- `gauge` - Semi-circular gauge with thresholds
|
||||
- `percentage` - Progress bar (0-100%)
|
||||
- `trend` - Sparkline with recent data
|
||||
- `text` - Text value display
|
||||
- `multiPercentage` - Multiple progress bars
|
||||
- `cpuCores` - Vertical bar visualization for CPU cores
|
||||
|
||||
## Component Group Taxonomy (2026-01-27)
|
||||
|
||||
All components are organized into `00group-*` directories with 14 groups visible in the wcctools sidebar. The `demoGroups` property (plural, `string[]`) replaces the old `demoGroup` (singular). Components can belong to multiple groups.
|
||||
|
||||
### Group Directories
|
||||
| Directory | Group Name | Count |
|
||||
|-----------|-----------|-------|
|
||||
| `00group-appui` | App UI | 10 |
|
||||
| `00group-button` | Button | 3 |
|
||||
| `00group-chart` | Chart | 2 |
|
||||
| `00group-dataview` | Data View | 4 |
|
||||
| `00group-feedback` | Feedback | 6 |
|
||||
| `00group-form` | Form | 2 |
|
||||
| `00group-input` | Input | 18 |
|
||||
| `00group-layout` | Layout | 7 |
|
||||
| `00group-media` | Media | 14 (viewers + PDF + tiles) |
|
||||
| `00group-overlay` | Overlay | 4 |
|
||||
| `00group-simple` | Simple | 3 |
|
||||
| `00group-utility` | Utility | 5 |
|
||||
| `00group-workspace` | Workspace | 9 |
|
||||
| `00group-runtime` | (internal) | - |
|
||||
|
||||
### Multi-Group Components
|
||||
Some components appear in multiple groups via `demoGroups = ['Primary', 'Secondary']`:
|
||||
- `dees-chart-log`: Chart, Workspace
|
||||
- `dees-dataview-codebox`: Data View, Workspace
|
||||
- `dees-input-code`: Input, Workspace
|
||||
- `dees-input-wysiwyg`: Input, Workspace
|
||||
- `dees-form-submit`: Form, Button
|
||||
- `dees-preview`: Media, Data View
|
||||
- `dees-pdf*` / `dees-tile-pdf`: Media, PDF
|
||||
- `dees-stepper`: Layout, Form
|
||||
- `dees-label`: Layout, Input
|
||||
- `dees-toast`: Feedback, Overlay
|
||||
- `dees-actionbar`: Feedback, Overlay
|
||||
|
||||
### Import Conventions
|
||||
- Within same group: `import '../sibling-component/file.js'`
|
||||
- Cross-group (from depth-2): `import '../../00group-X/component/file.js'`
|
||||
- Shared utilities: `import '../../00plugins.js'`, `import '../../00theme.js'`, etc.
|
||||
|
||||
### Key Notes
|
||||
- The old `demoGroup` property (singular, string) is fully removed
|
||||
- All 79 components with demos use `demoGroups` (plural, string[])
|
||||
- `00group-pdf` no longer exists; PDF components are in `00group-media`
|
||||
- `dees-search` and `dees-tooltip` remain standalone (no demos)
|
||||
691
readme.md
@@ -1,6 +1,6 @@
|
||||
# @design.estate/dees-catalog
|
||||
|
||||
A comprehensive web components library built with TypeScript and LitElement, providing **80+ production-ready UI components** for building modern web applications with consistent design and behavior. 🚀
|
||||
A comprehensive web components library built with TypeScript and LitElement, providing **90+ production-ready UI components** for building modern web applications with consistent design and behavior. 🚀
|
||||
|
||||
[](https://www.typescriptlang.org/)
|
||||
[](https://lit.dev/)
|
||||
@@ -11,12 +11,15 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- 🎨 **Consistent Design System** - Beautiful, cohesive components following modern UI/UX principles
|
||||
- 🌙 **Dark/Light Theme Support** - All components automatically adapt to your theme
|
||||
- ⌨️ **Keyboard Accessible** - Full keyboard navigation and ARIA support
|
||||
- 📱 **Responsive** - Mobile-first design that works across all screen sizes
|
||||
- 🔧 **TypeScript-First** - Fully typed APIs with excellent IDE support
|
||||
- 🧩 **Modular** - Use only what you need, tree-shakeable architecture
|
||||
- 🎨 **Consistent Design System** — Beautiful, cohesive components following modern UI/UX principles
|
||||
- 🌙 **Dark/Light Theme Support** — All components automatically adapt to your theme
|
||||
- ⌨️ **Keyboard Accessible** — Full keyboard navigation and ARIA support
|
||||
- 📱 **Responsive** — Mobile-first design that works across all screen sizes
|
||||
- 🔧 **TypeScript-First** — Fully typed APIs with excellent IDE support
|
||||
- 🧩 **Modular** — Use only what you need, tree-shakeable architecture
|
||||
- 🏗️ **Full App Shell** — `dees-appui` provides a complete application framework with menus, routing, activity log, and bottom bar
|
||||
- 🎬 **Media Components** — Rich tile-based previews for PDFs, images, audio, video, notes, and folders
|
||||
- 💻 **IDE Workspace** — Full workspace component with Monaco editor, file tree, terminal, and diff viewer
|
||||
|
||||
## 📦 Installation
|
||||
|
||||
@@ -52,16 +55,17 @@ For developers working on this library, please refer to the [UI Components Playb
|
||||
|
||||
| Category | Components |
|
||||
|----------|------------|
|
||||
| **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols) |
|
||||
| **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputList`](#deesinputlist), [`DeesInputProfilepicture`](#deesinputprofilepicture), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesFormSubmit`](#deesformsubmit) |
|
||||
| **Layout** | [`DeesAppui`](#deesappui), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
|
||||
| **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination) |
|
||||
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartLog`](#deeschartlog) |
|
||||
| **Core UI** | [`DeesButton`](#deesbutton), [`DeesButtonExit`](#deesbuttonexit), [`DeesButtonGroup`](#deesbuttongroup), [`DeesBadge`](#deesbadge), [`DeesChips`](#deeschips), [`DeesHeading`](#deesheading), [`DeesHint`](#deeshint), [`DeesIcon`](#deesicon), [`DeesLabel`](#deeslabel), [`DeesPanel`](#deespanel), [`DeesSearchbar`](#deessearchbar), [`DeesSpinner`](#deesspinner), [`DeesToast`](#deestoast), [`DeesWindowcontrols`](#deeswindowcontrols), [`DeesActionbar`](#deesactionbar) |
|
||||
| **Forms** | [`DeesForm`](#deesform), [`DeesInputText`](#deesinputtext), [`DeesInputCheckbox`](#deesinputcheckbox), [`DeesInputDropdown`](#deesinputdropdown), [`DeesInputRadiogroup`](#deesinputradiogroup), [`DeesInputFileupload`](#deesinputfileupload), [`DeesInputIban`](#deesinputiban), [`DeesInputPhone`](#deesinputphone), [`DeesInputQuantitySelector`](#deesinputquantityselector), [`DeesInputMultitoggle`](#deesinputmultitoggle), [`DeesInputToggle`](#deesinputtoggle), [`DeesInputTags`](#deesinputtags), [`DeesInputTypelist`](#deesinputtypelist), [`DeesInputList`](#deesinputlist), [`DeesInputProfilepicture`](#deesinputprofilepicture), [`DeesInputRichtext`](#deesinputrichtext), [`DeesInputWysiwyg`](#deesinputwysiwyg), [`DeesInputDatepicker`](#deesinputdatepicker), [`DeesInputSearchselect`](#deesinputsearchselect), [`DeesInputCode`](#deesinputcode), [`DeesFormSubmit`](#deesformsubmit) |
|
||||
| **App Shell (Layout)** | [`DeesAppui`](#deesappui-️), [`DeesAppuiMainmenu`](#deesappuimainmenu), [`DeesAppuiSecondarymenu`](#deesappuisecondarymenu), [`DeesAppuiMaincontent`](#deesappuimaincontent), [`DeesAppuiAppbar`](#deesappuiappbar), [`DeesAppuiActivitylog`](#deesappuiactivitylog), [`DeesAppuiBottombar`](#deesappuibottombar), [`DeesAppuiProfiledropdown`](#deesappuiprofiledropdown), [`DeesAppuiTabs`](#deesappuitabs), [`DeesMobileNavigation`](#deesmobilenavigation), [`DeesDashboardGrid`](#deesdashboardgrid) |
|
||||
| **Data Display** | [`DeesTable`](#deestable), [`DeesDataviewCodebox`](#deesdataviewcodebox), [`DeesDataviewStatusobject`](#deesdataviewstatusobject), [`DeesPdf`](#deespdf), [`DeesStatsGrid`](#deesstatsgrid), [`DeesPagination`](#deespagination), [`DeesStorageBrowser`](#deesstorgebrowser) |
|
||||
| **Media & Thumbnails** | [`DeesThumbnailPdf`](#deesthumbnailpdf), [`DeesThumbnailImage`](#deesthumbnailimage), [`DeesThumbnailAudio`](#deesthumbnailaudio), [`DeesThumbnailVideo`](#deesthumbnalvideo), [`DeesThumbnailNote`](#deesthumbnailnote), [`DeesThumbnailFolder`](#deesthumbnailfolder), [`DeesPreview`](#deespreview), [`DeesPdfViewer`](#deespdfviewer), [`DeesImageViewer`](#deesimageviewer), [`DeesAudioViewer`](#deesaudioviewer), [`DeesVideoViewer`](#deesvideoviewer) |
|
||||
| **Visualization** | [`DeesChartArea`](#deeschartarea), [`DeesChartBar`](#deeschartbar), [`DeesChartDonut`](#deeschartdonut), [`DeesChartGauge`](#deeschartgauge), [`DeesChartRadar`](#deeschartradar), [`DeesChartLog`](#deeschartlog) |
|
||||
| **Dialogs & Overlays** | [`DeesModal`](#deesmodal), [`DeesContextmenu`](#deescontextmenu), [`DeesSpeechbubble`](#deesspeechbubble), [`DeesWindowlayer`](#deeswindowlayer) |
|
||||
| **Navigation** | [`DeesStepper`](#deesstepper), [`DeesProgressbar`](#deesprogressbar) |
|
||||
| **Development** | [`DeesEditor`](#deeseditor), [`DeesEditorMarkdown`](#deeseditormarkdown), [`DeesEditorMarkdownoutlet`](#deeseditormarkdownoutlet), [`DeesTerminal`](#deesterminal), [`DeesUpdater`](#deesupdater) |
|
||||
| **Theming** | [`DeesTheme`](#deestheme) |
|
||||
| **Auth & Utilities** | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) |
|
||||
| **Workspace / IDE** | [`DeesWorkspace`](#deesworkspace), [`DeesWorkspaceMonaco`](#deesworkspacemonaco), [`DeesWorkspaceDiffEditor`](#deesworkspacediffeditor), [`DeesWorkspaceFiletree`](#deesworkspacefiletree), [`DeesWorkspaceTerminal`](#deesworkspaceterminal), [`DeesWorkspaceTerminalPreview`](#deesworkspaceterminalpreview), [`DeesWorkspaceMarkdown`](#deesworkspacemarkdown), [`DeesWorkspaceMarkdownoutlet`](#deesworkspacemarkdownoutlet), [`DeesWorkspaceBottombar`](#deesworkspacebottombar) |
|
||||
| **Theming** | [`DeesTheme`](#deestheme), [`DeesUpdater`](#deesupdater) |
|
||||
| **Pre-built Templates** | [`DeesSimpleAppdash`](#deessimpleappdash), [`DeesSimpleLogin`](#deessimplelogin) |
|
||||
| **Shopping** | [`DeesShoppingProductcard`](#deesshoppingproductcard) |
|
||||
|
||||
---
|
||||
@@ -117,14 +121,14 @@ Interactive chips/tags with selection capabilities.
|
||||
Display icons from FontAwesome and Lucide icon libraries with library prefixes.
|
||||
|
||||
```typescript
|
||||
// FontAwesome icons - use 'fa:' prefix
|
||||
// FontAwesome icons — use 'fa:' prefix
|
||||
<dees-icon
|
||||
icon="fa:check" // FontAwesome icon with fa: prefix
|
||||
iconSize="24" // Size in pixels
|
||||
color="#22c55e" // Optional: custom color
|
||||
></dees-icon>
|
||||
|
||||
// Lucide icons - use 'lucide:' prefix
|
||||
// Lucide icons — use 'lucide:' prefix
|
||||
<dees-icon
|
||||
icon="lucide:menu" // Lucide icon with lucide: prefix
|
||||
iconSize="24" // Size in pixels
|
||||
@@ -134,19 +138,18 @@ Display icons from FontAwesome and Lucide icon libraries with library prefixes.
|
||||
|
||||
// Legacy API (deprecated but still supported)
|
||||
<dees-icon
|
||||
iconFA="check" // Without prefix - assumes FontAwesome
|
||||
iconFA="check" // Without prefix — assumes FontAwesome
|
||||
></dees-icon>
|
||||
```
|
||||
|
||||
#### `DeesLabel`
|
||||
Text label component with optional icon and status indicators.
|
||||
Text label component with optional required indicator and info tooltip. Used internally by all input components.
|
||||
|
||||
```typescript
|
||||
<dees-label
|
||||
text="Status" // Label text
|
||||
icon="info-circle" // Optional: icon name
|
||||
type="info" // Options: default, info, success, warning, error
|
||||
size="medium" // Options: small, medium, large
|
||||
.label=${'Email Address'} // Label text
|
||||
.required=${true} // Optional: shows red asterisk
|
||||
.infoText=${'We will never share your email'} // Optional: shows hover info icon with tooltip
|
||||
></dees-label>
|
||||
```
|
||||
|
||||
@@ -287,6 +290,18 @@ Window control buttons (minimize, maximize, close) for desktop-like applications
|
||||
></dees-windowcontrols>
|
||||
```
|
||||
|
||||
#### `DeesActionbar`
|
||||
Floating action bar for contextual actions.
|
||||
|
||||
```typescript
|
||||
<dees-actionbar
|
||||
.actions=${[
|
||||
{ icon: 'lucide:save', label: 'Save', action: () => handleSave() },
|
||||
{ icon: 'lucide:trash', label: 'Delete', action: () => handleDelete() }
|
||||
]}
|
||||
></dees-actionbar>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Form Components
|
||||
@@ -305,7 +320,7 @@ Container component for form elements with built-in validation and data handling
|
||||
```
|
||||
|
||||
#### `DeesInputText`
|
||||
Text input field with validation and formatting options.
|
||||
Text input field with validation, info tooltips, description text, and context menu (Cut/Copy/Paste/Select All).
|
||||
|
||||
```typescript
|
||||
<dees-input-text
|
||||
@@ -314,10 +329,20 @@ Text input field with validation and formatting options.
|
||||
value="initial@value.com" // Initial value
|
||||
required // Makes the field required
|
||||
disabled // Disables the input
|
||||
placeholder="Enter your email"
|
||||
.infoText=${'Hover icon tooltip text'} // Shows ⓘ icon on label with hover tooltip
|
||||
.description=${'Permanent help text below the input'} // Small text below the input
|
||||
.validationFunction=${(value) => { // Auto-validates on every keystroke
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (emailRegex.test(value)) {
|
||||
return { valid: true, message: 'Email is valid' };
|
||||
}
|
||||
return { valid: false, message: 'Please enter a valid email' };
|
||||
}}
|
||||
></dees-input-text>
|
||||
```
|
||||
|
||||
> 💡 **All input components** share these common properties from `DeesInputBase`: `key`, `label`, `required`, `disabled`, `infoText`, `description`, `layoutMode`, `labelPosition`.
|
||||
|
||||
#### `DeesInputCheckbox`
|
||||
Checkbox input component for boolean values.
|
||||
|
||||
@@ -331,6 +356,18 @@ Checkbox input component for boolean values.
|
||||
></dees-input-checkbox>
|
||||
```
|
||||
|
||||
#### `DeesInputToggle`
|
||||
Toggle switch component for boolean on/off states.
|
||||
|
||||
```typescript
|
||||
<dees-input-toggle
|
||||
key="darkMode"
|
||||
label="Enable Dark Mode"
|
||||
.value=${true}
|
||||
@change=${handleToggle}
|
||||
></dees-input-toggle>
|
||||
```
|
||||
|
||||
#### `DeesInputDropdown`
|
||||
Dropdown selection component with search and filtering capabilities.
|
||||
|
||||
@@ -658,6 +695,19 @@ Advanced block-based editor with slash commands and rich content blocks.
|
||||
- Collaborative editing ready
|
||||
- Extensible block types
|
||||
|
||||
#### `DeesInputCode`
|
||||
Code input component for editing source code with syntax highlighting.
|
||||
|
||||
```typescript
|
||||
<dees-input-code
|
||||
key="snippet"
|
||||
label="Code Snippet"
|
||||
.value=${codeString}
|
||||
language="typescript"
|
||||
@change=${handleCodeChange}
|
||||
></dees-input-code>
|
||||
```
|
||||
|
||||
#### `DeesFormSubmit`
|
||||
Submit button component specifically designed for `DeesForm`.
|
||||
|
||||
@@ -670,10 +720,10 @@ Submit button component specifically designed for `DeesForm`.
|
||||
|
||||
---
|
||||
|
||||
### Layout Components
|
||||
### App Shell (Layout) Components
|
||||
|
||||
#### `DeesAppui`
|
||||
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, and view management.
|
||||
#### `DeesAppui` 🏗️
|
||||
A comprehensive application shell component providing a complete UI framework with navigation, menus, activity logging, bottom bar, and view management.
|
||||
|
||||
> **Full API Documentation**: See [ts_web/elements/00group-appui/dees-appui/readme.md](./ts_web/elements/00group-appui/dees-appui/readme.md) for complete documentation including all programmatic APIs, view lifecycle hooks, and TypeScript interfaces.
|
||||
|
||||
@@ -690,7 +740,6 @@ class MyApp extends DeesElement {
|
||||
async firstUpdated() {
|
||||
this.appui = this.shadowRoot.querySelector('dees-appui');
|
||||
|
||||
// Configure with views and menu
|
||||
this.appui.configure({
|
||||
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
|
||||
views: [
|
||||
@@ -700,7 +749,13 @@ class MyApp extends DeesElement {
|
||||
mainMenu: {
|
||||
sections: [{ name: 'Main', views: ['dashboard', 'settings'] }]
|
||||
},
|
||||
defaultView: 'dashboard'
|
||||
defaultView: 'dashboard',
|
||||
bottomBar: {
|
||||
visible: true,
|
||||
widgets: [
|
||||
{ id: 'status', iconName: 'lucide:activity', label: 'Online', status: 'success' }
|
||||
]
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -710,43 +765,123 @@ class MyApp extends DeesElement {
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **Configure API**: Single `configure()` method for complete app setup
|
||||
- **View Management**: Automatic view caching, lazy loading, and lifecycle hooks
|
||||
- **Programmatic APIs**: Full control over AppBar, Main Menu, Secondary Menu, Content Tabs, and Activity Log
|
||||
- **View Lifecycle Hooks**: `onActivate()`, `onDeactivate()`, and `canDeactivate()` for view components
|
||||
- **Hash-based Routing**: Automatic URL synchronization with view navigation
|
||||
- **RxJS Observables**: `viewChanged$` and `viewLifecycle$` for reactive programming
|
||||
- **TypeScript-first**: Typed `IViewActivationContext` passed to views on activation
|
||||
- **Activity Log Toggle**: Built-in appbar button to show/hide activity panel with entry count badge
|
||||
**Architecture Overview:**
|
||||
|
||||
**Programmatic APIs include:**
|
||||
- `navigateToView(viewId, params?)` - Navigate between views
|
||||
- `setAppBarMenus()`, `setBreadcrumbs()`, `setUser()` - Control the app bar
|
||||
- `setMainMenu()`, `setMainMenuSelection()`, `setMainMenuBadge()` - Control main navigation
|
||||
- `setMainMenuCollapsed()`, `setMainMenuVisible()` - Control main menu visibility
|
||||
- `setSecondaryMenu()`, `setSecondaryMenuCollapsed()`, `setSecondaryMenuVisible()` - Control secondary menu
|
||||
- `setContentTabs()`, `setContentTabsVisible()` - Control view-specific UI
|
||||
- `activityLog.add()`, `activityLog.addMany()`, `activityLog.clear()` - Manage activity entries
|
||||
- `setActivityLogVisible()`, `toggleActivityLog()`, `getActivityLogVisible()` - Control activity panel
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ AppBar (dees-appui-appbar) │
|
||||
│ ├── Menus (File, Edit, View...) │
|
||||
│ ├── Breadcrumbs │
|
||||
│ ├── User Profile + Dropdown │
|
||||
│ └── Activity Log Toggle │
|
||||
├─────────────┬───────────────────────────────────┬───────────────────┤
|
||||
│ Main Menu │ Content Area │ Activity Log │
|
||||
│ (collapsed/ │ ├── Content Tabs │ (slide panel) │
|
||||
│ expanded) │ │ (closable, from tables/lists)│ │
|
||||
│ │ └── View Container │ │
|
||||
│ ┌─────────┐ │ └── Active View │ │
|
||||
│ │ 🏠 Home │ ├─────────────────────────────────┐ │ │
|
||||
│ │ 📁 Files│ │ Secondary Menu │ │ │
|
||||
│ │ ⚙ Set.. │ │ ├── Collapsible Groups │ │ │
|
||||
│ │ │ │ │ ├── Tabs / Actions │ │ │
|
||||
│ └─────────┘ │ │ ├── Filters / Links │ │ │
|
||||
│ │ │ └── Dividers / Headers │ │ │
|
||||
├─────────────┴──┴───────────────────────────────┴───────────────────┤
|
||||
│ Bottom Bar (dees-appui-bottombar) — 24px status bar │
|
||||
│ ├── Status widgets (left/right) │
|
||||
│ └── Action buttons (left/right) │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Configuration (`IAppConfig`):**
|
||||
|
||||
**View Visibility Control:**
|
||||
```typescript
|
||||
// In your view's onActivate hook
|
||||
onActivate(context: IViewActivationContext) {
|
||||
// Hide secondary menu for a fullscreen view
|
||||
context.appui.setSecondaryMenuVisible(false);
|
||||
|
||||
// Hide content tabs
|
||||
context.appui.setContentTabsVisible(false);
|
||||
|
||||
// Collapse main menu to give more space
|
||||
context.appui.setMainMenuCollapsed(true);
|
||||
interface IAppConfig {
|
||||
branding?: { logoIcon?: string; logoText?: string };
|
||||
appBar?: IAppBarConfig;
|
||||
views: IViewDefinition[];
|
||||
mainMenu?: IMainMenuConfig;
|
||||
defaultView?: string;
|
||||
activityLog?: IActivityLogConfig;
|
||||
bottomBar?: IBottomBarConfig;
|
||||
onViewChange?: (viewId: string, view: IViewDefinition) => void;
|
||||
onSearch?: (query: string) => void;
|
||||
}
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- 🔧 **Configure API** — Single `configure()` method for complete app setup
|
||||
- 📄 **View Management** — Automatic view caching, lazy loading, and lifecycle hooks (`onActivate`, `onDeactivate`, `canDeactivate`)
|
||||
- 🧭 **Hash-based Routing** — Automatic URL synchronization with view navigation and parameterized routes
|
||||
- 📊 **Activity Log** — Slide-out panel with stacked entries, date grouping, search, and filtering
|
||||
- 📌 **Bottom Status Bar** — Configurable widgets and actions with status colors and loading states
|
||||
- 🎯 **RxJS Observables** — `viewChanged$` and `viewLifecycle$` for reactive programming
|
||||
- 🏷️ **TypeScript-first** — Typed `IViewActivationContext` passed to views on activation
|
||||
|
||||
**Programmatic APIs:**
|
||||
|
||||
| Area | Methods |
|
||||
|------|---------|
|
||||
| **Navigation** | `navigateToView(viewId, params?)`, `getCurrentView()`, `getViewRegistry()` |
|
||||
| **App Bar** | `setAppBarMenus()`, `updateAppBarMenu()`, `setBreadcrumbs()`, `setUser()`, `setProfileMenuItems()`, `setSearchVisible()`, `onSearch()`, `setWindowControlsVisible()` |
|
||||
| **Main Menu** | `setMainMenu()`, `updateMainMenuGroup()`, `addMainMenuItem()`, `removeMainMenuItem()`, `setMainMenuSelection()`, `setMainMenuCollapsed()`, `setMainMenuVisible()`, `setMainMenuBadge()`, `clearMainMenuBadge()` |
|
||||
| **Secondary Menu** | `setSecondaryMenu()`, `updateSecondaryMenuGroup()`, `addSecondaryMenuItem()`, `setSecondaryMenuSelection()`, `setSecondaryMenuCollapsed()`, `setSecondaryMenuVisible()`, `clearSecondaryMenu()` |
|
||||
| **Content Tabs** | `setContentTabs()`, `addContentTab()`, `removeContentTab()`, `selectContentTab()`, `getSelectedContentTab()`, `setContentTabsVisible()`, `setContentTabsAutoHide()` |
|
||||
| **Activity Log** | `activityLog.add()`, `activityLog.addMany()`, `activityLog.clear()`, `activityLog.getEntries()`, `activityLog.filter()`, `activityLog.search()`, `setActivityLogVisible()`, `toggleActivityLog()`, `getActivityLogVisible()` |
|
||||
| **Bottom Bar** | `bottomBar.addWidget()`, `bottomBar.updateWidget()`, `bottomBar.removeWidget()`, `bottomBar.getWidget()`, `bottomBar.clearWidgets()`, `bottomBar.addAction()`, `bottomBar.removeAction()`, `bottomBar.clearActions()`, `setBottomBarVisible()`, `getBottomBarVisible()` |
|
||||
| **Observables** | `viewChanged$`, `viewLifecycle$` |
|
||||
|
||||
**View Lifecycle Hooks:**
|
||||
|
||||
```typescript
|
||||
import { DeesElement, customElement } from '@design.estate/dees-element';
|
||||
import type { IViewActivationContext, IViewLifecycle } from '@design.estate/dees-catalog';
|
||||
|
||||
@customElement('my-settings-view')
|
||||
class MySettingsView extends DeesElement implements IViewLifecycle {
|
||||
// Called when view becomes visible
|
||||
async onActivate(context: IViewActivationContext) {
|
||||
const { appui, viewId, params } = context;
|
||||
|
||||
// Set view-specific secondary menu
|
||||
appui.setSecondaryMenu({
|
||||
heading: 'Settings',
|
||||
groups: [{ name: 'Options', items: [...] }]
|
||||
});
|
||||
|
||||
// Control visibility of other shell parts
|
||||
appui.setContentTabsVisible(false);
|
||||
appui.setSecondaryMenuVisible(true);
|
||||
}
|
||||
|
||||
// Called when navigating away
|
||||
onDeactivate() { /* cleanup */ }
|
||||
|
||||
// Return false or a message string to block navigation
|
||||
canDeactivate(): boolean | string {
|
||||
if (this.hasUnsavedChanges) return 'You have unsaved changes. Leave anyway?';
|
||||
return true;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Secondary Menu Item Types:**
|
||||
|
||||
The secondary menu supports **8 distinct item types** for building rich contextual sidebars:
|
||||
|
||||
| Type | Description |
|
||||
|------|-------------|
|
||||
| **Tab** (default) | Selectable item that stays highlighted |
|
||||
| **Action** | Executes on click without staying selected (blue styling) |
|
||||
| **Filter** | Checkbox toggle for filtering |
|
||||
| **MultiFilter** | Collapsible multi-select filter box |
|
||||
| **Divider** | Visual separator line |
|
||||
| **Header** | Non-interactive section label |
|
||||
| **Link** | Opens an external URL |
|
||||
| **Danger Action** | Red-styled action with optional confirmation |
|
||||
|
||||
#### `DeesAppuiMainmenu`
|
||||
Main navigation menu component for application-wide navigation.
|
||||
Main navigation menu component for application-wide navigation. Supports collapsed (icon-only) mode.
|
||||
|
||||
```typescript
|
||||
<dees-appui-mainmenu
|
||||
@@ -759,12 +894,12 @@ Main navigation menu component for application-wide navigation.
|
||||
]
|
||||
}
|
||||
]}
|
||||
collapsed // Optional: show collapsed version
|
||||
collapsed // Optional: show collapsed icon-only version
|
||||
></dees-appui-mainmenu>
|
||||
```
|
||||
|
||||
#### `DeesAppuiSecondarymenu`
|
||||
Secondary navigation component for sub-section selection with collapsible groups and badges.
|
||||
Secondary navigation component for sub-section selection with collapsible groups, badges, and 8 item types.
|
||||
|
||||
```typescript
|
||||
<dees-appui-secondarymenu
|
||||
@@ -806,28 +941,12 @@ Professional application bar component with hierarchical menus, breadcrumb navig
|
||||
.menuItems=${[
|
||||
{
|
||||
name: 'File',
|
||||
action: async () => {}, // No-op for parent menu items
|
||||
action: async () => {},
|
||||
submenu: [
|
||||
{
|
||||
name: 'New File',
|
||||
shortcut: 'Cmd+N',
|
||||
iconName: 'file-plus',
|
||||
action: async () => handleNewFile()
|
||||
},
|
||||
{
|
||||
name: 'Open...',
|
||||
shortcut: 'Cmd+O',
|
||||
iconName: 'folder-open',
|
||||
action: async () => handleOpen()
|
||||
},
|
||||
{ divider: true }, // Menu separator
|
||||
{
|
||||
name: 'Save',
|
||||
shortcut: 'Cmd+S',
|
||||
iconName: 'save',
|
||||
action: async () => handleSave(),
|
||||
disabled: true // Disabled state
|
||||
}
|
||||
{ name: 'New File', shortcut: 'Cmd+N', iconName: 'file-plus', action: async () => handleNewFile() },
|
||||
{ name: 'Open...', shortcut: 'Cmd+O', iconName: 'folder-open', action: async () => handleOpen() },
|
||||
{ divider: true },
|
||||
{ name: 'Save', shortcut: 'Cmd+S', iconName: 'save', action: async () => handleSave(), disabled: true }
|
||||
]
|
||||
}
|
||||
]}
|
||||
@@ -849,12 +968,12 @@ Professional application bar component with hierarchical menus, breadcrumb navig
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- **Hierarchical Menu System** - Top-level menus with dropdown submenus, icons, and keyboard shortcuts
|
||||
- **Keyboard Navigation** - Full keyboard support (Tab, Arrow keys, Enter, Escape)
|
||||
- **Breadcrumb Navigation** - Customizable breadcrumb trail with click events
|
||||
- **User Account Section** - Avatar with status indicator and profile dropdown
|
||||
- **Activity Log Toggle** - Button with badge count to show/hide activity panel
|
||||
- **Accessibility** - Full ARIA support with menubar roles
|
||||
- **Hierarchical Menu System** — Top-level menus with dropdown submenus, icons, and keyboard shortcuts
|
||||
- **Keyboard Navigation** — Full keyboard support (Tab, Arrow keys, Enter, Escape)
|
||||
- **Breadcrumb Navigation** — Customizable breadcrumb trail with click events
|
||||
- **User Account Section** — Avatar with status indicator and profile dropdown
|
||||
- **Activity Log Toggle** — Button with badge count to show/hide activity panel
|
||||
- **Accessibility** — Full ARIA support with menubar roles
|
||||
|
||||
#### `DeesAppuiActivitylog`
|
||||
Real-time activity log panel for displaying user actions and system events.
|
||||
@@ -886,6 +1005,61 @@ activityLog.search('settings'); // Search by message
|
||||
- Animated slide-in/out panel
|
||||
- Theme-aware styling
|
||||
|
||||
#### `DeesAppuiBottombar`
|
||||
A 24px fixed-height status bar at the bottom of the application shell. Supports status widgets and action buttons positioned left or right.
|
||||
|
||||
```typescript
|
||||
// Configure via DeesAppui
|
||||
appui.configure({
|
||||
bottomBar: {
|
||||
visible: true,
|
||||
widgets: [
|
||||
{
|
||||
id: 'status',
|
||||
iconName: 'lucide:activity',
|
||||
label: 'System Online',
|
||||
status: 'success', // 'idle' | 'active' | 'success' | 'warning' | 'error'
|
||||
tooltip: 'All systems operational',
|
||||
onClick: () => console.log('Status clicked'),
|
||||
},
|
||||
{
|
||||
id: 'version',
|
||||
iconName: 'lucide:gitBranch',
|
||||
label: 'v1.2.3',
|
||||
position: 'right',
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
id: 'terminal',
|
||||
iconName: 'lucide:terminal',
|
||||
tooltip: 'Open Terminal',
|
||||
position: 'right',
|
||||
onClick: () => console.log('Terminal clicked'),
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// Programmatic updates
|
||||
appui.bottomBar.addWidget({ id: 'build', iconName: 'lucide:hammer', label: 'Building...', loading: true, status: 'active' });
|
||||
appui.bottomBar.updateWidget('build', { label: 'Build complete', loading: false, status: 'success' });
|
||||
appui.bottomBar.removeWidget('build');
|
||||
|
||||
appui.bottomBar.addAction({ id: 'refresh', iconName: 'lucide:refreshCw', onClick: () => location.reload() });
|
||||
appui.bottomBar.removeAction('refresh');
|
||||
|
||||
appui.setBottomBarVisible(false);
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Configurable status widgets with icons, labels, and colored status indicators
|
||||
- Loading spinner state for widgets
|
||||
- Contextual actions with icon buttons
|
||||
- Left/right positioning for both widgets and actions
|
||||
- Tooltips on hover
|
||||
- Context menu support per widget
|
||||
|
||||
#### `DeesAppuiTabs`
|
||||
Reusable tab component with horizontal/vertical layout support.
|
||||
|
||||
@@ -1007,7 +1181,7 @@ A responsive grid component for displaying statistical data with various visuali
|
||||
value: 125420,
|
||||
unit: '$',
|
||||
type: 'number',
|
||||
icon: 'faDollarSign',
|
||||
icon: 'lucide:dollarSign',
|
||||
description: '+12.5% from last month',
|
||||
color: '#22c55e'
|
||||
},
|
||||
@@ -1016,7 +1190,7 @@ A responsive grid component for displaying statistical data with various visuali
|
||||
title: 'CPU Usage',
|
||||
value: 73,
|
||||
type: 'gauge',
|
||||
icon: 'faMicrochip',
|
||||
icon: 'lucide:cpu',
|
||||
gaugeOptions: {
|
||||
min: 0, max: 100,
|
||||
thresholds: [
|
||||
@@ -1032,8 +1206,22 @@ A responsive grid component for displaying statistical data with various visuali
|
||||
value: '1.2k',
|
||||
unit: '/min',
|
||||
type: 'trend',
|
||||
icon: 'faServer',
|
||||
icon: 'lucide:server',
|
||||
trendData: [45, 52, 38, 65, 72, 68, 75, 82, 79, 85, 88, 92]
|
||||
},
|
||||
{
|
||||
id: 'cores',
|
||||
title: 'CPU Cores',
|
||||
value: 0,
|
||||
type: 'cpuCores',
|
||||
icon: 'lucide:cpu',
|
||||
columnSpan: 2,
|
||||
coresData: [
|
||||
{ id: 0, usage: 45, label: '0' },
|
||||
{ id: 1, usage: 72, label: '1' },
|
||||
{ id: 2, usage: 30, label: '2' },
|
||||
{ id: 3, usage: 88, label: '3' }
|
||||
]
|
||||
}
|
||||
]}
|
||||
.minTileWidth=${250}
|
||||
@@ -1041,6 +1229,8 @@ A responsive grid component for displaying statistical data with various visuali
|
||||
></dees-statsgrid>
|
||||
```
|
||||
|
||||
**Tile Types:** `number`, `gauge`, `percentage`, `trend`, `text`, `multiPercentage`, `cpuCores`
|
||||
|
||||
#### `DeesPagination`
|
||||
Pagination component for navigating through large datasets.
|
||||
|
||||
@@ -1056,6 +1246,146 @@ Pagination component for navigating through large datasets.
|
||||
|
||||
---
|
||||
|
||||
### Media & Tile Components 🎬
|
||||
|
||||
A rich collection of tile-based preview components for displaying media files in grids. All tiles share a consistent base class (`DeesTileBase`) with lazy loading via `IntersectionObserver`, hover interactions, click events, context menus, and three size variants (`small`, `default`, `large`).
|
||||
|
||||
All tile badges use a unified styling system with label-awareness — when a `label` is set, bottom badges automatically shift up to avoid overlapping.
|
||||
|
||||
#### `DeesTilePdf`
|
||||
PDF document tile with live page preview on hover.
|
||||
|
||||
```typescript
|
||||
<dees-tile-pdf
|
||||
pdfUrl="/documents/report.pdf"
|
||||
label="Annual Report"
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-pdf>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Renders first page as canvas preview
|
||||
- Hover to scrub through pages (mouse X position maps to page number)
|
||||
- Shows page count badge, hover page indicator
|
||||
- Detects A4/Letter vs non-standard aspect ratios
|
||||
|
||||
#### `DeesTileImage`
|
||||
Image tile with lazy loading and dimension display.
|
||||
|
||||
```typescript
|
||||
<dees-tile-image
|
||||
src="/photos/landscape.jpg"
|
||||
alt="Mountain landscape"
|
||||
label="landscape.jpg"
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-image>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Lazy loads image on scroll into view
|
||||
- Shows image dimensions on hover (e.g. "1920 × 1080")
|
||||
- Checkerboard background for transparent images
|
||||
|
||||
#### `DeesTileAudio`
|
||||
Audio file tile with waveform visualization.
|
||||
|
||||
```typescript
|
||||
<dees-tile-audio
|
||||
src="/music/track.mp3"
|
||||
title="Summer Vibes"
|
||||
artist="DJ Example"
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-audio>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Generates waveform visualization from audio data
|
||||
- Shows duration badge (e.g. "3:42")
|
||||
- Displays title and artist metadata
|
||||
- Play overlay on hover
|
||||
|
||||
#### `DeesTileVideo`
|
||||
Video tile with thumbnail capture and hover preview.
|
||||
|
||||
```typescript
|
||||
<dees-tile-video
|
||||
src="/videos/intro.mp4"
|
||||
poster="/thumbs/intro.jpg"
|
||||
label="Introduction"
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-video>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Auto-captures first frame as thumbnail (or uses provided `poster`)
|
||||
- Plays video preview on hover
|
||||
- Shows duration badge
|
||||
- Play button overlay
|
||||
|
||||
#### `DeesTileNote`
|
||||
Code/text snippet tile with syntax-aware display.
|
||||
|
||||
```typescript
|
||||
<dees-tile-note
|
||||
title="config.ts"
|
||||
language="TypeScript"
|
||||
.content=${codeString}
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-note>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- Monospace font with line numbers
|
||||
- Language badge (top-right)
|
||||
- Scrollable content on hover (mouse X position controls scroll)
|
||||
- Line indicator badge while scrolling
|
||||
- Gradient fade at bottom
|
||||
|
||||
#### `DeesTileFolder`
|
||||
Folder tile with 2×2 content preview grid.
|
||||
|
||||
```typescript
|
||||
<dees-tile-folder
|
||||
name="Project Assets"
|
||||
.items=${[
|
||||
{ type: 'image', name: 'logo.png', thumbnailSrc: '/thumbs/logo.png' },
|
||||
{ type: 'pdf', name: 'spec.pdf' },
|
||||
{ type: 'audio', name: 'jingle.mp3' },
|
||||
{ type: 'video', name: 'demo.mp4' },
|
||||
]}
|
||||
clickable
|
||||
@tile-click=${handleClick}
|
||||
></dees-tile-folder>
|
||||
```
|
||||
|
||||
**Key Features:**
|
||||
- 2×2 preview grid showing first 4 items (thumbnails or type icons)
|
||||
- Item count badge (e.g. "12 items")
|
||||
- Folder icon header with name
|
||||
- Supports: `pdf`, `image`, `audio`, `video`, `note`, `folder`, `unknown` types
|
||||
|
||||
#### `DeesPreview`
|
||||
Unified preview component that auto-selects the right tile type based on content.
|
||||
|
||||
#### `DeesPdfViewer` / `DeesPdfPreview`
|
||||
Full PDF viewing components with navigation controls.
|
||||
|
||||
#### `DeesImageViewer`
|
||||
Full-screen image viewer with zoom and pan.
|
||||
|
||||
#### `DeesAudioViewer`
|
||||
Audio playback component with waveform and controls.
|
||||
|
||||
#### `DeesVideoViewer`
|
||||
Video playback component with standard controls.
|
||||
|
||||
---
|
||||
|
||||
### Visualization Components
|
||||
|
||||
#### `DeesChartArea`
|
||||
@@ -1116,16 +1446,41 @@ DeesModal.createAndShow({
|
||||
```
|
||||
|
||||
#### `DeesContextmenu`
|
||||
Context menu component for right-click actions.
|
||||
Context menu component for right-click actions with nested submenu support.
|
||||
|
||||
```typescript
|
||||
<dees-contextmenu
|
||||
.items=${[
|
||||
{ label: 'Edit', icon: 'edit', action: () => handleEdit() },
|
||||
{ label: 'Delete', icon: 'trash', action: () => handleDelete() }
|
||||
]}
|
||||
position="right"
|
||||
></dees-contextmenu>
|
||||
// Programmatic usage
|
||||
DeesContextmenu.openContextMenuWithOptions(mouseEvent, [
|
||||
{
|
||||
name: 'Edit',
|
||||
iconName: 'lucide:edit',
|
||||
action: async () => handleEdit()
|
||||
},
|
||||
{ divider: true },
|
||||
{
|
||||
name: 'More Options',
|
||||
iconName: 'lucide:moreHorizontal',
|
||||
submenu: [
|
||||
{ name: 'Duplicate', iconName: 'lucide:copy', action: async () => handleDuplicate() },
|
||||
{ name: 'Archive', iconName: 'lucide:archive', action: async () => handleArchive() },
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
iconName: 'lucide:trash2',
|
||||
action: async () => handleDelete()
|
||||
}
|
||||
]);
|
||||
|
||||
// Component-based (implement getContextMenuItems on any element)
|
||||
class MyComponent extends DeesElement {
|
||||
getContextMenuItems() {
|
||||
return [
|
||||
{ name: 'View Details', iconName: 'lucide:eye', action: async () => { ... } },
|
||||
{ name: 'Edit', iconName: 'lucide:edit', action: async () => { ... } },
|
||||
];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `DeesSpeechbubble`
|
||||
@@ -1189,7 +1544,7 @@ Progress indicator component for tracking completion status.
|
||||
Theme provider component that wraps children and provides CSS custom properties for consistent theming.
|
||||
|
||||
```typescript
|
||||
// Basic usage - wrap your app
|
||||
// Basic usage — wrap your app
|
||||
<dees-theme>
|
||||
<my-app></my-app>
|
||||
</dees-theme>
|
||||
@@ -1217,76 +1572,71 @@ Theme provider component that wraps children and provides CSS custom properties
|
||||
|
||||
---
|
||||
|
||||
### Development Components
|
||||
### Workspace / IDE Components 💻
|
||||
|
||||
#### `DeesEditor`
|
||||
Code editor component with syntax highlighting and code completion, powered by Monaco Editor.
|
||||
A full-featured IDE workspace component suite for building browser-based code editors, terminal interfaces, and documentation viewers.
|
||||
|
||||
#### `DeesWorkspace`
|
||||
Top-level workspace shell that composes editor, file tree, terminal, and bottom bar into an IDE-like layout.
|
||||
|
||||
```typescript
|
||||
<dees-editor
|
||||
<dees-workspace></dees-workspace>
|
||||
```
|
||||
|
||||
#### `DeesWorkspaceMonaco`
|
||||
Monaco Editor integration for code editing with full IntelliSense, syntax highlighting, and language support.
|
||||
|
||||
```typescript
|
||||
<dees-workspace-monaco
|
||||
.value=${code}
|
||||
@change=${handleCodeChange}
|
||||
.language=${'typescript'}
|
||||
.theme=${'vs-dark'}
|
||||
.options=${{
|
||||
lineNumbers: true,
|
||||
minimap: { enabled: true },
|
||||
autoClosingBrackets: 'always'
|
||||
}}
|
||||
></dees-editor>
|
||||
@change=${handleCodeChange}
|
||||
></dees-workspace-monaco>
|
||||
```
|
||||
|
||||
#### `DeesEditorMarkdown`
|
||||
Markdown editor component with live preview.
|
||||
#### `DeesWorkspaceDiffEditor`
|
||||
Side-by-side diff editor powered by Monaco for comparing file versions.
|
||||
|
||||
```typescript
|
||||
<dees-editor-markdown
|
||||
.value=${markdown}
|
||||
@change=${handleMarkdownChange}
|
||||
.options=${{ preview: true, toolbar: true, spellcheck: true }}
|
||||
></dees-editor-markdown>
|
||||
<dees-workspace-diff-editor
|
||||
.originalValue=${originalCode}
|
||||
.modifiedValue=${modifiedCode}
|
||||
.language=${'typescript'}
|
||||
></dees-workspace-diff-editor>
|
||||
```
|
||||
|
||||
#### `DeesEditorMarkdownoutlet`
|
||||
Markdown preview component for rendering markdown content.
|
||||
#### `DeesWorkspaceFiletree`
|
||||
File tree navigation component with expand/collapse, file icons, and selection.
|
||||
|
||||
```typescript
|
||||
<dees-editor-markdownoutlet
|
||||
.markdown=${markdownContent}
|
||||
.theme=${'github'}
|
||||
allowHtml={false}
|
||||
></dees-editor-markdownoutlet>
|
||||
<dees-workspace-filetree
|
||||
.files=${fileTreeData}
|
||||
@file-select=${handleFileSelect}
|
||||
></dees-workspace-filetree>
|
||||
```
|
||||
|
||||
#### `DeesTerminal`
|
||||
Terminal emulator component for command-line interface.
|
||||
#### `DeesWorkspaceTerminal`
|
||||
Terminal emulator component powered by xterm.js.
|
||||
|
||||
```typescript
|
||||
<dees-terminal
|
||||
.commands=${{
|
||||
'echo': (args) => `Echo: ${args.join(' ')}`,
|
||||
'help': () => 'Available commands: echo, help'
|
||||
}}
|
||||
.prompt=${'$'}
|
||||
.welcomeMessage=${'Welcome! Type "help" for available commands.'}
|
||||
></dees-terminal>
|
||||
<dees-workspace-terminal></dees-workspace-terminal>
|
||||
```
|
||||
|
||||
#### `DeesUpdater`
|
||||
Component for managing application updates and version control.
|
||||
#### `DeesWorkspaceTerminalPreview`
|
||||
Terminal with integrated preview pane for output visualization.
|
||||
|
||||
```typescript
|
||||
<dees-updater
|
||||
.currentVersion=${'1.5.2'}
|
||||
.checkInterval=${3600000}
|
||||
.autoUpdate=${false}
|
||||
@update-available=${handleUpdateAvailable}
|
||||
></dees-updater>
|
||||
```
|
||||
#### `DeesWorkspaceMarkdown`
|
||||
Markdown editor with live preview.
|
||||
|
||||
#### `DeesWorkspaceMarkdownoutlet`
|
||||
Read-only markdown renderer for documentation display.
|
||||
|
||||
#### `DeesWorkspaceBottombar`
|
||||
IDE-style bottom status bar for the workspace.
|
||||
|
||||
---
|
||||
|
||||
### Auth & Utilities Components
|
||||
### Pre-built Templates
|
||||
|
||||
#### `DeesSimpleAppdash`
|
||||
Simple application dashboard component for quick prototyping.
|
||||
@@ -1360,6 +1710,8 @@ interface IMenuItem {
|
||||
action: () => void;
|
||||
badge?: string | number;
|
||||
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||
closeable?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
// Menu group interface for organized menus
|
||||
@@ -1375,11 +1727,13 @@ interface IViewDefinition {
|
||||
id: string;
|
||||
name: string;
|
||||
iconName?: string;
|
||||
content: string | HTMLElement | (() => TemplateResult);
|
||||
content: string | (new () => HTMLElement) | (() => TemplateResult) | (() => Promise<any>);
|
||||
secondaryMenu?: ISecondaryMenuGroup[];
|
||||
contentTabs?: IMenuItem[];
|
||||
route?: string;
|
||||
badge?: string | number;
|
||||
badgeVariant?: 'default' | 'success' | 'warning' | 'error';
|
||||
cache?: boolean;
|
||||
}
|
||||
|
||||
// Activity log entry
|
||||
@@ -1392,6 +1746,43 @@ interface IActivityEntry {
|
||||
iconName?: string;
|
||||
data?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// Bottom bar widget
|
||||
interface IBottomBarWidget {
|
||||
id: string;
|
||||
iconName?: string;
|
||||
label?: string;
|
||||
status?: 'idle' | 'active' | 'success' | 'warning' | 'error';
|
||||
tooltip?: string;
|
||||
loading?: boolean;
|
||||
onClick?: () => void;
|
||||
position?: 'left' | 'right';
|
||||
order?: number;
|
||||
}
|
||||
|
||||
// Bottom bar action button
|
||||
interface IBottomBarAction {
|
||||
id: string;
|
||||
iconName: string;
|
||||
tooltip?: string;
|
||||
onClick: () => void | Promise<void>;
|
||||
disabled?: boolean;
|
||||
position?: 'left' | 'right';
|
||||
}
|
||||
|
||||
// View activation context (passed to onActivate)
|
||||
interface IViewActivationContext {
|
||||
appui: DeesAppui;
|
||||
viewId: string;
|
||||
params?: Record<string, string>;
|
||||
}
|
||||
|
||||
// Tile folder item (for DeesTileFolder)
|
||||
interface ITileFolderItem {
|
||||
type: 'pdf' | 'image' | 'audio' | 'video' | 'note' | 'folder' | 'unknown';
|
||||
thumbnailSrc?: string;
|
||||
name: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
@@ -1410,7 +1801,7 @@ Use of these trademarks must comply with Task Venture Capital GmbH's Trademark G
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Task Venture Capital GmbH
|
||||
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||
|
||||
352
readme.plan.md
Normal file
@@ -0,0 +1,352 @@
|
||||
# Plan: dees-stepper — adopt dees-tile + optional overlay window layer
|
||||
|
||||
> First line (per CLAUDE.md): Please reread `/home/philkunz/.claude/CLAUDE.md` before continuing.
|
||||
|
||||
## Context
|
||||
|
||||
Today `dees-stepper` is an inline-only layout component: it hard-codes each step as a custom `.step` `<div>` with its own border / background / box-shadow / border-radius, and its `:host` is `position: absolute; width: 100%; height: 100%;` so it can only live inside a bounded parent container.
|
||||
|
||||
The user wants it to behave more like `dees-modal`:
|
||||
|
||||
1. Each step should be wrapped in a `<dees-tile>` — the unified "rounded on rounded" frame used by modals and panels — rather than a bespoke `.step` div.
|
||||
2. A `DeesWindowLayer` should be added behind the stepper, the same way `DeesModal.createAndShow` does, so the stepper can appear as an overlay on top of the page.
|
||||
|
||||
User has confirmed (via AskUserQuestion in this session):
|
||||
- **API**: keep the current inline usage working AND add a static `createAndShow()` like dees-modal.
|
||||
- **Layout**: keep the current vertical stack + SweetScroll behavior inside the overlay (don't switch to single-tile swap).
|
||||
- **Nav placement**: split header/footer — goBack + step counter go into the `dees-tile` header slot; the title stays in the content area; the tile footer is used for optional next/submit buttons supplied per-step.
|
||||
|
||||
No external consumers of `dees-stepper` were found inside this package (`grep dees-stepper|DeesStepper` only matches its own source, demo, index, changelog, readme). External consumers in dependent projects may exist — the refactor is kept backward-compatible for the inline path.
|
||||
|
||||
## Current state (reference)
|
||||
|
||||
**File:** `ts_web/elements/00group-layout/dees-stepper/dees-stepper.ts` (lines 20–299)
|
||||
|
||||
- `IStep` interface — `title`, `content: TemplateResult`, `validationFunc`, `onReturnToStepFunc`, internal flags (lines 20–27).
|
||||
- `:host { position: absolute; width: 100%; height: 100%; }` (lines 59–63).
|
||||
- `.stepperContainer` — absolute, 100% w/h, `overflow: hidden`, holds SweetScroll (lines 64–69).
|
||||
- `.step` — max-width 500, min-height 300, `border-radius: 12px`, theme background, theme border, `box-shadow: 0 8px 32px rgba(0,0,0,0.4)`, `filter: opacity(0.55) saturate(0.85)`, transform transition (lines 71–97). **These frame styles overlap with what `dees-tile` already provides.**
|
||||
- `.step.selected` — `filter: opacity(1) saturate(1)` (lines 89–93). **Scroll-through visual cue, keep.**
|
||||
- `.step.hiddenStep` — `filter: opacity(0)` (line 95). **Keep.**
|
||||
- `.step.entrance` — faster transition variant for first-render (lines 99–105). **Keep.**
|
||||
- `.step .stepCounter` — `position: absolute; top: 12px; right: 12px;` pill (lines 111–121). **Move into header slot as a flex child.**
|
||||
- `.step .goBack` — `position: absolute; top: 12px; left: 12px;` pill + icon + hover (lines 123–161). **Move into header slot as a flex child.**
|
||||
- `.step .title` — centered, 24px, 64px top padding (lines 163–171). **Keep inside the tile's content slot; remove the 64px top padding since goBack/counter no longer overlap it.**
|
||||
- `.step .content` — 32px padding (lines 173–175). **Keep.**
|
||||
- `render()` (lines 179–204) — maps `steps` to `.step` divs.
|
||||
- `setScrollStatus()` (lines 226–263) — SweetScroll container setup + step validation kick-off. **Keep mostly as-is; selectors still target `.step`/`.selected` so rename cautiously.**
|
||||
- `firstUpdated` (lines 210–218), `updated` (lines 220–222), `goBack` (lines 265–282), `goNext` (lines 284–298) — untouched in behavior, only DOM selectors may need adjusting.
|
||||
|
||||
**Reference files (read, do not modify):**
|
||||
- `ts_web/elements/00group-overlay/dees-modal/dees-modal.ts` — canonical `createAndShow` + `destroy` + window-layer coordination + z-index registry usage.
|
||||
- `ts_web/elements/00group-layout/dees-tile/dees-tile.ts` — slot API: `slot="header"`, default slot, `slot="footer"`. Auto-hides footer when slotted nodes are empty. Uses `part="outer"`, `part="header"`, `part="content"`, `part="footer"` for external shadow-part styling.
|
||||
- `ts_web/elements/00group-overlay/dees-windowlayer/dees-windowlayer.ts` — `createAndShow({ blur })`, `destroy()`, dispatches `clicked` event on backdrop click, uses `zIndexRegistry`.
|
||||
- `ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts` — existing inline demo.
|
||||
|
||||
## Target state
|
||||
|
||||
### 1. IStep interface — add one optional field
|
||||
|
||||
```ts
|
||||
export interface IStep {
|
||||
title: string;
|
||||
content: TemplateResult;
|
||||
footerContent?: TemplateResult; // NEW: optional, rendered in dees-tile footer slot
|
||||
validationFunc?: (stepper: DeesStepper, htmlElement: HTMLElement, signal?: AbortSignal) => Promise<any>;
|
||||
onReturnToStepFunc?: (stepper: DeesStepper, htmlElement: HTMLElement) => Promise<any>;
|
||||
validationFuncCalled?: boolean;
|
||||
abortController?: AbortController;
|
||||
}
|
||||
```
|
||||
|
||||
Form-based steps don't need `footerContent` — their `dees-form-submit` stays inside the form in the content slot (as today). `footerContent` is for non-form steps that need an explicit primary action, or for any step that wants buttons in the conventional tile footer location.
|
||||
|
||||
### 2. New overlay-mode state + API on DeesStepper
|
||||
|
||||
```ts
|
||||
@state() accessor overlay: boolean = false;
|
||||
@state() accessor stepperZIndex: number = 1000;
|
||||
private windowLayer?: DeesWindowLayer;
|
||||
|
||||
public static async createAndShow(optionsArg: {
|
||||
steps: IStep[];
|
||||
}): Promise<DeesStepper> {
|
||||
const body = document.body;
|
||||
const stepper = new DeesStepper();
|
||||
stepper.steps = optionsArg.steps;
|
||||
stepper.overlay = true;
|
||||
stepper.windowLayer = await DeesWindowLayer.createAndShow({ blur: true });
|
||||
stepper.windowLayer.addEventListener('click', async () => {
|
||||
await stepper.destroy();
|
||||
});
|
||||
body.append(stepper.windowLayer); // (already appended inside createAndShow, but mirror dees-modal's pattern; see note)
|
||||
body.append(stepper);
|
||||
stepper.stepperZIndex = zIndexRegistry.getNextZIndex();
|
||||
zIndexRegistry.register(stepper, stepper.stepperZIndex);
|
||||
return stepper;
|
||||
}
|
||||
|
||||
public async destroy() {
|
||||
const domtools = await this.domtoolsPromise;
|
||||
const container = this.shadowRoot!.querySelector('.stepperContainer');
|
||||
container?.classList.add('predestroy');
|
||||
await domtools.convenience.smartdelay.delayFor(200);
|
||||
if (this.parentElement) this.parentElement.removeChild(this);
|
||||
if (this.windowLayer) await this.windowLayer.destroy();
|
||||
zIndexRegistry.unregister(this);
|
||||
}
|
||||
```
|
||||
|
||||
**Note on `body.append(windowLayer)`:** `DeesWindowLayer.createAndShow` already appends the window layer to `document.body` (line 27 of `dees-windowlayer.ts`). `dees-modal.ts:71` still calls `body.append(modal.windowLayer)` — that's either a no-op (already-attached nodes) or a re-parent to keep ordering. I will match dees-modal's exact sequence verbatim to avoid introducing subtle differences; if it's a bug in dees-modal it is out of scope for this task.
|
||||
|
||||
**Minimum new scope for createAndShow:** just `steps` for now. No `onComplete`, no `showCloseButton`, no width options. Future-proofing via additional options is an explicit follow-up — this plan keeps scope razor-sharp (per CLAUDE.md). The caller can already wire completion via the last step's `validationFunc` calling back into their own code.
|
||||
|
||||
### 3. Render template — wrap each step in `<dees-tile>`
|
||||
|
||||
```ts
|
||||
public render() {
|
||||
return html`
|
||||
<div class="stepperContainer ${this.overlay ? 'overlay' : ''}" style="${this.overlay ? `z-index: ${this.stepperZIndex}` : ''}">
|
||||
${this.steps.map((stepArg, i) => {
|
||||
const isSelected = stepArg === this.selectedStep;
|
||||
const isHidden = this.getIndexOfStep(stepArg) > this.getIndexOfStep(this.selectedStep);
|
||||
const isFirst = i === 0;
|
||||
const stepNumber = i + 1;
|
||||
return html`
|
||||
<dees-tile
|
||||
class="step ${isSelected ? 'selected' : ''} ${isHidden ? 'hiddenStep' : ''} ${isFirst ? 'entrance' : ''}"
|
||||
>
|
||||
<div slot="header" class="step-header">
|
||||
${!isFirst
|
||||
? html`<div class="goBack" @click=${this.goBack}>
|
||||
<span>←</span> go to previous step
|
||||
</div>`
|
||||
: html`<div class="goBack-spacer"></div>`}
|
||||
<div class="stepCounter">Step ${stepNumber} of ${this.steps.length}</div>
|
||||
</div>
|
||||
<div class="step-body">
|
||||
<div class="title">${stepArg.title}</div>
|
||||
<div class="content">${stepArg.content}</div>
|
||||
</div>
|
||||
${stepArg.footerContent
|
||||
? html`<div slot="footer" class="step-footer">${stepArg.footerContent}</div>`
|
||||
: ''}
|
||||
</dees-tile>
|
||||
`;
|
||||
})}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
```
|
||||
|
||||
**Key detail:** on the first step, render a `.goBack-spacer` (empty div) in the header instead of nothing — so the `stepCounter` stays right-aligned via `justify-content: space-between`. Without a spacer, flex would left-align the counter on step 1.
|
||||
|
||||
### 4. CSS changes
|
||||
|
||||
**Remove from `.step`:**
|
||||
- `border-radius: 12px;`
|
||||
- `background: ${cssManager.bdTheme(...)};`
|
||||
- `border: 1px solid ${cssManager.bdTheme(...)};`
|
||||
- `color: ${cssManager.bdTheme(...)};`
|
||||
- `box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);`
|
||||
- `overflow: hidden;`
|
||||
|
||||
**Why:** `dees-tile` owns all of these now. The `.step` selector still exists (since `dees-tile` has `class="step ..."` on it), but it only controls the outer animation wrapper: `max-width`, `min-height`, `margin`, `filter`, `transform`, `transition`, `user-select`, `pointer-events`.
|
||||
|
||||
**Keep on `.step`:**
|
||||
- `position: relative;`
|
||||
- `pointer-events: none;` + `.step.selected { pointer-events: all; }`
|
||||
- `max-width: 500px;` / `min-height: 300px;`
|
||||
- `margin: auto; margin-bottom: 20px;`
|
||||
- `filter: opacity(0.55) saturate(0.85);` + `.selected { filter: opacity(1) saturate(1); }`
|
||||
- `.hiddenStep { filter: opacity(0); }`
|
||||
- All the cubic-bezier transitions (transform/filter/box-shadow — but box-shadow is now a no-op since dees-tile provides the shadow; leave the transition spec in so we don't have to re-check browser parsing; or just drop `box-shadow` from the transition list — I'll drop it for cleanliness).
|
||||
- `.step.entrance` + `.step.entrance.hiddenStep { transform: translateY(16px); }`
|
||||
- `.step:last-child { margin-bottom: 100vh; }`
|
||||
|
||||
**Add for dees-tile shadow enhancement:** use `::part(outer)` to apply the modal-style elevated shadow only when in overlay mode (optional polish — inline mode stays flat):
|
||||
```css
|
||||
.stepperContainer.overlay dees-tile.step::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)')};
|
||||
}
|
||||
```
|
||||
This exactly mirrors the dees-modal::part(outer) shadow stack (dees-modal.ts:157–161) so the overlay stepper reads as "same visual language as modal."
|
||||
|
||||
**Restyle `.step-header` (NEW — the `<div slot="header">`):**
|
||||
```css
|
||||
.step-header {
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 8px 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
```
|
||||
|
||||
**Restyle `.step .stepCounter` → `.step-header .stepCounter` (move from absolute to flex child):**
|
||||
- Drop `position: absolute; top: 12px; right: 12px;`
|
||||
- Keep everything else (padding, font-size, border-radius, background, border).
|
||||
|
||||
**Restyle `.step .goBack` → `.step-header .goBack` (move from absolute to flex child):**
|
||||
- Drop `position: absolute; top: 12px; left: 12px;`
|
||||
- Keep everything else (padding, font-size, border-radius, background, border, hover/active states).
|
||||
|
||||
**Add `.goBack-spacer`:**
|
||||
```css
|
||||
.goBack-spacer { width: 1px; } /* placeholder so flex space-between works on step 1 */
|
||||
```
|
||||
|
||||
**Restyle `.step .title`:**
|
||||
- Drop `padding-top: 64px;` — no longer overlaps anything since header is in its own slot.
|
||||
- Keep `text-align: center; font-family: 'Geist Sans', sans-serif; font-size: 24px; font-weight: 600; letter-spacing: -0.01em; color: inherit;`
|
||||
- Add `padding-top: 32px;` (or similar) so there's consistent breathing room above the title inside the tile content.
|
||||
|
||||
**Add `.step-footer` (new container for `stepArg.footerContent`):**
|
||||
```css
|
||||
.step-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
```
|
||||
|
||||
**Add overlay-mode positioning:**
|
||||
```css
|
||||
.stepperContainer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.stepperContainer.overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
.stepperContainer.predestroy {
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease-in;
|
||||
}
|
||||
```
|
||||
|
||||
**Adjust `:host` for dual-mode:**
|
||||
```css
|
||||
:host {
|
||||
position: absolute; /* inline default */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-family: ${cssGeistFontFamily};
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
:host([overlay]) {
|
||||
position: fixed; /* overlay mode */
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
```
|
||||
|
||||
The `overlay` @state needs to reflect to an attribute for the `:host([overlay])` selector to work. Since `@state` doesn't reflect attributes, use `@property({ type: Boolean, reflect: true })` instead — change the decorator accordingly.
|
||||
|
||||
### 5. Imports to add in `dees-stepper.ts`
|
||||
|
||||
```ts
|
||||
import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js';
|
||||
import { zIndexRegistry } from '../../00zindex.js';
|
||||
import { cssGeistFontFamily } from '../../00fonts.js';
|
||||
import '../../00group-layout/dees-tile/dees-tile.js';
|
||||
```
|
||||
|
||||
`dees-tile` side-effect import registers the custom element. `cssGeistFontFamily` is only needed if I add it to `:host` (which I want, to match modal).
|
||||
|
||||
### 6. SweetScroll selector stability
|
||||
|
||||
`setScrollStatus()` selectors target `.step` and `.selected` (lines 228–229). These continue to match since I'm keeping those class names on the `<dees-tile>` elements. **No selector changes needed.**
|
||||
|
||||
One subtlety: `offsetTop` / `offsetHeight` on `<dees-tile>` should still work — the tile's `:host` is `display: flex; flex-direction: column;` which participates in layout. I'll verify visually in the demo.
|
||||
|
||||
### 7. Demo update
|
||||
|
||||
**File:** `ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts`
|
||||
|
||||
Current demo renders one inline stepper directly. I'll keep that and add an **overlay launcher button** above it:
|
||||
|
||||
```ts
|
||||
export const stepperDemo = () => html`
|
||||
<div style="padding: 16px;">
|
||||
<dees-button @click=${async () => {
|
||||
const stepper = await DeesStepper.createAndShow({
|
||||
steps: [/* same steps as inline demo */],
|
||||
});
|
||||
}}>Open stepper as overlay</dees-button>
|
||||
</div>
|
||||
<dees-stepper .steps=${[/* ... existing inline demo steps ... */]}></dees-stepper>
|
||||
`;
|
||||
```
|
||||
|
||||
Extract the step definitions into a `const demoSteps = [...]` above the template so both the inline and overlay paths reuse them (DRY). Import `DeesStepper` at the top of the demo file.
|
||||
|
||||
## Files to modify
|
||||
|
||||
1. **`ts_web/elements/00group-layout/dees-stepper/dees-stepper.ts`** — main refactor (IStep, imports, render, styles, createAndShow, destroy, overlay state).
|
||||
2. **`ts_web/elements/00group-layout/dees-stepper/dees-stepper.demo.ts`** — add overlay launcher button, extract shared `demoSteps` const, import `DeesStepper`.
|
||||
|
||||
**Files explicitly NOT modified:**
|
||||
- `dees-tile.ts` — used as-is via its slot API.
|
||||
- `dees-windowlayer.ts` — used as-is via `createAndShow` / `destroy` / `click` event.
|
||||
- `dees-modal.ts` — reference only.
|
||||
- `00zindex.ts` — reference only.
|
||||
|
||||
## Verification
|
||||
|
||||
1. **Build**: `pnpm run build` — must pass with no TS errors. Pure refactor, no new dependencies, no lib-check regressions expected.
|
||||
|
||||
2. **Inline demo (backward compat)**:
|
||||
- Start the demo server (port 8080 is already running) and navigate to the dees-stepper demo page.
|
||||
- Confirm the stepper renders inline exactly like before: first step centered, subsequent steps dimmed below, scroll-through animation on goNext / goBack.
|
||||
- Fill out the first form, submit → stepper scrolls to step 2. Click goBack → scrolls back.
|
||||
- Confirm the `dees-tile` frame is visible on each step (rounded, bordered, themed) and that the title + form are inside the tile's content area.
|
||||
- Confirm goBack button + step counter sit in the tile's header row, space-between, left/right respectively.
|
||||
|
||||
3. **Overlay demo (new path)**:
|
||||
- Click the "Open stepper as overlay" button.
|
||||
- Confirm a `dees-windowlayer` with blur appears behind the stepper.
|
||||
- Confirm the stepper fills the viewport (fixed, 100vw×100vh).
|
||||
- Confirm z-index stacking: stepper above window layer above page content.
|
||||
- Click the window layer (outside the tile) → stepper animates out, then destroys along with the window layer.
|
||||
- Re-open and step through forward & back — behavior identical to inline mode.
|
||||
|
||||
4. **Playwright visual check** (per CLAUDE.md: screenshots MUST go in `.playwright-mcp/`):
|
||||
- `.playwright-mcp/dees-stepper-inline.png` — inline mode, step 1 with form.
|
||||
- `.playwright-mcp/dees-stepper-overlay.png` — overlay mode, same step.
|
||||
- `.playwright-mcp/dees-stepper-overlay-step3.png` — overlay mode mid-flow, to verify scroll-stack visual.
|
||||
- Both light and dark themes if the demo has a theme toggle.
|
||||
|
||||
5. **Grep sanity**:
|
||||
- Confirm `dees-stepper` has no new unexpected match locations: `grep dees-stepper ts_web/` should still only match stepper's own files.
|
||||
- Confirm no `.step` class collisions elsewhere (unlikely — `.step` is a plain class name; all usages should be shadow-scoped to `dees-stepper`).
|
||||
|
||||
## Open assumptions & deferred scope
|
||||
|
||||
These are explicit defaults in this plan. If the user wants different behavior for any of them, they should flag it on review — each is a simple follow-up but not in scope right now (CLAUDE.md: stay focused, no "while we're at it"):
|
||||
|
||||
- **No close button on overlay stepper.** Clicking the window layer backdrop is the only way to dismiss. Matches how dees-modal with `showCloseButton: false` behaves. Can add a close button in a follow-up.
|
||||
- **No `onComplete` callback in `createAndShow`.** The last step doesn't auto-destroy the overlay — the app controls it via the step's `validationFunc`. Can add a callback option in a follow-up.
|
||||
- **No width/size options in `createAndShow`.** The step tile continues to use the stepper's existing `max-width: 500px`. Can parameterize in a follow-up.
|
||||
- **Box-shadow in the `.step` transition list** is dropped from the transition for cleanliness — the box-shadow is now on `dees-tile::part(outer)` and doesn't change between selected/hiddenStep, so transitioning it was already a no-op.
|
||||
- **`pnpm start` / dev server path**: I'll reuse the existing server on port 8080 that was already listening when this session began; if that server doesn't serve the stepper demo, I'll start wcctools manually.
|
||||
|
||||
## Risk
|
||||
|
||||
- **Low-medium.** The change is localized to one component and its demo. No API removal, only an additive `createAndShow` + an optional `footerContent` field. External consumers of the inline API continue to work if they only set `steps` + `selectedStep`.
|
||||
- **Biggest risk:** SweetScroll's `offsetTop` / `offsetHeight` measurements on `<dees-tile>` may compute differently than on the former `<div class="step">` because `dees-tile` has an internal `display: flex; flex-direction: column;` host and a `.tile-outer { flex: 1; min-height: 0; }` inner frame. If the scroll math drifts, the mitigation is to keep the `.step` wrapper as an outer `<div>` that **contains** a `<dees-tile>`, rather than putting the class directly on `<dees-tile>`. That preserves the exact box model SweetScroll was measuring. I'll try the direct-class approach first (simpler) and fall back to the wrapper approach if the scroll target looks off in the demo.
|
||||
- **Second risk:** The `:host([overlay])` attribute selector requires `overlay` to be a reflected `@property`, not `@state`. I've already accounted for this in the plan (decorator change).
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { demoFunc } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.demo.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import { demoFunc } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.demo.js';
|
||||
|
||||
tap.test('should render context menu demo', async () => {
|
||||
// Create demo container
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
|
||||
tap.test('should close all parent menus when clicking action in nested submenu', async () => {
|
||||
let actionCalled = false;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesElement, customElement, html } from '@design.estate/dees-element';
|
||||
|
||||
// Create a test element with shadow DOM
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
|
||||
tap.test('should show context menu with nested submenu', async () => {
|
||||
// Create a test element with context menu items
|
||||
|
||||
@@ -3,8 +3,8 @@ import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import {
|
||||
resolveWidgetPlacement,
|
||||
collectCollisions,
|
||||
} from '../ts_web/elements/dees-dashboardgrid/layout.ts';
|
||||
import type { DashboardWidget } from '../ts_web/elements/dees-dashboardgrid/types.ts';
|
||||
} from '../ts_web/elements/00group-layout/dees-dashboardgrid/layout.ts';
|
||||
import type { DashboardWidget } from '../ts_web/elements/00group-layout/dees-dashboardgrid/types.ts';
|
||||
|
||||
tap.test('dashboardgrid does not overlap widgets after swap attempt', async () => {
|
||||
const widgets: DashboardWidget[] = [
|
||||
|
||||
79
test/test.pdf-text-selection.chromium.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
|
||||
import * as deesCatalog from '../ts_web/index.js';
|
||||
|
||||
tap.test('PDF viewer should render text layer', async () => {
|
||||
const viewer = await webhelpers.fixture(
|
||||
webhelpers.html`
|
||||
<dees-pdf-viewer
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
|
||||
initialZoom="page-fit"
|
||||
style="height: 600px; width: 100%;"
|
||||
></dees-pdf-viewer>
|
||||
`
|
||||
) as deesCatalog.DeesPdfViewer;
|
||||
|
||||
// Wait for PDF to load and render
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
await viewer.updateComplete;
|
||||
|
||||
expect(viewer.totalPages).toBeGreaterThan(0);
|
||||
|
||||
const textLayer = viewer.shadowRoot?.querySelector('.text-layer[data-page="1"]');
|
||||
expect(textLayer).toBeTruthy();
|
||||
|
||||
const textSpans = textLayer?.querySelectorAll('span');
|
||||
expect(textSpans?.length).toBeGreaterThan(0);
|
||||
console.log(`Text layer has ${textSpans?.length} spans`);
|
||||
});
|
||||
|
||||
tap.test('Text should be selectable', async () => {
|
||||
const viewer = await webhelpers.fixture(
|
||||
webhelpers.html`
|
||||
<dees-pdf-viewer
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
|
||||
initialZoom="page-fit"
|
||||
style="height: 600px; width: 100%;"
|
||||
></dees-pdf-viewer>
|
||||
`
|
||||
) as deesCatalog.DeesPdfViewer;
|
||||
|
||||
// Wait for PDF to load and render
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
const textLayer = viewer.shadowRoot?.querySelector('.text-layer[data-page="1"]');
|
||||
const firstSpan = textLayer?.querySelector('span') as HTMLElement;
|
||||
|
||||
if (firstSpan?.textContent) {
|
||||
const range = document.createRange();
|
||||
const textNode = firstSpan.firstChild;
|
||||
if (textNode) {
|
||||
range.setStart(textNode, 0);
|
||||
range.setEnd(textNode, Math.min(5, textNode.textContent?.length || 0));
|
||||
const selection = window.getSelection();
|
||||
selection?.removeAllRanges();
|
||||
selection?.addRange(range);
|
||||
expect(selection?.toString().length).toBeGreaterThan(0);
|
||||
console.log('Selected text:', selection?.toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('endOfContent element exists for selection boundary', async () => {
|
||||
const viewer = await webhelpers.fixture(
|
||||
webhelpers.html`
|
||||
<dees-pdf-viewer
|
||||
pdfUrl="https://raw.githubusercontent.com/mozilla/pdf.js/ba2edeae/web/compressed.tracemonkey-pldi-09.pdf"
|
||||
initialZoom="page-fit"
|
||||
style="height: 600px; width: 100%;"
|
||||
></dees-pdf-viewer>
|
||||
`
|
||||
) as deesCatalog.DeesPdfViewer;
|
||||
|
||||
// Wait for PDF to load and render
|
||||
await new Promise(resolve => setTimeout(resolve, 5000));
|
||||
|
||||
const endOfContent = viewer.shadowRoot?.querySelector('.text-layer[data-page="1"] .endOfContent');
|
||||
expect(endOfContent).toBeTruthy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
|
||||
tap.test('should change block type via context menu', async () => {
|
||||
// Create WYSIWYG editor with a paragraph
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../ts_web/elements/00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
|
||||
tap.test('should show context menu on WYSIWYG blocks', async () => {
|
||||
// Create WYSIWYG editor
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@design.estate/dees-catalog',
|
||||
version: '3.34.0',
|
||||
version: '3.78.0',
|
||||
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@ import {
|
||||
} from '@design.estate/dees-element';
|
||||
|
||||
import * as domtools from '@design.estate/dees-domtools';
|
||||
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||
import '../../dees-icon/dees-icon.js';
|
||||
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import type { IActivityEntry, IActivityLogAPI } from '../../interfaces/appconfig.js';
|
||||
import { demoFunc } from './dees-appui-activitylog.demo.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
@@ -20,7 +20,7 @@ import { themeDefaultStyles } from '../../00theme.js';
|
||||
export class DeesAppuiActivitylog extends DeesElement implements IActivityLogAPI {
|
||||
// STATIC
|
||||
public static demo = demoFunc;
|
||||
public static demoGroup = 'App UI';
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE PROPERTIES
|
||||
@state()
|
||||
|
||||
@@ -15,8 +15,8 @@ import { appuiAppbarStyles } from './styles.js';
|
||||
import { renderAppuiAppbar } from './template.js';
|
||||
|
||||
// Import required components
|
||||
import '../../dees-icon/dees-icon.js';
|
||||
import '../../dees-windowcontrols/dees-windowcontrols.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import '../../00group-utility/dees-windowcontrols/dees-windowcontrols.js';
|
||||
import '../dees-appui-profiledropdown/dees-appui-profiledropdown.js';
|
||||
|
||||
declare global {
|
||||
@@ -28,7 +28,7 @@ declare global {
|
||||
@customElement('dees-appui-appbar')
|
||||
export class DeesAppuiBar extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroup = 'App UI';
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE PROPERTIES
|
||||
@property({ type: Array })
|
||||
@@ -122,7 +122,7 @@ export class DeesAppuiBar extends DeesElement {
|
||||
>
|
||||
${menuItem.iconName ? html`<dees-icon .icon="${`lucide:${menuItem.iconName}`}"></dees-icon>` : ''}
|
||||
${menuItem.name}
|
||||
${hasSubmenu ? this.renderDropdown(menuItem.submenu, itemId, isActive) : ''}
|
||||
${hasSubmenu ? this.renderDropdown(menuItem.submenu!, itemId, isActive) : ''}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -75,21 +75,21 @@ export const demoFunc = () => {
|
||||
// Set up status toggle
|
||||
const statusButtons = elementArg.querySelectorAll('.status-toggle dees-button');
|
||||
statusButtons[0].addEventListener('click', () => {
|
||||
appbar.user = { ...appbar.user, status: 'online' };
|
||||
appbar.user = { ...appbar.user!, status: 'online' };
|
||||
});
|
||||
statusButtons[1].addEventListener('click', () => {
|
||||
appbar.user = { ...appbar.user, status: 'busy' };
|
||||
appbar.user = { ...appbar.user!, status: 'busy' };
|
||||
});
|
||||
statusButtons[2].addEventListener('click', () => {
|
||||
appbar.user = { ...appbar.user, status: 'away' };
|
||||
appbar.user = { ...appbar.user!, status: 'away' };
|
||||
});
|
||||
statusButtons[3].addEventListener('click', () => {
|
||||
appbar.user = { ...appbar.user, status: 'offline' };
|
||||
appbar.user = { ...appbar.user!, status: 'offline' };
|
||||
});
|
||||
|
||||
// Set up window controls toggle
|
||||
const windowControlsButton = elementArg.querySelector('.window-controls-toggle dees-button');
|
||||
windowControlsButton.addEventListener('click', () => {
|
||||
windowControlsButton!.addEventListener('click', () => {
|
||||
appbar.showWindowControls = !appbar.showWindowControls;
|
||||
});
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
import '../../dees-icon/dees-icon.js';
|
||||
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import type {
|
||||
IBottomBarWidget,
|
||||
IBottomBarAction,
|
||||
@@ -26,7 +26,7 @@ declare global {
|
||||
@customElement('dees-appui-bottombar')
|
||||
export class DeesAppuiBottombar extends DeesElement implements IBottomBarAPI {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroup = 'App UI';
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE PROPERTIES
|
||||
@state()
|
||||
@@ -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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -31,7 +31,7 @@ export class DeesAppuiMaincontent extends DeesElement {
|
||||
</div>
|
||||
</dees-appui-maincontent>
|
||||
`;
|
||||
public static demoGroup = 'App UI';
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE
|
||||
@property({
|
||||
@@ -53,18 +53,23 @@ export class DeesAppuiMaincontent extends DeesElement {
|
||||
@property({ type: Number })
|
||||
accessor tabsAutoHideThreshold: number = 0;
|
||||
|
||||
@property({ type: Array })
|
||||
accessor tabActionsLeft: interfaces.ITabAction[] = [];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor tabActionsRight: interfaces.ITabAction[] = [];
|
||||
|
||||
public static styles = [
|
||||
themeDefaultStyles,
|
||||
cssManager.defaultStyles,
|
||||
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 {
|
||||
@@ -106,6 +111,8 @@ export class DeesAppuiMaincontent extends DeesElement {
|
||||
.tabStyle=${'horizontal'}
|
||||
.autoHide=${this.tabsAutoHide}
|
||||
.autoHideThreshold=${this.tabsAutoHideThreshold}
|
||||
.actionsLeft=${this.tabActionsLeft}
|
||||
.actionsRight=${this.tabActionsRight}
|
||||
@tab-select=${(e: CustomEvent) => this.handleTabSelect(e)}
|
||||
@tab-close=${(e: CustomEvent) => this.handleTabClose(e)}
|
||||
></dees-appui-tabs>
|
||||
@@ -157,7 +164,7 @@ export class DeesAppuiMaincontent extends DeesElement {
|
||||
}
|
||||
// Tab selection is now handled by the dees-appui-tabs component
|
||||
// But we need to ensure the tabs component is ready
|
||||
const tabsComponent = this.shadowRoot.querySelector('dees-appui-tabs') as DeesAppuiTabs;
|
||||
const tabsComponent = this.shadowRoot!.querySelector('dees-appui-tabs') as DeesAppuiTabs;
|
||||
if (tabsComponent) {
|
||||
await tabsComponent.updateComplete;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
css,
|
||||
cssManager,
|
||||
} from '@design.estate/dees-element';
|
||||
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import { demoFunc } from './dees-appui-mainmenu.demo.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
@@ -22,7 +22,7 @@ import { themeDefaultStyles } from '../../00theme.js';
|
||||
@customElement('dees-appui-mainmenu')
|
||||
export class DeesAppuiMainmenu extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroup = 'App UI';
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE
|
||||
|
||||
@@ -46,7 +46,7 @@ export class DeesAppuiMainmenu extends DeesElement {
|
||||
accessor tabs: interfaces.IMenuItem[] = [];
|
||||
|
||||
@property()
|
||||
accessor selectedTab: interfaces.IMenuItem;
|
||||
accessor selectedTab!: interfaces.IMenuItem;
|
||||
|
||||
@property({ type: Boolean, reflect: true })
|
||||
accessor collapsed: boolean = false;
|
||||
@@ -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;
|
||||
|
||||
@@ -35,7 +35,7 @@ export class DeesAppuiProfileDropdown extends DeesElement {
|
||||
.isOpen=${true}
|
||||
></dees-appui-profiledropdown>
|
||||
`;
|
||||
public static demoGroup = 'App UI';
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
@property({ type: Object })
|
||||
accessor user: {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import * as plugins from '../../00plugins.js';
|
||||
import * as interfaces from '../../interfaces/index.js';
|
||||
|
||||
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
|
||||
import '../../dees-icon/dees-icon.js';
|
||||
import { DeesContextmenu } from '../../00group-overlay/dees-contextmenu/dees-contextmenu.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
|
||||
import {
|
||||
DeesElement,
|
||||
@@ -33,7 +33,7 @@ import { themeDefaultStyles } from '../../00theme.js';
|
||||
@customElement('dees-appui-secondarymenu')
|
||||
export class DeesAppuiSecondarymenu extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroup = 'App UI';
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { html, cssManager, css, DeesElement, customElement, state } from '@desig
|
||||
import * as interfaces from '../../interfaces/index.js';
|
||||
import type { DeesAppuiTabs } from './dees-appui-tabs.js';
|
||||
|
||||
// Interactive demo component for closeable tabs
|
||||
// Interactive demo component for closeable tabs with action buttons
|
||||
@customElement('demo-closeable-tabs')
|
||||
class DemoCloseableTabs extends DeesElement {
|
||||
@state()
|
||||
@@ -18,24 +18,6 @@ class DemoCloseableTabs extends DeesElement {
|
||||
:host {
|
||||
display: block;
|
||||
}
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
button {
|
||||
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.1)')};
|
||||
border: 1px solid ${cssManager.bdTheme('rgba(59, 130, 246, 0.3)', 'rgba(59, 130, 246, 0.3)')};
|
||||
color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
|
||||
padding: 8px 16px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
button:hover {
|
||||
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.2)', 'rgba(59, 130, 246, 0.2)')};
|
||||
}
|
||||
.info {
|
||||
margin-top: 16px;
|
||||
padding: 12px 16px;
|
||||
@@ -66,17 +48,27 @@ class DemoCloseableTabs extends DeesElement {
|
||||
this.tabs = this.tabs.filter(t => t.key !== tabKey);
|
||||
}
|
||||
|
||||
private clearAll() {
|
||||
const tabsEl = this.shadowRoot!.querySelector('dees-appui-tabs') as DeesAppuiTabs;
|
||||
tabsEl?.clear();
|
||||
this.tabs = [];
|
||||
this.tabCounter = 0;
|
||||
}
|
||||
|
||||
render() {
|
||||
const rightActions: interfaces.ITabAction[] = [
|
||||
{ id: 'add', iconName: 'lucide:plus', action: () => this.addTab(), tooltip: 'New Tab' },
|
||||
{ id: 'clear', iconName: 'lucide:trash2', action: () => this.clearAll(), tooltip: 'Clear All Tabs' },
|
||||
];
|
||||
|
||||
return html`
|
||||
<dees-appui-tabs
|
||||
.tabs=${this.tabs}
|
||||
.actionsRight=${rightActions}
|
||||
@tab-close=${(e: CustomEvent) => this.removeTab(e.detail.tab.key)}
|
||||
></dees-appui-tabs>
|
||||
<div class="controls">
|
||||
<button @click=${() => this.addTab()}>+ Add New Tab</button>
|
||||
</div>
|
||||
<div class="info">
|
||||
Click the X button on tabs to close them. The "Main" tab is not closeable.
|
||||
Click the X button on tabs to close them. Use the + button to add tabs and the trash button to clear all.
|
||||
<br>Current tabs: ${this.tabs.length}
|
||||
</div>
|
||||
`;
|
||||
@@ -232,6 +224,16 @@ export const demoFunc = () => {
|
||||
{ key: 'Archived', action: () => console.log('Archived clicked') },
|
||||
];
|
||||
|
||||
const actionsLeft: interfaces.ITabAction[] = [
|
||||
{ id: 'back', iconName: 'lucide:arrowLeft', action: () => console.log('Back'), tooltip: 'Go Back' },
|
||||
];
|
||||
|
||||
const actionsRight: interfaces.ITabAction[] = [
|
||||
{ id: 'add', iconName: 'lucide:plus', action: () => console.log('Add tab'), tooltip: 'New Tab' },
|
||||
{ id: 'search', iconName: 'lucide:search', action: () => console.log('Search'), tooltip: 'Search Tabs' },
|
||||
{ id: 'disabled', iconName: 'lucide:lock', action: () => {}, tooltip: 'Disabled Action', disabled: true },
|
||||
];
|
||||
|
||||
const demoContent = (text: string) => html`
|
||||
<div style="padding: 24px; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};">
|
||||
${text}
|
||||
@@ -279,7 +281,17 @@ export const demoFunc = () => {
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Closeable Tabs (Browser-style)</div>
|
||||
<div class="section-title">Tabs with Action Buttons</div>
|
||||
<dees-appui-tabs
|
||||
.tabs=${horizontalTabs}
|
||||
.actionsLeft=${actionsLeft}
|
||||
.actionsRight=${actionsRight}
|
||||
></dees-appui-tabs>
|
||||
${demoContent('Action buttons can be placed on either side of the tab bar. They remain fixed while tabs scroll. The lock icon shows a disabled action.')}
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Closeable Tabs with Actions</div>
|
||||
<demo-closeable-tabs></demo-closeable-tabs>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { themeDefaultStyles } from '../../00theme.js';
|
||||
@customElement('dees-appui-tabs')
|
||||
export class DeesAppuiTabs extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroup = 'App UI';
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// INSTANCE
|
||||
@property({
|
||||
@@ -41,6 +41,12 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
@property({ type: Number })
|
||||
accessor autoHideThreshold: number = 0;
|
||||
|
||||
@property({ type: Array })
|
||||
accessor actionsLeft: interfaces.ITabAction[] = [];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor actionsRight: interfaces.ITabAction[] = [];
|
||||
|
||||
// Scroll state for fade indicators
|
||||
@state()
|
||||
private accessor canScrollLeft: boolean = false;
|
||||
@@ -54,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;
|
||||
@@ -70,9 +75,11 @@ 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;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
/* Scroll fade indicators */
|
||||
@@ -105,6 +112,72 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.scroll-area {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Tab action buttons */
|
||||
.tab-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
flex-shrink: 0;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.tab-actions.left {
|
||||
padding-left: 12px;
|
||||
padding-right: 8px;
|
||||
border-right: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
.tab-actions.right {
|
||||
padding-right: 12px;
|
||||
padding-left: 8px;
|
||||
border-left: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
|
||||
.tab-action-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease, color 0.15s ease;
|
||||
background: transparent;
|
||||
color: var(--dees-color-text-muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tab-action-button:hover {
|
||||
background: var(--dees-color-active);
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
|
||||
.tab-action-button:active {
|
||||
background: var(--dees-color-pressed);
|
||||
}
|
||||
|
||||
.tab-action-button.disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.tab-action-button.disabled:hover {
|
||||
background: transparent;
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
|
||||
.tab-action-button dees-icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.tabsContainer {
|
||||
position: relative;
|
||||
user-select: none;
|
||||
@@ -121,12 +194,14 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: transparent transparent;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0 16px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
/* Show scrollbar on hover */
|
||||
.tabs-wrapper:hover .tabsContainer.horizontal {
|
||||
.tabs-wrapper:hover .tabsContainer.horizontal,
|
||||
.scroll-area:hover .tabsContainer.horizontal {
|
||||
scrollbar-color: ${cssManager.bdTheme('rgba(0,0,0,0.2)', 'rgba(255,255,255,0.2)')} transparent;
|
||||
}
|
||||
|
||||
@@ -144,11 +219,13 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.tabs-wrapper:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb {
|
||||
.tabs-wrapper:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb,
|
||||
.scroll-area:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb {
|
||||
background: ${cssManager.bdTheme('rgba(0,0,0,0.2)', 'rgba(255,255,255,0.2)')};
|
||||
}
|
||||
|
||||
.tabs-wrapper:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb:hover {
|
||||
.tabs-wrapper:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb:hover,
|
||||
.scroll-area:hover .tabsContainer.horizontal::-webkit-scrollbar-thumb:hover {
|
||||
background: ${cssManager.bdTheme('rgba(0,0,0,0.35)', 'rgba(255,255,255,0.35)')};
|
||||
}
|
||||
|
||||
@@ -159,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;
|
||||
@@ -192,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;
|
||||
}
|
||||
|
||||
@@ -213,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,
|
||||
@@ -230,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,
|
||||
@@ -239,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 {
|
||||
@@ -259,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;
|
||||
}
|
||||
@@ -272,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);
|
||||
}
|
||||
@@ -288,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 {
|
||||
@@ -297,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 {
|
||||
@@ -331,13 +408,20 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
const containerClass = `tabsContainer ${this.tabStyle}`;
|
||||
|
||||
if (isHorizontal) {
|
||||
const hasLeftActions = this.actionsLeft && this.actionsLeft.length > 0;
|
||||
const hasRightActions = this.actionsRight && this.actionsRight.length > 0;
|
||||
|
||||
return html`
|
||||
<div class="${wrapperClass}">
|
||||
<div class="scroll-fade scroll-fade-left ${this.canScrollLeft ? 'visible' : ''}"></div>
|
||||
<div class="${containerClass}" @scroll=${this.handleScroll}>
|
||||
${this.tabs.map(tab => this.renderTab(tab, isHorizontal))}
|
||||
${hasLeftActions ? this.renderActions(this.actionsLeft, 'left') : ''}
|
||||
<div class="scroll-area">
|
||||
<div class="scroll-fade scroll-fade-left ${this.canScrollLeft ? 'visible' : ''}"></div>
|
||||
<div class="${containerClass}" @scroll=${this.handleScroll}>
|
||||
${this.tabs.map(tab => this.renderTab(tab, isHorizontal))}
|
||||
</div>
|
||||
<div class="scroll-fade scroll-fade-right ${this.canScrollRight ? 'visible' : ''}"></div>
|
||||
</div>
|
||||
<div class="scroll-fade scroll-fade-right ${this.canScrollRight ? 'visible' : ''}"></div>
|
||||
${hasRightActions ? this.renderActions(this.actionsRight, 'right') : ''}
|
||||
${this.showTabIndicator ? html`<div class="tabIndicator"></div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
@@ -353,6 +437,22 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private renderActions(actions: interfaces.ITabAction[], position: 'left' | 'right'): TemplateResult {
|
||||
return html`
|
||||
<div class="tab-actions ${position}">
|
||||
${actions.map(action => html`
|
||||
<div
|
||||
class="tab-action-button ${action.disabled ? 'disabled' : ''}"
|
||||
title="${action.tooltip || action.id}"
|
||||
@click=${() => !action.disabled && action.action()}
|
||||
>
|
||||
<dees-icon .icon=${action.iconName}></dees-icon>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
private renderTab(tab: interfaces.IMenuItem, isHorizontal: boolean): TemplateResult {
|
||||
const isSelected = tab === this.selectedTab;
|
||||
const classes = `tab ${isSelected ? 'selectedTab' : ''}`;
|
||||
@@ -406,6 +506,14 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all tabs and reset selection.
|
||||
*/
|
||||
public clear(): void {
|
||||
this.tabs = [];
|
||||
this.selectedTab = null;
|
||||
}
|
||||
|
||||
private closeTab(e: Event, tab: interfaces.IMenuItem) {
|
||||
e.stopPropagation(); // Don't select tab when closing
|
||||
|
||||
@@ -423,14 +531,9 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
if (this.tabs && this.tabs.length > 0) {
|
||||
this.selectTab(this.tabs[0]);
|
||||
}
|
||||
|
||||
// Set up ResizeObserver for scroll state updates
|
||||
// Tab selection is handled by updated() lifecycle
|
||||
this.setupResizeObserver();
|
||||
|
||||
// Initial scroll state check
|
||||
requestAnimationFrame(() => {
|
||||
this.updateScrollState();
|
||||
});
|
||||
@@ -503,8 +606,24 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
async updated(changedProperties: Map<string, any>) {
|
||||
super.updated(changedProperties);
|
||||
|
||||
if (changedProperties.has('tabs') && this.tabs && this.tabs.length > 0 && !this.selectedTab) {
|
||||
this.selectTab(this.tabs[0]);
|
||||
if (changedProperties.has('tabs')) {
|
||||
if (!this.tabs || this.tabs.length === 0) {
|
||||
// Tabs are empty => reset selection
|
||||
if (this.selectedTab !== null) {
|
||||
this.selectedTab = null;
|
||||
this.dispatchEvent(new CustomEvent('tab-select', {
|
||||
detail: { tab: null },
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}));
|
||||
}
|
||||
} else if (this.selectedTab && !this.tabs.includes(this.selectedTab)) {
|
||||
// Selected tab was removed => select first available
|
||||
this.selectTab(this.tabs[0]);
|
||||
} else if (!this.selectedTab) {
|
||||
// Tabs exist but nothing selected => select first
|
||||
this.selectTab(this.tabs[0]);
|
||||
}
|
||||
}
|
||||
|
||||
if (changedProperties.has('selectedTab') || changedProperties.has('tabs')) {
|
||||
@@ -543,21 +662,21 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
}
|
||||
|
||||
private shouldShowIndicator(): boolean {
|
||||
return this.selectedTab && this.showTabIndicator && this.tabs.includes(this.selectedTab);
|
||||
return !!this.selectedTab && this.showTabIndicator && this.tabs.includes(this.selectedTab);
|
||||
}
|
||||
|
||||
private getSelectedTabElement(): HTMLElement | null {
|
||||
const selectedIndex = this.tabs.indexOf(this.selectedTab);
|
||||
const selectedIndex = this.tabs.indexOf(this.selectedTab!);
|
||||
const isHorizontal = this.tabStyle === 'horizontal';
|
||||
const selector = isHorizontal
|
||||
const selector = isHorizontal
|
||||
? `.tabs-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`
|
||||
: `.vertical-wrapper .tabsContainer .tab:nth-child(${selectedIndex + 1})`;
|
||||
|
||||
return this.shadowRoot.querySelector(selector);
|
||||
|
||||
return this.shadowRoot!.querySelector(selector);
|
||||
}
|
||||
|
||||
private getIndicatorElement(): HTMLElement | null {
|
||||
return this.shadowRoot.querySelector('.tabIndicator');
|
||||
return this.shadowRoot!.querySelector('.tabIndicator');
|
||||
}
|
||||
|
||||
private handleInitialTransition(indicator: HTMLElement): void {
|
||||
@@ -575,7 +694,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
const tabContent = tabElement.querySelector('.tab-content') as HTMLElement;
|
||||
if (!tabContent) return;
|
||||
|
||||
const wrapperRect = indicator.parentElement.getBoundingClientRect();
|
||||
const wrapperRect = indicator.parentElement!.getBoundingClientRect();
|
||||
const contentRect = tabContent.getBoundingClientRect();
|
||||
|
||||
const contentLeft = contentRect.left - wrapperRect.left;
|
||||
@@ -587,7 +706,7 @@ export class DeesAppuiTabs extends DeesElement {
|
||||
}
|
||||
|
||||
private updateVerticalIndicator(indicator: HTMLElement, tabElement: HTMLElement): void {
|
||||
const tabsContainer = this.shadowRoot.querySelector('.vertical-wrapper .tabsContainer') as HTMLElement;
|
||||
const tabsContainer = this.shadowRoot!.querySelector('.vertical-wrapper .tabsContainer') as HTMLElement;
|
||||
if (!tabsContainer) return;
|
||||
|
||||
indicator.style.top = `${tabElement.offsetTop + tabsContainer.offsetTop}px`;
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { DeesAppui } from './dees-appui.js';
|
||||
import type { IAppConfig, IViewActivationContext } from '../../interfaces/appconfig.js';
|
||||
import type * as interfaces from '../../interfaces/index.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import '../../dees-statsgrid/dees-statsgrid.js';
|
||||
import type { IStatsTile } from '../../dees-statsgrid/dees-statsgrid.js';
|
||||
import '../../00group-dataview/dees-statsgrid/dees-statsgrid.js';
|
||||
import type { IStatsTile } from '../../00group-dataview/dees-statsgrid/dees-statsgrid.js';
|
||||
|
||||
// Demo view component with lifecycle hooks
|
||||
@customElement('demo-dashboard-view')
|
||||
@@ -12,7 +12,7 @@ class DemoDashboardView extends DeesElement {
|
||||
@state()
|
||||
accessor activated: boolean = false;
|
||||
|
||||
private ctx: IViewActivationContext;
|
||||
private ctx!: IViewActivationContext;
|
||||
|
||||
private statsTiles: IStatsTile[] = [
|
||||
{
|
||||
@@ -267,7 +267,7 @@ class DemoSettingsView extends DeesElement {
|
||||
@state()
|
||||
accessor hasChanges: boolean = false;
|
||||
|
||||
private appui: DeesAppui;
|
||||
private appui!: DeesAppui;
|
||||
|
||||
onActivate(context: IViewActivationContext) {
|
||||
this.appui = context.appui as any;
|
||||
|
||||
@@ -39,7 +39,7 @@ declare global {
|
||||
@customElement('dees-appui')
|
||||
export class DeesAppui extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroup = 'App UI';
|
||||
public static demoGroups = ['App UI'];
|
||||
|
||||
// ==========================================
|
||||
// REACTIVE OBSERVABLES (RxJS Subjects)
|
||||
@@ -143,6 +143,12 @@ export class DeesAppui extends DeesElement {
|
||||
@property({ type: Object })
|
||||
accessor maincontentSelectedTab: interfaces.IMenuItem | undefined = undefined;
|
||||
|
||||
@property({ type: Array })
|
||||
accessor contentTabActionsLeft: interfaces.ITabAction[] = [];
|
||||
|
||||
@property({ type: Array })
|
||||
accessor contentTabActionsRight: interfaces.ITabAction[] = [];
|
||||
|
||||
// References to child components
|
||||
@state()
|
||||
accessor appbar: DeesAppuiBar | undefined = undefined;
|
||||
@@ -178,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;
|
||||
@@ -213,11 +218,13 @@ export class DeesAppui extends DeesElement {
|
||||
.maingrid > dees-appui-mainmenu {
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.maingrid > dees-appui-secondarymenu {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.maingrid > dees-appui-maincontent {
|
||||
@@ -306,6 +313,8 @@ export class DeesAppui extends DeesElement {
|
||||
.showTabs=${this.maincontentTabsVisible}
|
||||
.tabsAutoHide=${this.contentTabsAutoHide}
|
||||
.tabsAutoHideThreshold=${this.contentTabsAutoHideThreshold}
|
||||
.tabActionsLeft=${this.contentTabActionsLeft}
|
||||
.tabActionsRight=${this.contentTabActionsRight}
|
||||
@tab-select=${(e: CustomEvent) => this.handleContentTabSelect(e)}
|
||||
@tab-close=${(e: CustomEvent) => this.handleContentTabClose(e)}
|
||||
>
|
||||
@@ -699,6 +708,20 @@ export class DeesAppui extends DeesElement {
|
||||
return this.maincontentSelectedTab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content tab action buttons on the left side
|
||||
*/
|
||||
public setContentTabActionsLeft(actions: interfaces.ITabAction[]): void {
|
||||
this.contentTabActionsLeft = [...actions];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set content tab action buttons on the right side
|
||||
*/
|
||||
public setContentTabActionsRight(actions: interfaces.ITabAction[]): void {
|
||||
this.contentTabActionsRight = [...actions];
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// PROGRAMMATIC API: ACTIVITY LOG
|
||||
// ==========================================
|
||||
@@ -853,8 +876,13 @@ export class DeesAppui extends DeesElement {
|
||||
try {
|
||||
await this.loadView(view, params);
|
||||
|
||||
// Update URL hash
|
||||
const route = view.route || viewId;
|
||||
// Update URL hash (substitute params into route pattern)
|
||||
let route = view.route || viewId;
|
||||
if (params) {
|
||||
for (const [key, val] of Object.entries(params)) {
|
||||
route = route.replace(`:${key}`, val);
|
||||
}
|
||||
}
|
||||
const newHash = `#${route}`;
|
||||
if (window.location.hash !== newHash) {
|
||||
window.history.pushState({ viewId }, '', newHash);
|
||||
@@ -1008,7 +1036,7 @@ export class DeesAppui extends DeesElement {
|
||||
if (!config.mainMenu?.sections) return [];
|
||||
|
||||
return config.mainMenu.sections.map((section) => ({
|
||||
name: section.name,
|
||||
name: section.name || '',
|
||||
items: section.views
|
||||
.map((viewId) => {
|
||||
const view = this.viewRegistry.get(viewId);
|
||||
|
||||
@@ -173,7 +173,7 @@ export class ViewRegistry {
|
||||
}
|
||||
|
||||
// Check for cached instance
|
||||
let element = shouldCache ? this.instances.get(viewId) : undefined;
|
||||
let element: HTMLElement | null | undefined = shouldCache ? this.instances.get(viewId) : undefined;
|
||||
|
||||
if (element) {
|
||||
// Reuse cached instance - just show it
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { html, css } from '@design.estate/dees-element';
|
||||
import '../00group-button/dees-button/dees-button.js';
|
||||
import '../dees-panel/dees-panel.js';
|
||||
import '../../00group-button/dees-button/dees-button.js';
|
||||
import '../../00group-layout/dees-panel/dees-panel.js';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
|
||||
export const demoFunc = () => html`
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as plugins from '../00plugins.js';
|
||||
import { zIndexRegistry } from '../00zindex.js';
|
||||
import { cssGeistFontFamily } from '../00fonts.js';
|
||||
import * as plugins from '../../00plugins.js';
|
||||
import { zIndexRegistry } from '../../00zindex.js';
|
||||
import { cssGeistFontFamily } from '../../00fonts.js';
|
||||
import {
|
||||
cssManager,
|
||||
css,
|
||||
@@ -12,13 +12,14 @@ import {
|
||||
property,
|
||||
state,
|
||||
} from '@design.estate/dees-element';
|
||||
import { DeesWindowLayer } from '../dees-windowlayer/dees-windowlayer.js';
|
||||
import '../dees-icon/dees-icon.js';
|
||||
import { themeDefaultStyles } from '../00theme.js';
|
||||
import { DeesWindowLayer } from '../../00group-overlay/dees-windowlayer/dees-windowlayer.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import { themeDefaultStyles } from '../../00theme.js';
|
||||
|
||||
@customElement('dees-mobilenavigation')
|
||||
export class DeesMobilenavigation extends DeesElement {
|
||||
// STATIC
|
||||
public static demoGroups = ['App UI'];
|
||||
public static demo = () => html`
|
||||
<dees-button @click=${() => {
|
||||
DeesMobilenavigation.createAndShow([
|
||||
@@ -300,15 +301,15 @@ export class DeesMobilenavigation extends DeesElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private windowLayer: DeesWindowLayer;
|
||||
private windowLayer!: DeesWindowLayer;
|
||||
|
||||
/**
|
||||
* inits the show
|
||||
*/
|
||||
public async show() {
|
||||
const domtools = await this.domtoolsPromise;
|
||||
const main = this.shadowRoot.querySelector('.main');
|
||||
|
||||
const main = this.shadowRoot!.querySelector('.main');
|
||||
|
||||
// Create window layer first (it will get its own z-index)
|
||||
if (!this.windowLayer) {
|
||||
this.windowLayer = await DeesWindowLayer.createAndShow({
|
||||
@@ -327,7 +328,7 @@ export class DeesMobilenavigation extends DeesElement {
|
||||
zIndexRegistry.register(this, this.mobileNavZIndex);
|
||||
|
||||
await domtools.convenience.smartdelay.delayFor(10);
|
||||
main.classList.add('show');
|
||||
main!.classList.add('show');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -335,8 +336,8 @@ export class DeesMobilenavigation extends DeesElement {
|
||||
*/
|
||||
public async hide() {
|
||||
const domtools = await this.domtoolsPromise;
|
||||
const main = this.shadowRoot.querySelector('.main');
|
||||
main.classList.remove('show');
|
||||
const main = this.shadowRoot!.querySelector('.main');
|
||||
main!.classList.remove('show');
|
||||
|
||||
// Unregister from z-index registry
|
||||
zIndexRegistry.unregister(this);
|
||||
@@ -8,3 +8,4 @@ export * from './dees-appui-mainmenu/index.js';
|
||||
export * from './dees-appui-secondarymenu/index.js';
|
||||
export * from './dees-appui-profiledropdown/index.js';
|
||||
export * from './dees-appui-tabs/index.js';
|
||||
export * from './dees-mobilenavigation/index.js';
|
||||
|
||||
@@ -16,7 +16,7 @@ export class DeesButtonExit extends DeesElement {
|
||||
public static demo = () => html`
|
||||
<dees-button-exit></dees-button-exit>
|
||||
`;
|
||||
public static demoGroup = 'Button';
|
||||
public static demoGroups = ['Button'];
|
||||
|
||||
// INSTANCE
|
||||
@property({
|
||||
|
||||
@@ -21,7 +21,7 @@ declare global {
|
||||
@customElement('dees-button-group')
|
||||
export class DeesButtonGroup extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroup = 'Button';
|
||||
public static demoGroups = ['Button'];
|
||||
|
||||
@property()
|
||||
accessor label: string = '';
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { html, css, cssManager, domtools } from '@design.estate/dees-element';
|
||||
import '@design.estate/dees-wcctools/demotools';
|
||||
import '../../dees-panel/dees-panel.js';
|
||||
import '../../00group-layout/dees-panel/dees-panel.js';
|
||||
import '../../00group-form/dees-form/dees-form.js';
|
||||
import '../../00group-form/dees-form-submit/dees-form-submit.js';
|
||||
import '../../00group-input/dees-input-text/dees-input-text.js';
|
||||
import '../../dees-icon/dees-icon.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
import type { DeesButton } from '../dees-button/dees-button.js';
|
||||
|
||||
export const demoFunc = () => html`
|
||||
@@ -142,54 +142,95 @@ export const demoFunc = () => html`
|
||||
<dees-panel .title=${'3. Buttons with Icons'} .subtitle=${'Combining icons with text for enhanced visual communication'}>
|
||||
<div class="icon-row">
|
||||
<dees-button>
|
||||
<dees-icon iconFA="faPlus"></dees-icon>
|
||||
<dees-icon icon="fa:plus"></dees-icon>
|
||||
Add Item
|
||||
</dees-button>
|
||||
<dees-button type="destructive">
|
||||
<dees-icon iconFA="faTrash"></dees-icon>
|
||||
<dees-icon icon="fa:trash"></dees-icon>
|
||||
Delete
|
||||
</dees-button>
|
||||
<dees-button type="outline">
|
||||
<dees-icon iconFA="faDownload"></dees-icon>
|
||||
<dees-icon icon="lucide:Download"></dees-icon>
|
||||
Download
|
||||
</dees-button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="icon-row">
|
||||
<dees-button type="secondary" size="sm">
|
||||
<dees-icon iconFA="faCog"></dees-icon>
|
||||
<dees-icon icon="fa:gear"></dees-icon>
|
||||
Settings
|
||||
</dees-button>
|
||||
<dees-button type="ghost">
|
||||
<dees-icon iconFA="faChevronLeft"></dees-icon>
|
||||
<dees-icon icon="fa:caretLeft"></dees-icon>
|
||||
Back
|
||||
</dees-button>
|
||||
<dees-button type="ghost">
|
||||
Next
|
||||
<dees-icon iconFA="faChevronRight"></dees-icon>
|
||||
<dees-icon icon="fa:caretRight"></dees-icon>
|
||||
</dees-button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="icon-row">
|
||||
<dees-button size="icon" type="default">
|
||||
<dees-icon iconFA="faPlus"></dees-icon>
|
||||
<dees-icon icon="fa:plus"></dees-icon>
|
||||
</dees-button>
|
||||
<dees-button size="icon" type="secondary">
|
||||
<dees-icon iconFA="faCog"></dees-icon>
|
||||
<dees-icon icon="fa:gear"></dees-icon>
|
||||
</dees-button>
|
||||
<dees-button size="icon" type="outline">
|
||||
<dees-icon iconFA="faSearch"></dees-icon>
|
||||
<dees-icon icon="lucide:Search"></dees-icon>
|
||||
</dees-button>
|
||||
<dees-button size="icon" type="ghost">
|
||||
<dees-icon iconFA="faEllipsisV"></dees-icon>
|
||||
<dees-icon icon="lucide:MoreVertical"></dees-icon>
|
||||
</dees-button>
|
||||
<dees-button size="icon" type="destructive">
|
||||
<dees-icon iconFA="faTrash"></dees-icon>
|
||||
<dees-icon icon="fa:trash"></dees-icon>
|
||||
</dees-button>
|
||||
</div>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
// Track icon property button clicks
|
||||
const buttons = elementArg.querySelectorAll('dees-button');
|
||||
buttons.forEach((button) => {
|
||||
button.addEventListener('clicked', () => {
|
||||
const icon = button.getAttribute('icon') || 'none';
|
||||
const position = button.getAttribute('iconPosition') || 'left';
|
||||
console.log(`Icon property button: icon=${icon}, position=${position}`);
|
||||
});
|
||||
});
|
||||
}}>
|
||||
<dees-panel .title=${'4. Icons via Property'} .subtitle=${'Simplified icon syntax using the icon property'}>
|
||||
<div class="icon-row">
|
||||
<dees-button icon="fa:plus">Add Item</dees-button>
|
||||
<dees-button type="destructive" icon="fa:trash">Delete</dees-button>
|
||||
<dees-button type="outline" icon="lucide:Download">Download</dees-button>
|
||||
</div>
|
||||
|
||||
<div class="icon-row">
|
||||
<dees-button type="secondary" size="sm" icon="fa:gear">Settings</dees-button>
|
||||
<dees-button type="ghost" icon="fa:caretLeft">Back</dees-button>
|
||||
<dees-button type="ghost" icon="fa:caretRight" iconPosition="right">Next</dees-button>
|
||||
</div>
|
||||
|
||||
<div class="icon-row">
|
||||
<dees-button size="icon" type="default" icon="fa:plus"></dees-button>
|
||||
<dees-button size="icon" type="secondary" icon="lucide:Settings"></dees-button>
|
||||
<dees-button size="icon" type="outline" icon="lucide:Search"></dees-button>
|
||||
<dees-button size="icon" type="ghost" icon="lucide:MoreVertical"></dees-button>
|
||||
<dees-button size="icon" type="destructive" icon="fa:trash"></dees-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 16px;">
|
||||
<div class="code-snippet">
|
||||
<dees-button icon="fa:plus">Add Item</dees-button><br>
|
||||
<dees-button icon="fa:caretRight" iconPosition="right">Next</dees-button>
|
||||
</div>
|
||||
</div>
|
||||
</dees-panel>
|
||||
</dees-demowrapper>
|
||||
|
||||
<dees-demowrapper .runAfterRender=${async (elementArg: HTMLElement) => {
|
||||
// Demonstrate status changes
|
||||
const pendingButton = elementArg.querySelector('dees-button[status="pending"]');
|
||||
@@ -215,7 +256,7 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'4. Button States'} .subtitle=${'Different states to indicate button status and loading conditions'}>
|
||||
<dees-panel .title=${'5. Button States'} .subtitle=${'Different states to indicate button status and loading conditions'}>
|
||||
<div class="button-group">
|
||||
<dees-button status="normal">Normal</dees-button>
|
||||
<dees-button status="pending">Processing...</dees-button>
|
||||
@@ -247,8 +288,8 @@ export const demoFunc = () => html`
|
||||
}
|
||||
|
||||
if (dataBtn && output) {
|
||||
dataBtn.addEventListener('clicked', (e: CustomEvent) => {
|
||||
output.textContent = `Clicked: Secondary button with data: ${e.detail.data}`;
|
||||
dataBtn.addEventListener('clicked', (e: Event) => {
|
||||
output.textContent = `Clicked: Secondary button with data: ${(e as CustomEvent).detail.data}`;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -260,7 +301,7 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'5. Event Handling'} .subtitle=${'Interactive examples with click event handling'}>
|
||||
<dees-panel .title=${'6. Event Handling'} .subtitle=${'Interactive examples with click event handling'}>
|
||||
<div class="button-group">
|
||||
<dees-button>Click Me</dees-button>
|
||||
<dees-button type="secondary" .eventDetailData=${'custom-data-123'}>
|
||||
@@ -281,9 +322,9 @@ export const demoFunc = () => html`
|
||||
const output = elementArg.querySelector('#form-output');
|
||||
|
||||
if (form && output) {
|
||||
form.addEventListener('formData', (e: CustomEvent) => {
|
||||
output.innerHTML = '<strong>Form submitted with data:</strong><br>' +
|
||||
JSON.stringify(e.detail.data, null, 2);
|
||||
form.addEventListener('formData', (e: Event) => {
|
||||
output.innerHTML = '<strong>Form submitted with data:</strong><br>' +
|
||||
JSON.stringify((e as CustomEvent).detail.data, null, 2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -303,7 +344,7 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'6. Form Integration'} .subtitle=${'Buttons working within forms with automatic spacing'}>
|
||||
<dees-panel .title=${'7. Form Integration'} .subtitle=${'Buttons working within forms with automatic spacing'}>
|
||||
<dees-form>
|
||||
<dees-input-text label="Name" key="name" required></dees-input-text>
|
||||
<dees-input-text label="Email" key="email" type="email" required></dees-input-text>
|
||||
@@ -330,7 +371,7 @@ export const demoFunc = () => html`
|
||||
}
|
||||
});
|
||||
}}>
|
||||
<dees-panel .title=${'7. Backward Compatibility'} .subtitle=${'Old button types are automatically mapped to new variants'}>
|
||||
<dees-panel .title=${'8. Backward Compatibility'} .subtitle=${'Old button types are automatically mapped to new variants'}>
|
||||
<div class="button-group">
|
||||
<dees-button type="normal">Normal → Default</dees-button>
|
||||
<dees-button type="highlighted">Highlighted → Destructive</dees-button>
|
||||
@@ -371,36 +412,36 @@ export const demoFunc = () => html`
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<dees-panel .title=${'8. Advanced Examples'} .subtitle=${'Complex button configurations and real-world use cases'}>
|
||||
<dees-panel .title=${'9. Advanced Examples'} .subtitle=${'Complex button configurations and real-world use cases'}>
|
||||
<div class="horizontal-group">
|
||||
<div class="vertical-group">
|
||||
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 500;">Action Group</h4>
|
||||
<dees-button type="default" size="sm">
|
||||
<dees-icon iconFA="faSave"></dees-icon>
|
||||
<dees-icon icon="lucide:Save"></dees-icon>
|
||||
Save Changes
|
||||
</dees-button>
|
||||
<dees-button type="secondary" size="sm">
|
||||
<dees-icon iconFA="faUndo"></dees-icon>
|
||||
<dees-icon icon="lucide:Undo2"></dees-icon>
|
||||
Discard
|
||||
</dees-button>
|
||||
<dees-button type="ghost" size="sm">
|
||||
<dees-icon iconFA="faQuestionCircle"></dees-icon>
|
||||
<dees-icon icon="lucide:HelpCircle"></dees-icon>
|
||||
Help
|
||||
</dees-button>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="vertical-group">
|
||||
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 500;">Danger Zone</h4>
|
||||
<dees-button type="destructive" size="sm">
|
||||
<dees-icon iconFA="faTrash"></dees-icon>
|
||||
<dees-icon icon="fa:trash"></dees-icon>
|
||||
Delete Account
|
||||
</dees-button>
|
||||
<dees-button type="outline" size="sm">
|
||||
<dees-icon iconFA="faArchive"></dees-icon>
|
||||
<dees-icon icon="lucide:Archive"></dees-icon>
|
||||
Archive Data
|
||||
</dees-button>
|
||||
<dees-button type="ghost" size="sm" disabled>
|
||||
<dees-icon iconFA="faBan"></dees-icon>
|
||||
<dees-icon icon="lucide:Ban"></dees-icon>
|
||||
Not Available
|
||||
</dees-button>
|
||||
</div>
|
||||
@@ -409,8 +450,7 @@ export const demoFunc = () => html`
|
||||
<div style="margin-top: 24px;">
|
||||
<h4 style="margin: 0 0 8px 0; font-size: 14px; font-weight: 500;">Code Example:</h4>
|
||||
<div class="code-snippet">
|
||||
<dees-button type="default" size="sm" @clicked="\${handleClick}"><br>
|
||||
<dees-icon iconFA="faSave"></dees-icon><br>
|
||||
<dees-button type="default" size="sm" icon="lucide:Save" @clicked="\${handleClick}"><br>
|
||||
Save Changes<br>
|
||||
</dees-button>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +23,7 @@ declare global {
|
||||
@customElement('dees-button')
|
||||
export class DeesButton extends DeesElement {
|
||||
public static demo = demoFunc;
|
||||
public static demoGroup = 'Button';
|
||||
public static demoGroups = ['Button'];
|
||||
|
||||
@property({
|
||||
reflect: true,
|
||||
@@ -31,10 +31,10 @@ export class DeesButton extends DeesElement {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
accessor text: string;
|
||||
accessor text!: string;
|
||||
|
||||
@property()
|
||||
accessor eventDetailData: string;
|
||||
accessor eventDetailData!: string;
|
||||
|
||||
@property({
|
||||
type: Boolean,
|
||||
@@ -68,6 +68,12 @@ export class DeesButton extends DeesElement {
|
||||
})
|
||||
accessor insideForm: boolean = false;
|
||||
|
||||
@property({ type: String, reflect: true })
|
||||
accessor icon!: string;
|
||||
|
||||
@property({ type: String, reflect: true })
|
||||
accessor iconPosition: 'left' | 'right' = 'left';
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
@@ -340,9 +346,62 @@ export class DeesButton extends DeesElement {
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
/* Text alignment */
|
||||
.textbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
`,
|
||||
];
|
||||
|
||||
/**
|
||||
* Extracts icon and text from light DOM and sets properties
|
||||
*/
|
||||
private extractLightDom(): void {
|
||||
const iconElement = this.querySelector('dees-icon') as any;
|
||||
|
||||
// Get all text content from light DOM
|
||||
const textContent = Array.from(this.childNodes)
|
||||
.filter(node => node.nodeType === Node.TEXT_NODE)
|
||||
.map(node => node.textContent?.trim())
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
if (textContent && !this.text) {
|
||||
this.text = textContent;
|
||||
}
|
||||
|
||||
if (iconElement) {
|
||||
// Get icon value
|
||||
const iconValue = iconElement.icon || iconElement.getAttribute('icon') ||
|
||||
(iconElement.iconFA ? `fa:${iconElement.iconFA}` : null);
|
||||
|
||||
if (iconValue) {
|
||||
// Determine position based on DOM order
|
||||
const children = Array.from(this.childNodes);
|
||||
const iconIndex = children.indexOf(iconElement);
|
||||
const textNodes = children.filter(node =>
|
||||
node.nodeType === Node.TEXT_NODE && node.textContent?.trim()
|
||||
);
|
||||
|
||||
if (textNodes.length > 0) {
|
||||
const firstTextIndex = children.indexOf(textNodes[0]);
|
||||
this.iconPosition = iconIndex < firstTextIndex ? 'left' : 'right';
|
||||
}
|
||||
|
||||
// Set the icon property
|
||||
this.icon = iconValue;
|
||||
}
|
||||
|
||||
// Remove the light DOM icon element
|
||||
iconElement.remove();
|
||||
}
|
||||
|
||||
// Clear all remaining light DOM
|
||||
this.innerHTML = '';
|
||||
}
|
||||
|
||||
public render(): TemplateResult {
|
||||
// Map old types to new types for backward compatibility
|
||||
const typeMap: {[key: string]: string} = {
|
||||
@@ -351,10 +410,20 @@ export class DeesButton extends DeesElement {
|
||||
'discreet': 'outline',
|
||||
'big': 'default' // Will use size instead
|
||||
};
|
||||
|
||||
|
||||
const actualType = typeMap[this.type] || this.type;
|
||||
const actualSize = this.type === 'big' ? 'lg' : this.size;
|
||||
|
||||
|
||||
const leftIcon = this.iconPosition === 'left' && this.icon
|
||||
? html`<dees-icon .icon=${this.icon}></dees-icon>`
|
||||
: '';
|
||||
const rightIcon = this.iconPosition === 'right' && this.icon
|
||||
? html`<dees-icon .icon=${this.icon}></dees-icon>`
|
||||
: '';
|
||||
|
||||
// For icon-only buttons, hide the textbox
|
||||
const isIconOnly = actualSize === 'icon' && this.icon;
|
||||
|
||||
return html`
|
||||
<div
|
||||
class="button ${this.isHidden ? 'hidden' : ''} ${actualType} size-${actualSize} ${this.status} ${this.disabled
|
||||
@@ -363,13 +432,15 @@ export class DeesButton extends DeesElement {
|
||||
@click="${this.dispatchClick}"
|
||||
>
|
||||
${this.status === 'normal' ? html``: html`
|
||||
<dees-spinner
|
||||
.bnw=${true}
|
||||
<dees-spinner
|
||||
.bnw=${true}
|
||||
status="${this.status}"
|
||||
size="${actualSize === 'sm' ? 14 : actualSize === 'lg' ? 18 : 16}"
|
||||
></dees-spinner>
|
||||
`}
|
||||
<div class="textbox">${this.text || html`<slot>Button</slot>`}</div>
|
||||
${leftIcon}
|
||||
${isIconOnly ? '' : html`<div class="textbox">${this.text || 'Button'}</div>`}
|
||||
${rightIcon}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
@@ -390,6 +461,7 @@ export class DeesButton extends DeesElement {
|
||||
}
|
||||
|
||||
public async firstUpdated() {
|
||||
// Don't set default text here as it interferes with slotted content
|
||||
// Extract light DOM content (icon + text) and set as properties
|
||||
this.extractLightDom();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,8 +44,8 @@ export const demoFunc = () => {
|
||||
// Get the chart elements
|
||||
const chartElement = elementArg.querySelector('#main-chart') as DeesChartArea;
|
||||
const connectionsChartElement = elementArg.querySelector('#connections-chart') as DeesChartArea;
|
||||
let intervalId: number;
|
||||
let connectionsIntervalId: number;
|
||||
let intervalId: number | null;
|
||||
let connectionsIntervalId: number | null;
|
||||
let currentDataset = 'system';
|
||||
|
||||
// Y-axis formatters for different datasets
|
||||
@@ -71,7 +71,7 @@ export const demoFunc = () => {
|
||||
|
||||
// Generate initial data points for time window
|
||||
const generateInitialData = (baseValue: number, variance: number, interval: number = DATA_POINT_INTERVAL) => {
|
||||
const data = [];
|
||||
const data: Array<{ x: string; y: number }> = [];
|
||||
const now = Date.now();
|
||||
const pointCount = Math.floor(TIME_WINDOW / interval);
|
||||
|
||||
@@ -240,10 +240,10 @@ export const demoFunc = () => {
|
||||
// Switch dataset
|
||||
const switchDataset = (name: string) => {
|
||||
currentDataset = name;
|
||||
const dataset = datasets[name];
|
||||
const dataset = (datasets as Record<string, any>)[name];
|
||||
chartElement.label = dataset.label;
|
||||
chartElement.series = dataset.series;
|
||||
chartElement.yAxisFormatter = formatters[name];
|
||||
chartElement.yAxisFormatter = (formatters as Record<string, any>)[name];
|
||||
|
||||
// Set appropriate y-axis scaling
|
||||
if (name === 'system') {
|
||||
@@ -310,30 +310,11 @@ export const demoFunc = () => {
|
||||
connectionsLastUpdate = 0;
|
||||
};
|
||||
|
||||
// Wire up button click handlers
|
||||
const buttons = elementArg.querySelectorAll('dees-button');
|
||||
buttons.forEach(button => {
|
||||
const text = button.textContent?.trim();
|
||||
if (text === 'System Usage') {
|
||||
button.addEventListener('click', () => switchDataset('system'));
|
||||
} else if (text === 'Network Traffic') {
|
||||
button.addEventListener('click', () => switchDataset('network'));
|
||||
} else if (text === 'Sales Data') {
|
||||
button.addEventListener('click', () => switchDataset('sales'));
|
||||
} else if (text === 'Start Live') {
|
||||
button.addEventListener('click', () => startRealtime());
|
||||
} else if (text === 'Stop Live') {
|
||||
button.addEventListener('click', () => stopRealtime());
|
||||
} else if (text === 'Spike Values') {
|
||||
button.addEventListener('click', () => randomizeData());
|
||||
}
|
||||
});
|
||||
|
||||
// Update button states based on current dataset
|
||||
const updateButtonStates = () => {
|
||||
const buttons = elementArg.querySelectorAll('dees-button');
|
||||
buttons.forEach(button => {
|
||||
const text = button.textContent?.trim();
|
||||
const allButtons = elementArg.querySelectorAll('dees-button');
|
||||
allButtons.forEach((button: any) => {
|
||||
const text = button.text?.trim();
|
||||
if (text === 'System Usage') {
|
||||
button.type = currentDataset === 'system' ? 'highlighted' : 'normal';
|
||||
} else if (text === 'Network Traffic') {
|
||||
@@ -343,41 +324,38 @@ export const demoFunc = () => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Wire up button click handlers
|
||||
const buttons = elementArg.querySelectorAll('dees-button');
|
||||
buttons.forEach((button: any) => {
|
||||
const text = button.text?.trim();
|
||||
if (text === 'System Usage') {
|
||||
button.addEventListener('click', () => { switchDataset('system'); updateButtonStates(); });
|
||||
} else if (text === 'Network Traffic') {
|
||||
button.addEventListener('click', () => { switchDataset('network'); updateButtonStates(); });
|
||||
} else if (text === 'Sales Data') {
|
||||
button.addEventListener('click', () => { switchDataset('sales'); updateButtonStates(); });
|
||||
} else if (text === 'Start Live') {
|
||||
button.addEventListener('click', () => startRealtime());
|
||||
} else if (text === 'Stop Live') {
|
||||
button.addEventListener('click', () => stopRealtime());
|
||||
} else if (text === 'Spike Values') {
|
||||
button.addEventListener('click', () => randomizeData());
|
||||
}
|
||||
});
|
||||
|
||||
// Configure main chart with rolling window
|
||||
chartElement.rollingWindow = TIME_WINDOW;
|
||||
chartElement.realtimeMode = false; // Will be enabled when starting live updates
|
||||
chartElement.yAxisScaling = 'percentage'; // Initial system dataset uses percentage
|
||||
chartElement.yAxisMax = 100;
|
||||
chartElement.autoScrollInterval = 1000; // Auto-scroll every second
|
||||
|
||||
|
||||
// Set initial time window
|
||||
setTimeout(() => {
|
||||
chartElement.updateTimeWindow();
|
||||
}, 100);
|
||||
|
||||
// Update button states when dataset changes
|
||||
const originalSwitchDataset = switchDataset;
|
||||
const switchDatasetWithButtonUpdate = (name: string) => {
|
||||
originalSwitchDataset(name);
|
||||
updateButtonStates();
|
||||
};
|
||||
|
||||
// Replace switchDataset with the one that updates buttons
|
||||
buttons.forEach(button => {
|
||||
const text = button.textContent?.trim();
|
||||
if (text === 'System Usage') {
|
||||
button.removeEventListener('click', () => switchDataset('system'));
|
||||
button.addEventListener('click', () => switchDatasetWithButtonUpdate('system'));
|
||||
} else if (text === 'Network Traffic') {
|
||||
button.removeEventListener('click', () => switchDataset('network'));
|
||||
button.addEventListener('click', () => switchDatasetWithButtonUpdate('network'));
|
||||
} else if (text === 'Sales Data') {
|
||||
button.removeEventListener('click', () => switchDataset('sales'));
|
||||
button.addEventListener('click', () => switchDatasetWithButtonUpdate('sales'));
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize connections chart with data
|
||||
if (connectionsChartElement) {
|
||||
const initialConnectionsData = generateInitialData(previousValues.connections, 30, UPDATE_INTERVAL);
|
||||
|
||||
@@ -1,60 +1,103 @@
|
||||
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%)')};
|
||||
font-weight: 400;
|
||||
color: var(--dees-color-text-primary);
|
||||
font-size: 14px;
|
||||
}
|
||||
.mainbox {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
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%)')};
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
dees-tile {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chartTitle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
padding: 16px 24px;
|
||||
z-index: 10;
|
||||
.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: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 63.9%)')};
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
.expandBtn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
color: var(--dees-color-text-muted);
|
||||
transition: all 0.15s ease;
|
||||
padding: 0;
|
||||
}
|
||||
.expandBtn:hover {
|
||||
background: var(--dees-color-hover);
|
||||
color: var(--dees-color-text-secondary);
|
||||
}
|
||||
.chartContainer {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
padding: 44px 16px 16px 0px;
|
||||
overflow: hidden;
|
||||
background: transparent; /* Ensure container doesn't override chart background */
|
||||
inset: 0 0 4px 0;
|
||||
}
|
||||
|
||||
/* ApexCharts theme overrides */
|
||||
.apexcharts-canvas {
|
||||
background: transparent !important;
|
||||
.statsBar {
|
||||
height: 32px;
|
||||
padding: 0 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.apexcharts-inner {
|
||||
background: transparent !important;
|
||||
.statsSeries {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.apexcharts-graphical {
|
||||
background: transparent !important;
|
||||
.statsSeries + .statsSeries {
|
||||
padding-left: 24px;
|
||||
border-left: 1px solid var(--dees-color-border-default);
|
||||
}
|
||||
.statsColor {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.statsName {
|
||||
font-weight: 500;
|
||||
font-size: 11px;
|
||||
color: var(--dees-color-text-secondary);
|
||||
margin-right: 4px;
|
||||
}
|
||||
.statsItem {
|
||||
font-size: 11px;
|
||||
color: var(--dees-color-text-muted);
|
||||
}
|
||||
.statsItem strong {
|
||||
color: var(--dees-color-text-primary);
|
||||
}
|
||||
.lw-tooltip {
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
pointer-events: none;
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
font-size: 12px;
|
||||
box-shadow: 0 2px 8px 0 rgba(0, 0, 0, 0.15);
|
||||
min-width: 140px;
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
|
||||
@@ -1,12 +1,31 @@
|
||||
import { html, type TemplateResult } from '@design.estate/dees-element';
|
||||
import type { DeesChartArea } from './component.js';
|
||||
import '../../00group-utility/dees-icon/dees-icon.js';
|
||||
|
||||
export const renderChartArea = (component: DeesChartArea): TemplateResult => {
|
||||
return html`
|
||||
<div class="mainbox">
|
||||
<div class="chartTitle">${component.label}</div>
|
||||
<dees-tile>
|
||||
<div slot="header" class="chartHeader">
|
||||
<span class="chartLabel">${component.label}</span>
|
||||
<button class="expandBtn" @click=${() => component.toggleFullPage()} title="${component.isFullPage ? 'Exit full page' : 'Full page'}">
|
||||
<dees-icon .icon=${component.isFullPage ? 'lucide:Minimize2' : 'lucide:Maximize2'} .iconSize=${14}></dees-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="chartContainer"></div>
|
||||
</div>
|
||||
${component.seriesStats.length > 0 ? html`
|
||||
<div slot="footer" class="statsBar">
|
||||
${component.seriesStats.map(s => html`
|
||||
<div class="statsSeries">
|
||||
<span class="statsColor" style="background:${s.color}"></span>
|
||||
<span class="statsName">${s.name}</span>
|
||||
<span class="statsItem">latest <strong>${component.yAxisFormatter(s.latest)}</strong></span>
|
||||
<span class="statsItem">min <strong>${component.yAxisFormatter(s.min)}</strong></span>
|
||||
<span class="statsItem">max <strong>${component.yAxisFormatter(s.max)}</strong></span>
|
||||
<span class="statsItem">avg <strong>${component.yAxisFormatter(s.avg)}</strong></span>
|
||||
</div>
|
||||
`)}
|
||||
</div>
|
||||
` : ''}
|
||||
</dees-tile>
|
||||
`;
|
||||
|
||||
};
|
||||
|
||||
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
`;
|
||||
};
|
||||