Compare commits

..

103 Commits

Author SHA1 Message Date
2d1d9d901b v3.3.2
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-19 12:36:12 +00:00
811ec492d8 fix(build): update build config, bump dependencies, and adjust test import paths after element reorganization 2025-12-19 12:36:12 +00:00
761b0b678b v3.3.1
Some checks failed
Default (tags) / security (push) Failing after 15s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-11 23:35:43 +00:00
12a7156928 fix(dees-pdf-viewer): Scroll active PDF thumbnail into view after rendering and on page changes; update dependency versions 2025-12-11 23:35:43 +00:00
59a870c3bc v3.3.0
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-09 08:26:24 +00:00
13fa654c0f feat(dees-appui-base): Add unified App UI API to dees-appui-base with ViewRegistry, AppRouter and StateManager 2025-12-09 08:26:24 +00:00
3616bbb9a7 v3.2.0
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 20:23:03 +00:00
27c071f7dc feat(dees-simple-appdash,dees-simple-login,dees-terminal): Revamp UI: dashboard & login styling, standardize icons to Lucide, and add terminal background/config 2025-12-08 20:23:03 +00:00
ac1ef4e497 v3.1.2
Some checks failed
Default (tags) / security (push) Failing after 12s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 16:17:52 +00:00
9c61c0542b fix(DeesAppuiMainmenu, DeesAppuiSecondarymenu): Add position: relative to main and secondary app UI menus to fix positioning of overlays and tooltips 2025-12-08 16:17:52 +00:00
5c099c8057 v3.1.1
Some checks failed
Default (tags) / security (push) Failing after 15s
Default (tags) / test (push) Failing after 18s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 16:16:25 +00:00
82b4afa95a fix(dees-appui): Extract demos for main and secondary app menus, adjust collapsed styles and toggle placement, bump devDependency 2025-12-08 16:16:25 +00:00
888430d55a v3.1.0
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 15:40:12 +00:00
85424d07cd feat(dees-appui): Add collapsible/compact mode to AppUI sidebars (mainmenu & secondarymenu) with toggles, tooltips and improved z-index stacking 2025-12-08 15:40:12 +00:00
24d3afe85d v3.0.1
Some checks failed
Default (tags) / security (push) Failing after 15s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 14:50:53 +00:00
9735af05c8 fix(dees-appui): Normalize header heights and box-sizing for App UI components 2025-12-08 14:50:53 +00:00
9471c419fa v3.0.0
Some checks failed
Default (tags) / security (push) Failing after 17s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 14:35:06 +00:00
778f457ed5 BREAKING CHANGE(dees-appui-secondarymenu): Add SecondaryMenu component and replace Mainselector with new SecondaryMenu in AppUI 2025-12-08 14:35:06 +00:00
a91098527c v2.0.7
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-08 12:04:01 +00:00
8f8aedc6b0 fix(structure): group components into groups inside the repo 2025-12-08 12:04:01 +00:00
f67be189eb v2.0.6
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 18s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-06 20:19:58 +00:00
4b8b1fa446 fix(dees-input-richtext): Initialize editor and link input element references in firstUpdated to ensure they exist before editor initialization. 2025-12-06 20:19:58 +00:00
0f9bc67a8e v2.0.5
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-06 14:09:21 +00:00
b33d51cebf fix(build): Bump devDependencies: update @git.zone/tsbundle and @git.zone/tswatch to patched versions 2025-12-06 14:09:21 +00:00
021e0fda3d v2.0.4
Some checks failed
Default (tags) / security (push) Failing after 19s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-06 13:54:17 +00:00
2ed0d8e0f2 fix(imports): Normalize and fix relative import paths for web components to ensure correct module resolution 2025-12-06 13:54:17 +00:00
5e4514c913 chore: remove obsolete files and documentation from the project 2025-12-05 10:20:29 +00:00
d1bc562b5c Refactor import paths for consistency and clarity across multiple components
- Updated import paths in dees-panel, dees-pdf, dees-progressbar, dees-searchbar, dees-shopping-productcard, dees-simple-appdash, dees-speechbubble, dees-statsgrid, dees-table, dees-toast, dees-updater, and dees-windowlayer to use consistent directory structure.
- Created index.ts files for various components to streamline imports and improve modularity.
- Ensured all imports point to the correct subdirectory structure, enhancing maintainability and readability of the codebase.
2025-12-05 10:19:37 +00:00
7adad49cb1 feat(structure): adjust 2025-12-05 10:19:11 +00:00
d07fec834f v2.0.3
Some checks failed
Default (tags) / security (push) Failing after 17s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-03 09:22:35 +00:00
6f54bd228c fix(dependencies): Bump dependencies and developer tooling versions 2025-12-03 09:22:35 +00:00
ca7aa12218 v2.0.2
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-11-30 23:57:14 +00:00
c2ee19308d fix(dees-stepper): Make step validation abortable and cancel active step listeners when navigating 2025-11-30 23:57:14 +00:00
5e27449e50 v2.0.1
Some checks failed
Default (tags) / security (push) Failing after 17s
Default (tags) / test (push) Failing after 18s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-11-30 23:46:39 +00:00
d69f777b25 fix(dees-stepper): Improve dees-stepper visual style and transitions 2025-11-30 23:46:39 +00:00
caa954a539 update 2025-11-30 23:39:04 +00:00
997520f3ba v2.0.0
Some checks failed
Default (tags) / security (push) Failing after 20s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-11-17 13:27:12 +00:00
92f69e2aa6 BREAKING CHANGE(decorators): Migrate to TC39 standard decorators (accessor) across components, update tsconfig and bump dependencies 2025-11-17 13:27:11 +00:00
70c29c778c 1.12.6
Some checks failed
Default (tags) / security (push) Failing after 26s
Default (tags) / test (push) Failing after 17s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-10-23 23:46:40 +00:00
0fc302699e fix(dependencies): Bump FontAwesome to ^7.1.0 2025-10-23 23:46:40 +00:00
dcb7ca2df3 1.12.5
Some checks failed
Default (tags) / security (push) Failing after 28s
Default (tags) / test (push) Failing after 15s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-23 20:26:55 +00:00
ccbb0415e4 fix(ci): Add local permissions settings for development 2025-09-23 20:26:55 +00:00
496f54cedd feat(dees-pdf-viewer): add toggle button for sidebar visibility and enhance thumbnail re-rendering logic 2025-09-23 19:43:51 +00:00
83b5ecebeb feat(dees-pdf-viewer): update styles to improve layout with full height and hidden overflow 2025-09-20 22:09:11 +00:00
53b5cbed07 feat(dees-pdf-viewer): optimize thumbnail rendering and styles for improved layout and responsiveness 2025-09-20 22:07:41 +00:00
352fe79791 feat(dees-pdf-viewer): improve scrolling behavior and styles for better user experience 2025-09-20 22:03:47 +00:00
a95d5a96a0 feat(dees-pdf-viewer): add functionality to scroll thumbnail into view when sidebar is visible 2025-09-20 22:00:40 +00:00
ece7bb9a94 feat(dees-pdf-viewer): enhance page rendering and scrolling behavior with new data structure and styles 2025-09-20 21:56:23 +00:00
d42859b7b2 1.12.4
Some checks failed
Default (tags) / security (push) Failing after 23s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-20 21:52:27 +00:00
f5655ad20b fix(ci): Add local assistant settings to enable permitted dev tooling commands 2025-09-20 21:52:27 +00:00
d3463f009b feat(dees-pdf-preview): enhance A4 format detection and improve canvas rendering quality 2025-09-20 21:46:52 +00:00
bb883ce341 feat(dees-pdf-preview): enhance hover functionality and page indicator display
feat(dees-pdf-viewer): improve input handling and remove unused variables
2025-09-20 21:36:04 +00:00
d9703d3ce3 feat: Update PDF components to improve rendering performance and manage document lifecycle without caching 2025-09-20 21:28:43 +00:00
7b5ba74d8b feat: Add context menu functionality for PDF components with options to view, copy URL, and download 2025-09-20 11:54:37 +00:00
a61f57db13 feat: Add PDF viewer and preview components with styling and functionality
- Implemented DeesPdfViewer for full-featured PDF viewing with toolbar and sidebar navigation.
- Created DeesPdfPreview for lightweight PDF previews.
- Introduced PdfManager for managing PDF document loading and caching.
- Added CanvasPool for efficient canvas management.
- Developed utility functions for performance monitoring and file size formatting.
- Established styles for viewer and preview components to enhance UI/UX.
- Included demo examples for showcasing PDF viewer capabilities.
2025-09-20 11:42:22 +00:00
c33ad2e405 fix(dees-input-fileupload): reorder baseStyles import for consistent styling application 2025-09-19 18:23:45 +00:00
4190324cb4 1.12.3
Some checks failed
Default (tags) / security (push) Failing after 20s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-19 17:36:03 +00:00
1b108fcc8c fix(dees-input-fileupload): Show selected files inside dropzone and improve file upload UX 2025-09-19 17:36:03 +00:00
0b2675c7e5 fix(dees-input-fileupload): enhance dropzone styles and improve file list rendering 2025-09-19 17:35:58 +00:00
12b0aa0aad Refactor dees-input-fileupload component and styles
- Updated demo.ts to enhance layout and styling, including renaming classes and adjusting spacing.
- Removed unused template rendering logic from template.ts.
- Simplified index.ts by removing the export of renderFileupload.
- Revamped styles in styles.ts for improved design consistency and responsiveness.
- Enhanced file upload functionality with better descriptions and validation messages.
2025-09-19 17:31:26 +00:00
987ae70e7a feat: add DeesInputFileupload and DeesInputRichtext components
- Implemented DeesInputFileupload component with file upload functionality, including drag-and-drop support, file previews, and clear all option.
- Developed DeesInputRichtext component featuring a rich text editor with a formatting toolbar, link management, and word count display.
- Created demo for DeesInputRichtext showcasing various use cases including basic editing, placeholder text, different heights, and disabled state.
- Added styles for both components to ensure a consistent and user-friendly interface.
- Introduced types for toolbar buttons in the rich text editor for better type safety and maintainability.
2025-09-19 15:26:21 +00:00
3ba673282a fix: update dees-wcctools dependency to version 1.2.0; adjust workspace dependencies and refactor demo function 2025-09-19 14:16:48 +00:00
20a52d1b3e 1.12.2
Some checks failed
Default (tags) / security (push) Failing after 21s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-18 16:04:02 +00:00
dafcf3834c fix(dees-input-wysiwyg): Integrate output format preview into WYSIWYG demo; update plan and add local dev settings 2025-09-18 16:04:02 +00:00
639672358a 1.12.1
Some checks failed
Default (tags) / security (push) Failing after 21s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-18 14:42:16 +00:00
671fb7dc66 fix(ci): Add local settings to allow running pnpm scripts and enable dev chat permission 2025-09-18 14:42:16 +00:00
b92966ef28 feat: consolidate contributor documentation by merging codex.md and CLAUDE.md into readme.info.md 2025-09-18 14:39:19 +00:00
c1102634f3 feat(dees-stepper): implement stepper demo with multi-step form functionality 2025-09-18 14:30:11 +00:00
ee470775b2 feat: Add WYSIWYG editor components and utilities
- Implemented WysiwygModalManager for managing modals related to code blocks and block settings.
- Created WysiwygSelection for handling text selection across Shadow DOM boundaries.
- Introduced WysiwygShortcuts for managing keyboard shortcuts and slash menu items.
- Developed wysiwygStyles for consistent styling of the WYSIWYG editor.
- Defined types for blocks, slash menu items, and shortcut patterns in wysiwyg.types.ts.
2025-09-18 14:23:42 +00:00
ba0f1602a1 feat: refactor imports and add index files for modular structure 2025-09-18 14:18:43 +00:00
682955212e feat(dees-stepper): add DeesStepper component with multi-step form functionality and validation 2025-09-18 14:18:36 +00:00
0410f6c196 1.12.0
Some checks failed
Default (tags) / security (push) Failing after 21s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-18 14:10:56 +00:00
24aa7588c5 feat(dees-stepper): Revamp dees-stepper: modern styling, new steps and improved navigation/validation 2025-09-18 14:10:55 +00:00
b46fe8fe93 feat(dees-editor): integrate Monaco version management and update CDN references 2025-09-18 13:39:59 +00:00
b47c2053b5 feat(dees-editor): add DeesEditor component with Monaco editor integration and content management 2025-09-18 13:39:52 +00:00
16bf8001ae feat(dees-dashboardgrid): implement collision detection during widget swap to prevent overlaps 2025-09-18 12:37:52 +00:00
792e77f824 feat(dees-dashboardgrid): enhance widget placement validation and logging for drag-and-drop interactions 2025-09-18 10:39:11 +00:00
9b39196195 1.11.8
Some checks failed
Default (tags) / security (push) Failing after 22s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-18 09:25:37 +00:00
ad59e3d334 fix(ci): Add local tool permissions config to allow running pnpm scripts and enable mcp__zen__chat 2025-09-18 09:25:37 +00:00
0de4283fae feat(dees-dashboardgrid): enhance drag-and-drop functionality with preview state and previous position tracking 2025-09-18 08:05:41 +00:00
6f9c92a866 feat: implement DeesDashboardgrid component with drag-and-drop functionality
- Added DeesDashboardgrid class for managing a grid of dashboard widgets.
- Implemented widget dragging and resizing capabilities.
- Introduced layout management with collision detection and margin resolution.
- Created styles for grid layout, widget appearance, and animations.
- Added support for customizable margins, cell height, and grid lines.
- Included methods for adding, removing, and updating widgets dynamically.
- Implemented context menu for widget actions and keyboard navigation support.
- Established a responsive design with breakpoint handling for different layouts.
2025-09-17 21:46:44 +00:00
0ec2f2aebb 1.11.7
Some checks failed
Default (tags) / security (push) Failing after 22s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-17 20:48:18 +00:00
cd22106597 fix(readme): Expand README with comprehensive component documentation, examples and developer guide; add local Claude settings 2025-09-17 20:48:18 +00:00
a212536cfa 1.11.6
Some checks failed
Default (tags) / security (push) Failing after 21s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-16 16:29:52 +00:00
18297d54c4 fix(dees-table): Improve Lucene range comparisons, pin monaco-editor to 0.52.2, and add local dev metadata 2025-09-16 16:29:52 +00:00
f790ca38d0 1.11.5
Some checks failed
Default (tags) / security (push) Failing after 21s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-16 16:22:25 +00:00
ce2b42ecd5 fix(ci): Add local Claude agent settings for CI tooling 2025-09-16 16:22:25 +00:00
09e299bc2e feat(styles): enhance table scrollbar behavior for sticky and non-sticky headers 2025-09-16 16:20:35 +00:00
bbc7dfe29a feat(demo): add demo sections for wide properties and scrollable table with actions 2025-09-16 16:17:03 +00:00
49b9e833e8 feat(styles): enhance actions column with sticky positioning and responsive layout adjustments 2025-09-16 16:12:13 +00:00
f739bb608e feat: enhance DeesTable with server-side search and Lucene filtering capabilities 2025-09-16 15:46:44 +00:00
286a6f9088 feat(styles): adjust searchGrid layout for content-based sizing 2025-09-16 15:28:12 +00:00
e32b9589a5 feat(styles): update searchGrid layout for improved responsiveness and control width 2025-09-16 15:25:04 +00:00
6427510c98 feat: add per-column filtering and sticky header support to DeesTable component 2025-09-16 15:17:33 +00:00
cf92a423cf Refactor DeesTable component: modularize data handling and styles
- Moved column computation and data retrieval logic to a new data.ts file for better separation of concerns.
- Created a styles.ts file to encapsulate all CSS styles related to the DeesTable component.
- Updated the DeesTable class to utilize the new data handling functions and styles.
- Introduced selection and filtering features, allowing for single and multi-row selection.
- Enhanced rendering logic to accommodate selection checkboxes and filtering capabilities.
- Re-exported types from types.ts for better type management and clarity.
2025-09-16 14:53:59 +00:00
3f3677ebaa feat: implement DeesTable component with schema-first columns API, data actions, and customizable styles
- Added DeesTable class extending DeesElement
- Introduced properties for headings, data, actions, and columns
- Implemented rendering logic for table headers, rows, and cells
- Added support for sorting, searching, and context menus
- Included customizable styles for table layout and appearance
- Integrated editable fields and drag-and-drop file handling
- Enhanced accessibility with ARIA attributes for sorting
2025-09-14 19:57:50 +00:00
edc15a727c 1.11.4
Some checks failed
Default (tags) / security (push) Failing after 24s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-14 19:23:23 +00:00
960085145d fix(readme): Rewrite and expand README with Quick Start, feature highlights, demos and usage examples; add local Claude settings file 2025-09-14 19:23:23 +00:00
7fdb4f19a8 1.11.3
Some checks failed
Default (tags) / security (push) Failing after 23s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-09 11:18:56 +00:00
e21fb79731 fix(dees-input-list): Prevent list animations from affecting scroll bounds and fix content-visibility issues in dees-input-list; add local developer settings 2025-09-09 11:18:56 +00:00
05f669a7bd feat(dees-input-list): add new input list component with demo and validation features 2025-09-08 19:21:37 +00:00
8137d79e18 1.11.2
Some checks failed
Default (tags) / security (push) Failing after 22s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-07 09:02:25 +00:00
3b474b7dcc fix(DeesFormSubmit): Make form submit robust by locating nearest dees-form via closest(); add local CLAUDE settings 2025-09-07 09:02:25 +00:00
337 changed files with 19176 additions and 13122 deletions

174
CLAUDE.md
View File

@@ -1,174 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
@design.estate/dees-catalog is a comprehensive web components library built with TypeScript and LitElement. It provides a large collection of UI components for building modern web applications with consistent design and behavior.
## Build and Development Commands
```bash
# Install dependencies
pnpm install
# Build the project
pnpm run build
# This runs: tsbuild tsfolders --allowimplicitany && tsbundle element --production --bundler esbuild
# Run development watch mode
pnpm run watch
# This runs: tswatch element
# Run tests (browser tests)
pnpm test
# This runs: tstest test/ --web --verbose --timeout 30 --logfile
# Run a specific test file
tsx test/test.wysiwyg-basic.browser.ts --verbose
# Build documentation
pnpm run buildDocs
```
### Testing Notes
- Test files follow the pattern: `test.*.browser.ts`, `test.*.node.ts`, or `test.*.both.ts`
- Browser tests run in a headless browser environment
- Use `--logfile` option to store logs in `.nogit/testlogs/`
- For debugging, create files in `.nogit/debug/` and run with `tsx`
## Architecture Overview
### Component Structure
The library is organized into several categories:
1. **Core UI Components** (`dees-button`, `dees-badge`, `dees-icon`, etc.)
- Basic building blocks with consistent theming
- All support light/dark themes via `cssManager.bdTheme()`
2. **Form Components** (`dees-form`, `dees-input-*`)
- Complete form system with validation
- Base class `DeesInputBase` provides common functionality
- Form data collection via `DeesForm` container
3. **Layout Components** (`dees-appui-*`)
- Application shell components
- `DeesAppuiBase` orchestrates the entire layout
- Grid-based responsive design
4. **Data Display** (`dees-table`, `dees-dataview-*`, `dees-statsgrid`)
- Complex data visualization components
- Interactive tables with sorting/filtering
- Chart components using ApexCharts
5. **Overlays** (`dees-modal`, `dees-contextmenu`, `dees-toast`)
- Managed by central z-index registry
- Window layer system for proper stacking
### Key Architectural Patterns
#### Z-Index Management
All overlay components use a centralized z-index registry system:
- Definition in `ts_web/elements/00zindex.ts`
- Dynamic z-index assignment via `ZIndexRegistry` class
- Components get z-index from registry when showing
- Ensures proper stacking order (dropdowns above modals, etc.)
#### Theme System
- All components support light/dark themes
- Use `cssManager.bdTheme(lightValue, darkValue)` for theme-aware colors
- Consistent color palette defined in `00colors.ts`
#### Component Demo System
- Each component has a static `demo` property
- Demo functions in separate `.demo.ts` files
- Showcase pages aggregate demos (e.g., `input-showcase.ts`)
#### WYSIWYG Editor Architecture
The WYSIWYG editor uses a sophisticated architecture with separated concerns:
- **Main Component**: `dees-input-wysiwyg.ts` - Orchestrates the editor
- **Handler Classes**:
- `WysiwygInputHandler` - Handles text input and block transformations
- `WysiwygKeyboardHandler` - Manages keyboard shortcuts and navigation
- `WysiwygDragDropHandler` - Manages block reordering
- `WysiwygModalManager` - Shows configuration modals
- `WysiwygBlockOperations` - Core block manipulation logic
- **Global Menus**:
- `DeesSlashMenu` and `DeesFormattingMenu` render globally to avoid focus issues
- Singleton pattern ensures single instance
- **Programmatic Rendering**: Uses manual DOM manipulation to prevent focus loss
### Component Communication
- Custom events for parent-child communication
- Form components emit standardized events (`change`, `blur`, etc.)
- Complex components like `DeesAppuiBase` re-emit child events
### Build System
- TypeScript compilation with decorators support
- Web component bundling with esbuild
- Element exports in `ts_web/elements/index.ts`
- Distribution builds in `dist_ts_web/`
## Important Implementation Details
### When Creating New Components
1. Extend `DeesElement` from `@design.estate/dees-element`
2. Use `@customElement('dees-componentname')` decorator
3. Implement theme support with `cssManager.bdTheme()`
4. Create a demo function in a separate `.demo.ts` file
5. Export from `elements/index.ts`
### Form Input Components
1. Extend `DeesInputBase` for form inputs
2. Implement `getValue()` and `setValue()` methods
3. Use `changeSubject.next(this)` to emit changes
4. Support `disabled` and `required` properties
### Overlay Components
1. Import z-index from `00zindex.ts`
2. Get z-index from registry when showing: `zIndexRegistry.getNextZIndex()`
3. Register/unregister with the registry
4. Use `DeesWindowLayer` for backdrop if needed
### Testing Components
1. Create test files in `test/` directory
2. Use `@git.zone/tstest` with tap-bundle
3. Test in browser environment for web components
4. Use proper async/await for component lifecycle
## Common Patterns and Pitfalls
### Focus Management
- WYSIWYG editor uses programmatic rendering to prevent focus loss
- Use `requestAnimationFrame` for timing-sensitive focus operations
- Avoid reactive re-renders during user input
### Event Handling
- Prevent event bubbling in nested interactive components
- Use `pointer-events: none/auto` for click-through behavior
- Handle both mouse and keyboard events for accessibility
### Performance Considerations
- Large components (editor, terminal) use lazy loading
- Charts use debounced resize observers
- Tables implement virtual scrolling for large datasets
## File Organization
```
ts_web/
├── elements/ # All component files
│ ├── 00*.ts # Shared utilities (colors, z-index, plugins)
│ ├── dees-*.ts # Component implementations
│ ├── dees-*.demo.ts # Component demos
│ ├── interfaces/ # Shared TypeScript interfaces
│ ├── helperclasses/ # Utility classes (FormController)
│ └── wysiwyg/ # WYSIWYG editor subsystem
├── pages/ # Demo showcase pages
└── index.ts # Main export file
```
## Recent Major Changes
- Z-Index Registry System (2025-12-24): Dynamic stacking order management
- WYSIWYG Refactoring (2025-06-24): Complete architecture overhaul with separated concerns
- Form System Enhancement: Unified validation and data collection
- Theme System: Consistent light/dark theme support across all components

View File

@@ -1,5 +1,260 @@
# Changelog
## 2025-12-19 - 3.3.2 - fix(build)
update build config, bump dependencies, and adjust test import paths after element reorganization
- npmextra.json: renamed gitzone entry to @git.zone/cli, moved tsdoc key to @git.zone/tsdoc, added @ship.zone/szci entry and added release registries + accessLevel
- package.json: bumped @design.estate/dees-wcctools ^2.0.1 -> ^3.1.0, lucide ^0.560.0 -> ^0.562.0, @git.zone/tsbuild ^3.1.2 -> ^4.0.2, @types/node ^25.0.0 -> ^25.0.3
- tests: updated import paths to follow reorganized source layout (wysiwyg files moved under elements/00group-input/dees-input-wysiwyg and dees-contextmenu moved to elements/dees-contextmenu/dees-contextmenu.js); updated BlockRegistry and blockregistration import paths
- Purpose: align tests and build metadata with refactored element file locations and updated tool/dependency versions
## 2025-12-11 - 3.3.1 - fix(dees-pdf-viewer)
Scroll active PDF thumbnail into view after rendering and on page changes; update dependency versions
- Ensure the active thumbnail is scrolled into view after thumbnails are rendered (improves sidebar navigation for dees-pdf-viewer).
- Scroll the thumbnail into view when navigating pages if the sidebar is visible (prevents the active page from being off-screen).
- Retain re-setup of the intersection observer for lazy-loading pages after thumbnail re-render.
- Bumped dependencies in package.json: @design.estate/dees-wcctools -> ^2.0.1, lucide -> ^0.560.0, @git.zone/tswatch -> ^2.3.13, @types/node -> ^25.0.0.
## 2025-12-09 - 3.3.0 - feat(dees-appui-base)
Add unified App UI API to dees-appui-base with ViewRegistry, AppRouter and StateManager
- Introduce ViewRegistry for declarative view registration and rendering (supports tag names, element classes and template functions).
- Add AppRouter with hash/history/external/none modes, URL synchronization, navigate/back/forward and onRouteChange listener support.
- Add StateManager to persist UI state (localStorage, sessionStorage or in-memory) with save/load/update/clear APIs.
- Extend interfaces (interfaces/appconfig.ts) with IAppConfig, IViewDefinition, IRoutingConfig, IStatePersistenceConfig and IAppUIState.
- Expose new public DeesAppuiBase methods: configure, navigateToView, getCurrentView, getUIState, restoreUIState, saveState, loadState, getViewRegistry, getRouter.
- Maintain backward compatibility with existing property-based API and slot usage.
- Export new modules (view.registry, app.router, state.manager) from dees-appui-base index and update element exports.
## 2025-12-08 - 3.2.0 - feat(dees-simple-appdash,dees-simple-login,dees-terminal)
Revamp UI: dashboard & login styling, standardize icons to Lucide, and add terminal background/config
- Standardize icon usage to Lucide prefixes in dees-simple-appdash; add fallback handling for legacy icon names
- Revamped dees-simple-appdash sidebar: updated spacing, typography, header icon wrapper, scrollbar styling, section labels, hover/selected states, and visual indicators
- Change 'Logout' label to 'Sign out' in dees-simple-appdash and add explicit status classes for controlbar (connected, terminal)
- Improve terminal UX: smoother launch/close animations, updated shadow and sizing logic in dees-simple-appdash
- Add background property to dees-terminal, sync it to a CSS variable and apply it to xterm theme for configurable terminal background
- Redesign dees-simple-login: new header/subheader, card layout, spacing, and updated submit text to 'Sign in'
- Bump devDependency @git.zone/tswatch to ^2.3.5
## 2025-12-08 - 3.1.2 - fix(DeesAppuiMainmenu, DeesAppuiSecondarymenu)
Add position: relative to main and secondary app UI menus to fix positioning of overlays and tooltips
- ts_web/elements/00group-appui/dees-appui-mainmenu/dees-appui-mainmenu.ts: add `position: relative` to host styles
- ts_web/elements/00group-appui/dees-appui-secondarymenu/dees-appui-secondarymenu.ts: add `position: relative` to host styles
- Fixes incorrect positioning for absolutely positioned children (tooltips, overlays, badges) inside the main and secondary menus
## 2025-12-08 - 3.1.1 - fix(dees-appui)
Extract demos for main and secondary app menus, adjust collapsed styles and toggle placement, bump devDependency
- Extracted inline demo markup into separate demo files: ts_web/elements/00group-appui/dees-appui-mainmenu/dees-appui-mainmenu.demo.ts and ts_web/elements/00group-appui/dees-appui-secondarymenu/dees-appui-secondarymenu.demo.ts and wired them up via imported demoFunc to reduce component size.
- Moved collapse toggle button markup in both dees-appui-mainmenu and dees-appui-secondarymenu templates to after the main container to improve layout/stacking and focus behavior.
- Adjusted collapsed logo/heading styles: removed extra padding/gap and hide logo text using display:none for a cleaner collapsed state.
- Bumped devDependency @git.zone/tswatch from ^2.3.1 to ^2.3.2 in package.json.
## 2025-12-08 - 3.1.0 - feat(dees-appui)
Add collapsible/compact mode to AppUI sidebars (mainmenu & secondarymenu) with toggles, tooltips and improved z-index stacking
- Add collapsed property to dees-appui-mainmenu and dees-appui-secondarymenu (reflect: true) to enable compact horizontal mode.
- Add floating collapse toggle buttons and public toggleCollapse() methods on mainmenu and secondarymenu; these dispatch 'collapse-change' events (bubbles & composed).
- Expose and track collapse state in dees-appui-base via mainmenuCollapsed and secondarymenuCollapsed properties; bind states to child components and re-emit collapse-change events as mainmenu-collapse-change and secondarymenu-collapse-change.
- Implement collapsed styles and animations: reduced sidebar widths, hide/compact labels and headers, center icons, hide badges, and add smooth width/opacity transitions.
- Add tooltips that appear for tabs/items when sidebars are collapsed to preserve discoverability.
- Adjust layout grid in DeesAppuiBase (use auto columns) and add explicit z-index layering to ensure proper stacking order of mainmenu, secondarymenu, maincontent and activitylog.
## 2025-12-08 - 3.0.1 - fix(dees-appui)
Normalize header heights and box-sizing for App UI components
- Set topbar/header heights to 48px (was 40px) and adjusted dependent offsets (activity container top, topShadow position) in dees-appui-activitylog.
- Make logo and secondary menu headers fixed 48px tall and replace vertical padding with horizontal padding for consistent vertical alignment (dees-appui-mainmenu, dees-appui-secondarymenu).
- Ensure tabs wrapper uses explicit 48px height and tabsContainer fills height (height:100%) to keep tab items vertically centered (dees-appui-tabs).
- Add box-sizing: border-box to affected header/logo containers to prevent overflow and ensure correct sizing.
- Minor CSS alignment and overflow fixes to improve consistent layout and scrolling behavior across the app UI components.
## 2025-12-08 - 3.0.0 - BREAKING CHANGE(dees-appui-secondarymenu)
Add SecondaryMenu component and replace Mainselector with new SecondaryMenu in AppUI
- Add dees-appui-secondarymenu component: collapsible groups, badges, dynamic heading, context menu and legacy flat-options support
- Introduce interfaces ISecondaryMenuItem and ISecondaryMenuGroup under elements/interfaces
- Replace dees-appui-mainselector usage with dees-appui-secondarymenu in DeesAppuiBase (props/events updated: secondarymenuGroups, secondarymenuHeading, secondarymenuOptions, item-select / secondarymenu-item-select)
- Remove dees-appui-mainselector implementation and its index export; update group exports and imports to expose secondarymenu
- Update demos and pages to showcase the new SecondaryMenu and adjust import paths for grouped components
- Bump devDependency @git.zone/tswatch to ^2.3.1
## 2025-12-08 - 2.0.7 - fix(structure)
Add many new UI components, input controls, charts, editors, and demos
- Introduce App UI components: dees-appui-appbar, dees-appui-mainmenu, dees-appui-mainselector, dees-appui-maincontent, dees-appui-activitylog, dees-appui-profiledropdown, dees-appui-tabs, dees-appui-base, dees-appui-view (templates, styles and demos included).
- Add a comprehensive set of input components: dees-input-text, dees-input-checkbox, dees-input-dropdown, dees-input-fileupload, dees-input-datepicker, dees-input-phone, dees-input-iban, dees-input-quantityselector, dees-input-list, dees-input-typelist, dees-input-tags, dees-input-multitoggle, dees-input-radiogroup, dees-input-richtext and supporting demos/styles/templates.
- Add form primitives and integration: dees-form and dees-form-submit with validation, collection and demo pages showcasing usage.
- Add button family and utilities: dees-button (with updated variants, sizes, status handling and demo), dees-button-group and dees-button-exit.
- Add charting components: dees-chart-area (ApexCharts integration) and dees-chart-log (log viewer) plus rich demo scenarios and realtime features.
- Add data display components: dees-dataview-codebox (highlight.js integration) and dees-dataview-statusobject with copy/context behaviours and demos.
- Add editor tooling: dees-editor (Monaco loader/version management), dees-editor-markdown and dees-editor-markdownoutlet; also TipTap-based richtext input with toolbar and link handling.
- Add global utilities and infra: dees-toast (programmatic toast API and containers), z-index registry and theme/font helpers (fonts, color tokens), plus many styles and accessibility/keyboard improvements across components.
- Export and index updates: new group exports added to ts_web/elements index and many index.ts files to expose the new components and demos.
- Extensive demos and showcase pages added (input-showcase, component demos) to illustrate integration, keyboard navigation, theming and form flows.
## 2025-12-06 - 2.0.6 - fix(dees-input-richtext)
Initialize editor and link input element references in firstUpdated to ensure they exist before editor initialization.
- Assign editorElement from shadowRoot.querySelector('.editor-content') in firstUpdated.
- Assign linkInputElement from shadowRoot.querySelector('.link-input input') in firstUpdated.
- Call initializeEditor() after DOM references are set to avoid undefined-element runtime errors.
## 2025-12-06 - 2.0.5 - fix(build)
Bump devDependencies: update @git.zone/tsbundle and @git.zone/tswatch to patched versions
- Update @git.zone/tsbundle from ^2.6.2 to ^2.6.3
- Update @git.zone/tswatch from ^2.2.2 to ^2.2.3
## 2025-12-06 - 2.0.4 - fix(imports)
Normalize and fix relative import paths for web components to ensure correct module resolution
- Replaced numerous './<component>.js' imports with explicit '../<component>/<component>.js' paths across many elements and demos to fix module resolution.
- Updated imports for core shared components such as dees-icon, dees-panel, dees-contextmenu, dees-windowlayer, dees-windowcontrols and several app-ui components (appbar, maincontent, mainselector, activitylog, mobilenavigation, modal, pdf, profilepicture, statsgrid, etc.).
- No runtime behavior changes — this is a refactor to import paths to address build/bundling and resolution issues.
## 2025-12-03 - 2.0.3 - fix(dependencies)
Bump dependencies and developer tooling versions
- Upgrade lucide from ^0.553.0 to ^0.555.0
- Bump @git.zone/tsbuild from ^3.1.0 to ^3.1.2
- Bump @git.zone/tsbundle from ^2.5.2 to ^2.6.2
- Bump @git.zone/tstest from ^2.8.1 to ^3.1.3
- Bump @git.zone/tswatch from ^2.2.1 to ^2.2.2
- Upgrade @types/node from ^22.0.0 to ^24.10.1
- Patch release: increment package version to 2.0.3
## 2025-11-30 - 2.0.2 - fix(dees-stepper)
Make step validation abortable and cancel active step listeners when navigating
- Extend IStep.validationFunc signature to accept an optional AbortSignal so validation handlers can be cancelled.
- Store an AbortController on the selected step and pass its signal into validationFunc when invoked.
- Abort the step's AbortController when navigating to the previous or next step to cancel any active listeners or async operations.
## 2025-11-30 - 2.0.1 - fix(dees-stepper)
Improve dees-stepper visual style and transitions
- Smooth animation: extend .step transition duration and use a cubic-bezier curve for smoother motion.
- Add .step.entrance class with a shorter easing for entrance animations to keep entrance timing distinct.
- Visual tweaks: reduce border-radius from 18px to 12px and increase inner content padding to 32px.
- Color and border updates: adjust background and border colors for light/dark themes to more consistent values.
- Shadow simplification: replace theme-dependent heavy shadows with a single subtle shadow (0 8px 32px rgba(0,0,0,0.4)).
- Remove selected-state border/box-shadow overrides (selection visuals simplified).
- Remove background-clip: padding-box to simplify rendering.
## 2025-11-17 - 2.0.0 - BREAKING CHANGE(decorators)
Migrate to TC39 standard decorators (accessor) across components, update tsconfig and bump dependencies
- Replaced experimental decorator-backed class fields with the TC39-compatible "accessor" form across ~69 web component files (properties and state fields) to follow Lit 3.x recommendations.
- Updated tsconfig.json to remove experimentalDecorators and useDefineForClassFields, aligning compiler settings with the standard decorators migration.
- Fixed optional/nullable fields to explicit `Type | undefined = undefined` where necessary to preserve runtime behavior and typing.
- Adjusted/remove usages of some non-reactive decorators/@query patterns to be compatible with the new decorator model (notable changes in a few components).
- Bumped several dependencies and devDependencies (examples: @design.estate/dees-domtools, @design.estate/dees-element, @design.estate/dees-wcctools, @git.zone/tsbuild, @git.zone/tstest, apexcharts, lucide).
- Added migration notes and testing summary to readme.hints.md documenting the TC39 decorators migration and verification steps.
## 2025-10-23 - 1.12.6 - fix(dependencies)
Bump FontAwesome to ^7.1.0 and add local claude settings
- Updated @fortawesome packages (@fortawesome/fontawesome-svg-core, @fortawesome/free-brands-svg-icons, @fortawesome/free-regular-svg-icons, @fortawesome/free-solid-svg-icons) to ^7.1.0 in package.json
- Added .claude/settings.local.json to configure local Claude/tooling permissions for repository operations
## 2025-09-23 - 1.12.5 - fix(ci)
Add local permissions settings for development
- Adds a new local settings file: .claude/settings.local.json
- Provides explicit permission entries for development tasks (allow running pnpm scripts, reading files, searching/replacing patterns, activating project, and helper tooling)
- Intended for local dev environment to enable tool automation without changing repository code
## 2025-09-20 - 1.12.4 - fix(ci)
Add local assistant settings to enable permitted dev tooling commands
- Add a local assistant settings file to configure allowed development tooling commands.
- Allows running pnpm scripts, file read/search/replace operations and other local project helper actions.
- Local configuration only — does not change library code or public API.
## 2025-09-19 - 1.12.3 - fix(dees-input-fileupload)
Show selected files inside dropzone and improve file upload UX
- Render the selected file list inside the dropzone container so files are displayed inline with the drop area
- Add dropzone--has-files class and styles to visually indicate when files are present
- Avoid opening the file selector when clicking on the browse button or inside the file list (prevents accidental re-opening)
- Refine file list and file-row styles (sizes, paddings, border radius, hover/background behavior and thumbnail/icon sizes) for a more compact and consistent appearance
- Simplify empty-state handling by returning an empty template when no files are present (file list is only rendered when files exist)
## 2025-09-18 - 1.12.2 - fix(dees-input-wysiwyg)
Integrate output format preview into WYSIWYG demo; update plan and add local dev settings
- Wire output format preview into the WYSIWYG demo (ts_web/elements/dees-input-wysiwyg.demo.ts) by calling setupOutputFormatDemo(editors.meeting, editors.recipe) so HTML/Markdown preview controls are initialized.
- Update readme.plan.md: mark the Output Formats review tasks as completed and document that preview controls were added.
- Add a local settings file to allow running local tooling tasks (grants permission for pnpm run scripts and related local commands).
- No library API or runtime component behavior changed — this is a demo/documentation and local-settings update.
## 2025-09-18 - 1.12.1 - fix(ci)
Add local settings to allow running pnpm scripts and enable dev chat permission
- Add a repository-local settings file granting permission to run pnpm scripts (Bash(pnpm run:*)) for development tooling.
- Enable the mcp__zen__chat permission for local dev workflows.
## 2025-09-18 - 1.12.0 - feat(dees-stepper)
Revamp dees-stepper: modern styling, new steps and improved navigation/validation
- Visual refresh for dees-stepper: updated card shapes, shadows, refined borders and stronger selected-state visuals for a modern shadcn-inspired look
- Improved transitions and animations (transform, box-shadow, filter) for smoother step selection and show/hide behavior
- Expanded default/demo steps: replaced small sample with a richer multi-step flow (Account Setup, Profile Details, Contact Information, Team Size, Goals, Brand Preferences, Integrations, Review & Launch)
- Enhanced step interactions: safer goNext/goBack handling with boundary checks and reset of validation flags to avoid stale validation state
- Better toolbar/controls placement for stepper demo (spacing, counters, accessible back control) and improved keyboard/UX affordances
- Minor documentation and meta updates: readme.plan.md extended with dees-stepper plan items and added .claude/settings.local.json
## 2025-09-18 - 1.11.8 - fix(ci)
Add local tool permissions config to allow running pnpm scripts and enable mcp__zen__chat
- Add local settings file to grant permission to run pnpm scripts (Bash(pnpm run:*))
- Enable mcp__zen__chat permission in local tool settings
## 2025-09-16 - 1.11.7 - fix(readme)
Expand README with comprehensive component documentation, examples and developer guide; add local Claude settings
- Expanded README substantially: installation, component overview, detailed component docs, usage examples, demos and developer guidance
- Updated many example snippets and API usage examples (icons, inputs, editor, forms, overlays, charts, etc.) to be more explicit and consistent
- Added .claude/settings.local.json to configure local Claude permissions for repository tooling
- No runtime or library code changes — documentation and demo content only
## 2025-09-16 - 1.11.6 - fix(dees-table)
Improve Lucene range comparisons, pin monaco-editor to 0.52.2, and add local dev metadata
- Fix lucene inRange behavior to correctly compare homogeneous types (strings, numbers, dates) and fall back to string comparison when needed (ts_web/elements/dees-table/lucene.ts).
- Pin monaco-editor to 0.52.2 in package.json to avoid a breaking upgrade regression observed with ^0.53.0.
- Add local development/tooling metadata and conveniences: .claude/settings.local.json (tool permissions) and .serena/ memory files (done_checklist, project_overview, style_and_conventions, suggested_commands).
- Minor housekeeping: update project dev docs / memories to capture build/test/checklist guidance.
## 2025-09-16 - 1.11.5 - fix(ci)
Add local Claude agent settings for CI tooling
- Add .claude/settings.local.json to configure local Claude agent permissions
- Allow Bash commands matching pnpm run:* and the mcp__zen__chat permission for development tooling
## 2025-09-10 - 1.11.4 - fix(readme)
Rewrite and expand README with Quick Start, feature highlights, demos and usage examples; add local Claude settings file
- Completely rewritten and reorganized README: added Quick Start, component highlights, usage examples, demos, development workflow, troubleshooting and links.
- Added .claude/settings.local.json with local Claude permission configuration.
## 2025-09-08 - 1.11.3 - fix(dees-input-list)
Prevent list animations from affecting scroll bounds and fix content-visibility issues in dees-input-list; add local developer settings
- dees-input-list: add overflow:hidden to list items to prevent animations from altering scroll bounds and causing visual/scroll glitches
- dees-input-list: force content-visibility/contain to visible/none to avoid unexpected scrolling/layout issues when items animate
- Add .claude/settings.local.json with local developer permissions (allows running pnpm scripts via Claude-local tooling)
## 2025-09-07 - 1.11.2 - fix(DeesFormSubmit)
Make form submit robust by locating nearest dees-form via closest(); add local CLAUDE settings
- Fix: DeesFormSubmit.submit now walks up the DOM with closest('dees-form') to find and call gatherAndDispatch on the parent form. This fixes cases where the submit button is slotted or not a direct child of the form.
- Chore: Add .claude/settings.local.json to permit running pnpm scripts in the local CLAUDE environment (allows Bash(pnpm run:*)).
## 2025-09-06 - 1.11.1 - fix(dees-input-text)
Normalize Lucide icon names for password toggle
@@ -174,7 +429,7 @@ Add dees-searchbar component with live search and filter demo
## 2025-04-22 - 1.6.0 - feat(documentation/dees-heading)
Add codex documentation overview and dees-heading component demo
- Introduce 'codex.md' to provide a high-level overview of project layout, component patterns, and build workflow
- Introduce contributor overview doc (`codex.md`, now consolidated into `readme.info.md`) to provide a high-level overview of project layout, component patterns, and build workflow
- Add and update dees-heading component with demo to support multiple heading levels and horizontal rule styles
- Update component export index to include dees-heading

View File

@@ -1,43 +0,0 @@
# Codex: Project Overview and Codebase Structure
## Project Overview
- Package: `@design.estate/dees-catalog`
- Focus: Web Components library providing UI elements and layouts for modern web apps.
## Directory Layout
- ts_web/: TypeScript source files
- elements/: Individual Web Component definitions
- pages/: Page-level templates for composite layouts
- html/: Demo/app entry point loading the bundled scripts
- dist_bundle/: Bundled browser JS and source maps
- dist_ts_web/: ES module outputs for TypeScript/web consumers
- dist_watch/: Watch-mode development bundle with live reload
- test/: Browser-based tests using `@push.rocks/tapbundle`
## Component Patterns
- Each component in ts_web/elements/:
- Decorated with `@customElement('tag-name')`
- Extends `DeesElement` from `@design.estate/dees-element`
- Uses `@property` for reactive, reflected attributes
- Defines `static styles = [cssManager.defaultStyles, css`...`]`
- Implements `render()` returning a Lit `html` template with slots or markup
- Exposes a demo via `public static demo` linking to `.demo.ts` files
## Build & Development Workflow
- Install dependencies: `npm install` or `pnpm install`
- Build production bundle: `npm run build`
- Start dev watch mode: `npm run watch`
- Run tests: `npm test` (launches browser fixtures)
## Theming & Utilities
- Default global styles via `cssManager.defaultStyles`
- Theme-aware values with `cssManager.bdTheme(light, dark)`
- DOM utilities set up in `html/index.ts` using `@design.estate/dees-domtools`
## Documentation
- `readme.md` provides an overview of all components and basic usage
- Live examples in `.demo.ts` files
accessible via component `demo` static property
## Updates to this file
If you have pattern insisights or general changes to the codebase, please update this file.

View File

@@ -1,5 +1,5 @@
{
"gitzone": {
"@git.zone/cli": {
"projectType": "wcc",
"module": {
"githost": "code.foss.global",
@@ -35,13 +35,19 @@
"Modern Web",
"Frontend Development"
]
},
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
}
},
"npmci": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
},
"tsdoc": {
"@git.zone/tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
},
"@ship.zone/szci": {
"npmGlobalTools": []
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-catalog",
"version": "1.11.1",
"version": "3.3.2",
"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",
@@ -10,46 +10,48 @@
"test": "tstest test/ --web --verbose --timeout 30 --logfile",
"build": "tsbuild tsfolders --allowimplicitany && tsbundle element --production --bundler esbuild",
"watch": "tswatch element",
"buildDocs": "tsdoc"
"buildDocs": "tsdoc",
"postinstall": "node scripts/update-monaco-version.cjs"
},
"author": "Lossless GmbH",
"license": "MIT",
"dependencies": {
"@design.estate/dees-domtools": "^2.3.3",
"@design.estate/dees-element": "^2.1.2",
"@design.estate/dees-wcctools": "^1.1.1",
"@fortawesome/fontawesome-svg-core": "^7.0.1",
"@fortawesome/free-brands-svg-icons": "^7.0.1",
"@fortawesome/free-regular-svg-icons": "^7.0.1",
"@fortawesome/free-solid-svg-icons": "^7.0.1",
"@design.estate/dees-domtools": "^2.3.6",
"@design.estate/dees-element": "^2.1.3",
"@design.estate/dees-wcctools": "^3.1.0",
"@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",
"@push.rocks/smarti18n": "^1.0.4",
"@push.rocks/smartpromise": "^4.2.0",
"@push.rocks/smartstring": "^4.0.15",
"@push.rocks/smartstring": "^4.1.0",
"@tiptap/core": "^2.23.0",
"@tiptap/extension-link": "^2.23.0",
"@tiptap/extension-text-align": "^2.23.0",
"@tiptap/extension-typography": "^2.23.0",
"@tiptap/extension-underline": "^2.23.0",
"@tiptap/starter-kit": "^2.23.0",
"@tsclass/tsclass": "^9.2.0",
"@tsclass/tsclass": "^9.3.0",
"@webcontainer/api": "1.2.0",
"apexcharts": "^5.3.4",
"apexcharts": "^5.3.6",
"highlight.js": "11.11.1",
"ibantools": "^4.5.1",
"lucide": "^0.542.0",
"monaco-editor": "^0.52.2",
"lit": "^3.3.1",
"lucide": "^0.562.0",
"monaco-editor": "0.52.2",
"pdfjs-dist": "^4.10.38",
"xterm": "^5.3.0",
"xterm-addon-fit": "^0.8.0"
},
"devDependencies": {
"@git.zone/tsbuild": "^2.6.8",
"@git.zone/tsbundle": "^2.5.1",
"@git.zone/tstest": "^2.3.6",
"@git.zone/tswatch": "^2.2.1",
"@git.zone/tsbuild": "^4.0.2",
"@git.zone/tsbundle": "^2.6.3",
"@git.zone/tstest": "^3.1.3",
"@git.zone/tswatch": "^2.3.13",
"@push.rocks/projectinfo": "^5.0.2",
"@push.rocks/tapbundle": "^6.0.3",
"@types/node": "^22.0.0"
"@types/node": "^25.0.3"
},
"files": [
"ts/**/*",

7485
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -604,4 +604,200 @@ import { zIndexLayers } from './00zindex.js';
z-index: ${zIndexLayers.overlay.modal};
```
This system ensures proper stacking order for all overlay components and prevents z-index conflicts.
This system ensures proper stacking order for all overlay components and prevents z-index conflicts.
## TC39 Standard Decorators Migration (2025-01-17)
Successfully migrated from experimental TypeScript decorators to standard TC39 decorators as recommended by Lit 3.x documentation.
### Migration Overview:
#### 1. Changes Made:
- **Added `accessor` keyword** to all `@property` and `@state` decorated fields across 69 component files
- **Updated tsconfig.json**: Removed `experimentalDecorators: true` and `useDefineForClassFields: false`
- **Fixed optional properties**: Changed `accessor prop?: Type` to `accessor prop: Type | undefined = undefined`
- **Removed incompatible decorators**: Removed `@query` and non-reactive `@state` decorators from regular fields
#### 2. Key Pattern Changes:
**Before (Experimental Decorators):**
```typescript
@property({ type: String })
public value: string = '';
@property({ type: Boolean })
public disabled?: boolean;
```
**After (Standard TC39 Decorators):**
```typescript
@property({ type: String })
accessor value: string = '';
@property({ type: Boolean })
accessor disabled: boolean | undefined = undefined;
```
#### 3. Important Rules:
- **@property and @state**: MUST use `accessor` keyword for reactive properties
- **@query decorators**: Should NOT use `accessor` (they work with regular fields)
- **Optional properties**: Cannot use `?` syntax with accessor, must use `| undefined = undefined`
- **Private fields**: Non-reactive private fields should not use decorators
#### 4. TypeScript Configuration:
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext"
}
}
```
Note: `experimentalDecorators` defaults to false, and `useDefineForClassFields` defaults to true with ES2022 target.
#### 5. Build Results:
- ✅ Build successful with standard decorators
- ✅ Tests passing (7/8 - same as before migration)
- ✅ No bundle size changes reported
- ✅ All components working correctly
#### 6. Files Modified:
- 69 component files with decorator updates
- 16 files with optional property fixes
- 3 files with @query decorator removals
- tsconfig.json configuration update
### Why This Migration:
According to Lit's documentation (https://lit.dev/docs/components/decorators/#decorator-versions):
- TC39 standard decorators are the future-proof approach
- Provides better TypeScript integration
- Aligns with JavaScript specification
- While bundle sizes are slightly larger, the standardization benefits outweigh this
### Testing:
- All unit tests passing
- Manual testing of key components verified
- No regressions detected
- Focus management and interactions working correctly
## Enhanced AppUI API (2025-12-08)
The `dees-appui-base` component has been enhanced with a unified configuration API for building real-world applications.
### New Modules:
1. **ViewRegistry** (`view.registry.ts`)
- Manages view definitions and their lifecycle
- Supports tag names, element classes, and template functions as view content
- Methods: register, get, renderView, findByRoute
2. **AppRouter** (`app.router.ts`)
- Built-in routing with hash or history mode
- External router support for framework integration
- Methods: navigate, back, forward, onRouteChange
3. **StateManager** (`state.manager.ts`)
- Persists UI state (collapsed menus, selections, current view)
- Supports localStorage, sessionStorage, or memory storage
- Methods: save, load, update, clear
### New Interfaces (in `interfaces/appconfig.ts`):
```typescript
interface IAppConfig {
branding?: { logoIcon?: string; logoText?: string };
appBar?: IAppBarConfig;
views: IViewDefinition[];
mainMenu?: IMainMenuConfig;
routing?: IRoutingConfig;
statePersistence?: IStatePersistenceConfig;
onViewChange?: (viewId: string, view: IViewDefinition) => void;
}
interface IViewDefinition {
id: string;
name: string;
iconName?: string;
content: string | (new () => HTMLElement) | (() => TemplateResult);
secondaryMenu?: ISecondaryMenuGroup[];
contentTabs?: ITab[];
route?: string;
}
interface IRoutingConfig {
mode: 'hash' | 'history' | 'external' | 'none';
basePath?: string;
defaultView?: string;
syncUrl?: boolean;
}
```
### New Public Methods on DeesAppuiBase:
```typescript
// Configure with unified config
configure(config: IAppConfig): void
// Navigation
navigateToView(viewId: string): boolean
getCurrentView(): IViewDefinition | undefined
// State management
getUIState(): IAppUIState
restoreUIState(state: IAppUIState): void
saveState(): void
loadState(): boolean
// Access internals
getViewRegistry(): ViewRegistry
getRouter(): AppRouter | null
```
### Usage Example (New Unified Config API):
```typescript
import type { IAppConfig } from '@design.estate/dees-catalog';
const config: IAppConfig = {
branding: { logoIcon: 'lucide:box', logoText: 'My App' },
views: [
{ id: 'dashboard', name: 'Dashboard', iconName: 'lucide:home', content: 'my-dashboard' },
{ id: 'settings', name: 'Settings', iconName: 'lucide:settings', content: 'my-settings' },
],
mainMenu: {
sections: [{ views: ['dashboard'] }],
bottomItems: ['settings'],
},
routing: { mode: 'hash', defaultView: 'dashboard' },
statePersistence: { enabled: true, storage: 'localStorage' },
};
html`<dees-appui-base .config=${config}></dees-appui-base>`;
```
### Backward Compatibility:
The existing property-based API still works:
```typescript
html`
<dees-appui-base
.mainmenuGroups=${groups}
.secondarymenuGroups=${secondaryGroups}
@mainmenu-tab-select=${handler}
>
<div slot="maincontent">...</div>
</dees-appui-base>
`;
```
### Key Features:
- **Declarative View Registry**: Map menu items to view components
- **Built-in Routing**: Hash or history mode with URL synchronization
- **External Router Support**: Integrate with Angular Router or other frameworks
- **State Persistence**: Save/restore collapsed menus, selections, and current view
- **View-specific Menus**: Each view can define its own secondary menu and tabs
- **Full Backward Compatibility**: Existing code continues to work

80
readme.info.md Normal file
View File

@@ -0,0 +1,80 @@
# Contributor Information
This reference consolidates the helper notes previously split across `codex.md` and `CLAUDE.md`. Use it to get oriented quickly when working on `@design.estate/dees-catalog`, a TypeScript/Lit web-components library that ships themed UI building blocks for modern web applications.
## Project Snapshot
- Package: `@design.estate/dees-catalog`
- Description: Comprehensive catalog of reusable web components with cohesive design, advanced form inputs, data displays, and layout scaffolding.
- Entry points: builds ship to `dist_ts_web/` (ES modules) and `dist_bundle/` (browser bundle); demos live in `html/`.
- Type system: strict TypeScript targeting modern browsers (see `tsconfig.json`).
## Repository Layout
- `ts_web/` TypeScript source
- `elements/` component implementations (`00*.ts` shared utilities, `dees-*.ts` components, `*.demo.ts` demos)
- `pages/` showcase pages aggregating demos
- `index.ts` main export surface
- `html/` demo entry point bootstrapping bundles
- `dist_bundle/`, `dist_ts_web/`, `dist_watch/` build outputs (production, module, and watch bundles)
- `test/` browser/node tests powered by `@push.rocks/tapbundle`
- `scripts/` maintenance utilities (e.g., Monaco version sync postinstall)
## Build & Development Commands
All workflows use pnpm (see `package.json`).
```bash
pnpm install # install dependencies
pnpm run build # tsbuild tsfolders --allowimplicitany && tsbundle element --production --bundler esbuild
pnpm run watch # tswatch element (development watch/dev server)
pnpm test # tstest test/ --web --verbose --timeout 30 --logfile
pnpm run buildDocs # tsdoc (generates docs)
tsx test/test.file.ts # run a specific test file (file must be named test.*)
```
`postinstall` runs `node scripts/update-monaco-version.cjs` to sync the Monaco editor version, so keep the script intact when updating dependencies.
## Testing Guidelines
- Framework: `@push.rocks/tapbundle` with smartexpect assertions. Always review https://code.foss.global/push.rocks/smartexpect/raw/branch/master/readme.md when adding tests.
- Import pattern:
```typescript
import { tap, expect } from '@push.rocks/tapbundle';
```
- Test naming: `test.*.both.ts` for dual runtime, `.node.ts` for Node-only, `.browser.ts` for browser-only suites.
- Prefer `pnpm test` for full runs; use `tsx` for focused debugging. Type-check failing tests with `tsc --noEmit`.
- Logs live under `.nogit/testlogs/`; put ad-hoc debug artefacts in `.nogit/debug/`.
## Component Architecture
- **Base pattern**: Components extend `DeesElement` from `@design.estate/dees-element`, use Lit decorators (`@customElement`, `@property`), and combine `cssManager.defaultStyles` with component styles. Rendering happens via Lit `html` templates; demos sit on a static `demo` property referencing a `.demo.ts` module.
- **Theming**: `cssManager.bdTheme(light, dark)` selects theme-aware values. Shared palettes live in `ts_web/elements/00colors.ts`.
- **Z-index management**: Overlays consult the registry in `ts_web/elements/00zindex.ts` (`ZIndexRegistry`) to coordinate stacking.
- **Component families**:
- Core UI (`dees-button`, `dees-badge`, `dees-icon`, …) focus on consistent theming and interactions.
- Form inputs (`dees-form`, `dees-input-*`) build on `DeesInputBase` and communicate through subjects/events for validation.
- Layout shells (`dees-appui-*`) orchestrate responsive app frames with centralized event rebroadcasts.
- Data views (`dees-table`, `dees-dataview-*`, `dees-statsgrid`) handle large datasets with virtualisation and chart integrations.
- Overlays (`dees-modal`, `dees-contextmenu`, `dees-toast`) respect the z-index registry and use shared window-layer utilities.
- **WYSIWYG editor**: `dees-input-wysiwyg` coordinates specialized handler classes (`WysiwygInputHandler`, `WysiwygKeyboardHandler`, drag/drop & modal managers) and global menus (`DeesSlashMenu`, `DeesFormattingMenu`). Rendering is imperative to preserve caret focus.
## Implementation Guidelines
- Import external modules through `ts_web/elements/00plugins.ts`: `import * as plugins from './plugins.ts';` then reference `plugins.moduleName`.
- When creating new components:
1. Extend `DeesElement` and decorate with `@customElement('dees-component')`.
2. Support theming, slots, and accessibility; provide meaningful default styles.
3. Expose a `.demo.ts` for the component and re-export via `elements/index.ts`.
- Form components must implement `getValue()` / `setValue()` and emit through `changeSubject` while honoring `disabled` and `required` states.
- Overlay components retrieve z-indices from the registry, register/unregister on show/hide, and use `DeesWindowLayer` for backdrops when appropriate.
- Avoid simplifying away functionality; prefer small, targeted changes and keep compatibility with existing APIs.
## Common Patterns & Pitfalls
- Focus management: schedule DOM updates with `requestAnimationFrame` inside interactive editors to avoid focus loss.
- Event handling: stop propagation where nested interactive elements coexist; mix pointer and keyboard handling for accessibility.
- Performance: heavy blocks/components may load lazily; charts use debounced observers, tables rely on virtual scrolling. Watch bundle size when adding dependencies.
## Documentation & Demos
- `readme.md` surfaces component overviews; demos in `.demo.ts` illustrate real usage.
- Update this `readme.info.md` when architectural patterns or workflows change so contributors stay in sync.
## Recent Highlights
- Z-index registry overhaul enables dynamic stacking control across overlays.
- WYSIWYG refactor separated block handlers for maintainability.
- Dashboard grid enhancements added live drag-and-drop previews and overlap fixes.
- Monaco editor integration now reads the installed version at build time.

829
readme.md
View File

@@ -92,15 +92,6 @@ Display icons from FontAwesome and Lucide icon libraries with library prefixes.
strokeWidth="2" // Optional: stroke width for Lucide icons
></dees-icon>
// Available FontAwesome icons include:
// fa:check, fa:bell, fa:gear, fa:trash, fa:copy, fa:paste, fa:eye, fa:eyeSlash,
// fa:plus, fa:minus, fa:circleInfo, fa:circleCheck, fa:circleXmark, fa:message,
// fa:arrowRight, fa:facebook, fa:twitter, fa:linkedin, fa:instagram, etc.
// Available Lucide icons include:
// lucide:menu, lucide:settings, lucide:home, lucide:file, lucide:folder,
// lucide:search, lucide:user, lucide:heart, lucide:star, lucide:download, etc.
// Legacy API (deprecated but still supported)
<dees-icon
iconFA="check" // Without prefix - assumes FontAwesome
@@ -599,10 +590,10 @@ Base container component for application layout structure with integrated appbar
.appbarMenuItems=${[
{
name: 'File',
action: async () => {},
action: async () => {}, // No-op for parent menu items
submenu: [
{ name: 'New', shortcut: 'Cmd+N', iconName: 'file-plus', action: async () => {} },
{ name: 'Open', shortcut: 'Cmd+O', iconName: 'folder-open', action: async () => {} },
{ name: 'New File', shortcut: 'Cmd+N', iconName: 'file-plus', action: async () => {} },
{ name: 'Open...', shortcut: 'Cmd+O', iconName: 'folder-open', action: async () => {} },
{ divider: true },
{ name: 'Save', shortcut: 'Cmd+S', iconName: 'save', action: async () => {} }
]
@@ -618,10 +609,7 @@ Base container component for application layout structure with integrated appbar
]}
.appbarBreadcrumbs=${'Dashboard > Overview'}
.appbarTheme=${'dark'}
.appbarUser=${{
name: 'John Doe',
status: 'online'
}}
.appbarUser=${{ name: 'John Doe', status: 'online' }}
.appbarShowSearch=${true}
.appbarShowWindowControls=${true}
@@ -667,43 +655,6 @@ Key Features:
- **Responsive Grid**: Uses CSS Grid for flexible, responsive layout
- **Slot Support**: Main content area supports custom content via slots
Layout Structure:
```
┌─────────────────────────────────────────────────┐
│ AppBar │
├────┬──────────────┬─────────────────┬──────────┤
│ │ │ │ │
│ M │ Selector │ Main Content │ Activity │
│ e │ │ │ Log │
│ n │ │ │ │
│ u │ │ │ │
│ │ │ │ │
└────┴──────────────┴─────────────────┴──────────┘
```
Grid Configuration:
- Main Menu: 60px width
- Selector: 240px width
- Main Content: Flexible (1fr)
- Activity Log: 240px width
Child Component Access:
```typescript
// Access child components after firstUpdated
const base = document.querySelector('dees-appui-base');
base.appbar; // DeesAppuiAppbar instance
base.mainmenu; // DeesAppuiMainmenu instance
base.mainselector; // DeesAppuiMainselector instance
base.maincontent; // DeesAppuiMaincontent instance
base.activitylog; // DeesAppuiActivitylog instance
```
Best Practices:
1. **Configuration**: Set all properties on the base component for consistency
2. **Event Handling**: Listen to events on the base component rather than child components
3. **Content**: Use the `maincontent` slot for your application's primary interface
4. **State Management**: Manage selected tabs and options at the base component level
#### `DeesAppuiMainmenu`
Main navigation menu component for application-wide navigation.
@@ -860,257 +811,6 @@ Key Features:
- Focus management
- Screen reader compatible
Menu Item Interface:
```typescript
// Regular menu item
interface IAppBarMenuItemRegular {
name: string; // Display text
action: () => Promise<any>; // Click handler
iconName?: string; // Optional icon (for dropdown items)
shortcut?: string; // Keyboard shortcut display
submenu?: IAppBarMenuItem[]; // Nested menu items
disabled?: boolean; // Disabled state
checked?: boolean; // For checkbox menu items
radioGroup?: string; // For radio button menu items
}
// Divider item
interface IAppBarMenuDivider {
divider: true;
}
// Combined type
type IAppBarMenuItem = IAppBarMenuItemRegular | IAppBarMenuDivider;
```
Best Practices:
1. **Menu Structure**
- Keep top-level menus text-only (no icons)
- Use icons in dropdown items for visual clarity
- Group related actions with dividers
- Provide keyboard shortcuts for common actions
2. **Navigation**
- Use breadcrumbs for deep navigation hierarchies
- Keep breadcrumb labels concise
- Provide meaningful navigation events
3. **User Experience**
- Show user status when relevant
- Provide clear visual feedback
- Ensure smooth transitions
- Handle edge cases (long menus, small screens)
4. **Accessibility**
- Always provide text labels
- Ensure keyboard navigation works
- Test with screen readers
- Maintain focus management
#### `DeesAppuiActivitylog`
Activity log component for displaying system events and user actions.
```typescript
<dees-appui-activitylog
.entries=${[
{
timestamp: new Date(),
type: 'info',
message: 'User logged in',
details: { userId: '123' }
},
{
timestamp: new Date(),
type: 'error',
message: 'Failed to save document',
details: { error: 'Network error' }
}
]}
maxEntries={100} // Maximum entries to display
@entry-click=${handleEntryClick}
></dees-appui-activitylog>
```
#### `DeesAppuiProfiledropdown`
User profile dropdown component with status and menu options.
```typescript
<dees-appui-profiledropdown
.user=${{
name: 'John Doe',
email: 'john@example.com',
avatar: '/path/to/avatar.jpg',
status: 'online' // Options: online, offline, busy, away
}}
.menuItems=${[
{ name: 'Profile', iconName: 'user', action: async () => {} },
{ name: 'Settings', iconName: 'settings', action: async () => {} },
{ divider: true },
{ name: 'Logout', iconName: 'logOut', action: async () => {} }
]}
@status-change=${handleStatusChange}
></dees-appui-profiledropdown>
```
#### `DeesAppuiTabs`
Tab navigation component for organizing content sections.
```typescript
<dees-appui-tabs
.tabs=${[
{
key: 'overview',
label: 'Overview',
icon: 'home',
content: html`<div>Overview content</div>`
},
{
key: 'details',
label: 'Details',
icon: 'info',
content: html`<div>Details content</div>`
}
]}
selectedTab="overview"
@tab-change=${handleTabChange}
></dees-appui-tabs>
```
#### `DeesAppuiView`
View container component for consistent page layouts.
```typescript
<dees-appui-view
viewTitle="Dashboard"
viewSubtitle="System Overview"
.headerActions=${[
{ icon: 'refresh', action: handleRefresh },
{ icon: 'settings', action: handleSettings }
]}
>
<!-- View content -->
</dees-appui-view>
```
#### `DeesMobileNavigation`
Responsive navigation component for mobile devices.
```typescript
<dees-mobile-navigation
.menuItems=${[
{
key: 'home',
label: 'Home',
icon: 'home',
action: () => navigate('home')
},
{
key: 'profile',
label: 'Profile',
icon: 'user',
action: () => navigate('profile')
}
]}
activeKey="home" // Currently active item
position="bottom" // Options: bottom, top
@item-click=${handleNavigationClick}
></dees-mobile-navigation>
```
#### `DeesDashboardGrid`
Drag-and-drop grid layout system for creating customizable dashboards.
```typescript
<dees-dashboardgrid
.widgets=${[
{
id: 'widget1',
x: 0, // Grid column position
y: 0, // Grid row position
w: 4, // Width in grid units
h: 3, // Height in grid units
minW: 2, // Minimum width
minH: 2, // Minimum height
maxW: 6, // Maximum width
title: 'Sales Overview',
icon: 'fa:chart-line',
content: html`<div>Widget content here</div>`,
noMove: false, // Allow moving
noResize: false // Allow resizing
},
{
id: 'widget2',
x: 4,
y: 0,
w: 4,
h: 3,
title: 'Recent Activity',
content: html`<dees-table .data=${activityData}></dees-table>`,
autoPosition: true // Auto-find position
}
]}
columns={12} // Number of grid columns
cellHeight={80} // Height of each grid cell in pixels
cellHeightUnit="px" // Options: px, em, rem, auto
margin={10} // Gap between widgets
editable={true} // Enable drag and resize
showGridLines={false} // Show grid guidelines
enableAnimation={true} // Smooth transitions
rtl={false} // Right-to-left support
@widget-move=${handleWidgetMove}
@widget-resize=${handleWidgetResize}
></dees-dashboardgrid>
// Programmatic methods
const grid = document.querySelector('dees-dashboardgrid');
// Add a new widget
grid.addWidget({
id: 'newWidget',
x: 0,
y: 0,
w: 3,
h: 2,
content: html`<div>New widget</div>`
}, true); // true = auto-position
// Remove widget
grid.removeWidget('widget1');
// Update widget
grid.updateWidget('widget2', {
title: 'Updated Title',
w: 6
});
// Get/set layout
const layout = grid.getLayout(); // Returns position data
grid.setLayout(savedLayout); // Restore positions
// Compact widgets
grid.compact('vertical'); // Or 'horizontal'
// Lock/unlock editing
grid.lockGrid();
grid.unlockGrid();
```
Key Features:
- Drag-and-drop widget repositioning
- Resize handles on edges and corners
- Grid-based layout system
- Collision detection
- Auto-positioning for new widgets
- Configurable constraints (min/max dimensions)
- Lock individual widgets or entire grid
- Compact layout algorithm
- Save/restore layout positions
- RTL layout support
- Optional grid lines for alignment
- Smooth animations
- Responsive sizing
- Empty state display
### Data Display Components
#### `DeesTable`
@@ -1145,6 +845,19 @@ Advanced table component with sorting, filtering, and action support.
></dees-table>
```
##### DeesTable (Updated)
Newer features available in `dees-table`:
- Schema-first columns or `displayFunction` rendering
- Sorting via header clicks with `aria-sort` + `sortChange`
- Global search with Lucene-like syntax; modes: `table`, `data`, `server`
- Per-column quick filters row; `showColumnFilters` and `column.filterable=false`
- Selection: `none` | `single` | `multi`, with select-all and `selectionChange`
- Sticky header + internal scroll (`stickyHeader`, `--table-max-height`)
- Rich actions: header/in-row/contextmenu/footer/doubleClick; pinned Actions column
- Editable cells via `editableFields`
- Drag & drop files onto rows
#### `DeesDataviewCodebox`
Code display component with syntax highlighting and line numbers.
@@ -1155,7 +868,7 @@ Code display component with syntax highlighting and line numbers.
import { html } from '@design.estate/dees-element';
export const myComponent = () => {
return html\`<div>Hello World</div>\`;
return html`<div>Hello World</div>`;
};
`}
></dees-dataview-codebox>
@@ -1208,37 +921,6 @@ PDF viewer component with navigation and zoom controls.
></dees-pdf>
```
Key Features:
- `DeesTable`:
- Sortable columns
- Searchable content
- Customizable row actions
- Selection support
- Form compatibility
- Custom display formatting
- `DeesDataviewCodebox`:
- Syntax highlighting for multiple languages
- Line numbering
- Copy to clipboard functionality
- Custom theme support
- Window-like appearance with controls
- `DeesDataviewStatusobject`:
- Hierarchical status display
- Color-coded status indicators
- Expandable details
- JSON export capability
- Customizable styling
- `DeesPdf`:
- Page navigation
- Zoom controls
- Download support
- Print functionality
- Responsive layout
- Loading states
#### `DeesStatsGrid`
A responsive grid component for displaying statistical data with various visualization types including numbers, gauges, percentages, and trends.
@@ -1336,116 +1018,6 @@ A responsive grid component for displaying statistical data with various visuali
></dees-statsgrid>
```
Key Features:
- Auto-responsive grid layout with configurable minimum tile width
- Multiple tile types for different data visualizations
- Full theme support (light/dark mode)
- Interactive tiles with action support
- Grid-level and tile-level actions
- Smooth animations and transitions
- Icon support for visual hierarchy
Tile Types:
1. **`number`** - Display numeric values with optional units
- Large, prominent value display
- Optional unit display
- Custom color support
- Description text
2. **`gauge`** - Circular gauge visualization
- Min/max value configuration
- Color thresholds for visual alerts
- Animated value transitions
- Compact circular design
3. **`percentage`** - Progress bar visualization
- Horizontal progress bar
- Percentage display overlay
- Custom color support
- Ideal for capacity metrics
4. **`trend`** - Mini sparkline chart
- Array of numeric values for trend data
- Area chart visualization
- Current value display
- Responsive SVG rendering
5. **`text`** - Simple text display
- Flexible text content
- Custom color support
- Ideal for status messages
Action System:
- **Grid Actions**: Displayed as buttons in the grid header
- Apply to the entire stats grid
- Use standard `dees-button` components
- Support icons and text
- **Tile Actions**: Context-specific actions per tile
- Single action: Direct click on tile
- Multiple actions: Right-click context menu
- Actions access tile data through closures
- Consistent with other library components
Configuration Options:
- `tiles`: Array of `IStatsTile` objects defining the grid content
- `gridActions`: Array of actions for the entire grid
- `minTileWidth`: Minimum width for tiles (default: 250px)
- `gap`: Space between tiles (default: 16px)
Best Practices:
1. **Data Organization**
- Group related metrics together
- Use consistent units and scales
- Provide meaningful descriptions
- Choose appropriate tile types for data
2. **Visual Hierarchy**
- Use colors strategically for alerts
- Include relevant icons
- Keep titles concise
- Balance tile types for visual interest
3. **Interactivity**
- Provide relevant actions for detailed views
- Use tile actions for item-specific operations
- Use grid actions for global operations
- Keep action names clear and concise
4. **Performance**
- Update only changed tiles
- Use reasonable update intervals
- Batch updates when possible
- Consider data volume for trends
Common Use Cases:
- System monitoring dashboards
- Business intelligence displays
- Performance metrics
- Resource utilization
- Real-time statistics
- KPI tracking
Integration Example:
```typescript
// Real-time updates
setInterval(() => {
const grid = document.querySelector('dees-statsgrid');
const updatedTiles = [...grid.tiles];
// Update specific tile
const cpuTile = updatedTiles.find(t => t.id === 'cpu');
cpuTile.value = Math.round(Math.random() * 100);
// Update trend data
const trendTile = updatedTiles.find(t => t.id === 'requests');
trendTile.trendData = [...trendTile.trendData.slice(1),
Math.round(Math.random() * 100)];
grid.tiles = updatedTiles;
}, 3000);
```
#### `DeesPagination`
Pagination component for navigating through large datasets.
@@ -1459,14 +1031,6 @@ Pagination component for navigating through large datasets.
></dees-pagination>
```
Key Features:
- Page number navigation
- Previous/next buttons
- Jump to first/last page
- Configurable items per page
- Responsive design
- Keyboard navigation support
### Visualization Components
#### `DeesChartArea`
@@ -1496,22 +1060,6 @@ Area chart component built on ApexCharts for visualizing time-series data.
></dees-chart-area>
```
Key Features:
- Responsive design with automatic resizing
- Gradient fill support
- Interactive tooltips
- Grid customization
- Multiple series support
- Time-based x-axis
- Customizable styling
Configuration Options:
- Series data format: `{ x: timestamp, y: value }`
- Tooltip customization with datetime formatting
- Grid line styling and colors
- Gradient fill properties
- Chart dimensions and responsiveness
#### `DeesChartLog`
Specialized chart component for visualizing log data and events.
@@ -1535,44 +1083,6 @@ Specialized chart component for visualizing log data and events.
></dees-chart-log>
```
Key Features:
- Event timeline visualization
- Color-coded event types
- Interactive event details
- Filtering capabilities
- Zoom and pan support
- Time-based navigation
- Event clustering
Common Use Cases:
- System monitoring
- Performance tracking
- Resource utilization
- Event logging
- Time-series analysis
- Trend visualization
Best Practices:
1. Data Formatting
- Use consistent timestamp formats
- Provide meaningful series names
- Include appropriate data points
2. Responsiveness
- Charts automatically adjust to container size
- Consider mobile viewports
- Set appropriate min/max dimensions
3. Interaction
- Enable relevant tooltips
- Configure meaningful click handlers
- Implement appropriate zoom levels
4. Styling
- Use consistent color schemes
- Configure appropriate grid lines
- Set readable font sizes
### Dialogs & Overlays Components
#### `DeesModal`
@@ -1616,14 +1126,6 @@ DeesModal.createAndShow({
></dees-modal>
```
Key Features:
- Backdrop blur effect
- Customizable content using HTML templates
- Flexible action buttons
- Outside click handling
- Animated transitions
- Automatic window layer management
#### `DeesContextmenu`
Context menu component for right-click actions.
@@ -1663,13 +1165,6 @@ const bubble = await DeesSpeechbubble.createAndShow(
></dees-speechbubble>
```
Key Features:
- Automatic positioning
- Non-intrusive overlay
- Animated appearance
- Reference element tracking
- Custom styling options
#### `DeesWindowlayer`
Base overlay component used by modal dialogs and other overlay components.
@@ -1692,43 +1187,6 @@ const layer = await DeesWindowLayer.createAndShow({
</dees-windowlayer>
```
Key Features:
- Backdrop blur support
- Click event handling
- Z-index management
- Animated transitions
- Flexible content container
Best Practices:
1. Modal Dialogs
- Use for important user interactions
- Provide clear action buttons
- Include close/cancel options
- Handle outside clicks appropriately
- Use meaningful headings
2. Context Menus
- Group related actions
- Use consistent icons
- Provide keyboard shortcuts
- Consider position constraints
- Handle menu item states
3. Speech Bubbles
- Keep content concise
- Position strategically
- Avoid overlapping
- Consider mobile viewports
- Use appropriate timing
4. Window Layers
- Manage z-index carefully
- Handle backdrop interactions
- Consider performance impact
- Implement proper cleanup
- Manage multiple layers
### Navigation Components
#### `DeesStepper`
@@ -1761,14 +1219,6 @@ Multi-step navigation component for guided user flows.
></dees-stepper>
```
Key Features:
- Linear or non-linear progression
- Step validation
- Progress tracking
- Customizable step content
- Navigation controls
- Step completion indicators
#### `DeesProgressbar`
Progress indicator component for tracking completion status.
@@ -1783,53 +1233,6 @@ Progress indicator component for tracking completion status.
></dees-progressbar>
```
Key Features:
- Determinate and indeterminate states
- Percentage display
- Custom styling options
- Status indicators
- Animation support
- Accessibility features
Best Practices:
1. Stepper Implementation
- Clear step labels
- Validation feedback
- Progress indication
- Error handling
- Consistent navigation
2. Progress Tracking
- Accurate progress calculation
- Clear visual feedback
- Status communication
- Performance monitoring
- Error state handling
Common Use Cases:
1. Stepper
- Multi-step forms
- User onboarding
- Checkout processes
- Setup wizards
- Tutorial flows
2. Progress Bar
- File uploads
- Process tracking
- Loading indicators
- Task completion
- Step progression
Accessibility Considerations:
- Keyboard navigation support
- ARIA labels and roles
- Focus management
- Screen reader compatibility
- Color contrast compliance
### Development Components
#### `DeesEditor`
@@ -1849,17 +1252,6 @@ Code editor component with syntax highlighting and code completion, powered by M
></dees-editor>
```
Key Features:
- Syntax highlighting for multiple languages
- Code completion
- Line numbers
- Minimap navigation
- Customizable options
- Theme support
- Search and replace
- Multiple cursors
- Code folding
#### `DeesEditorMarkdown`
Markdown editor component with live preview.
@@ -1876,16 +1268,6 @@ Markdown editor component with live preview.
></dees-editor-markdown>
```
Key Features:
- Live preview
- Toolbar for common formatting
- Markdown syntax highlighting
- Image upload support
- Table editor
- Customizable preview styles
- Spellcheck integration
- Auto-save functionality
#### `DeesEditorMarkdownoutlet`
Markdown preview component for rendering markdown content.
@@ -1898,14 +1280,6 @@ Markdown preview component for rendering markdown content.
></dees-editor-markdownoutlet>
```
Key Features:
- Safe markdown rendering
- Multiple themes
- Plugin support (mermaid diagrams, syntax highlighting)
- XSS protection
- Custom CSS injection
- Responsive images
#### `DeesTerminal`
Terminal emulator component for command-line interface.
@@ -1922,16 +1296,6 @@ Terminal emulator component for command-line interface.
></dees-terminal>
```
Key Features:
- Command history
- Custom commands
- Auto-completion
- Copy/paste support
- ANSI color support
- Scrollback buffer
- Keyboard shortcuts
- Command aliases
#### `DeesUpdater`
Component for managing application updates and version control.
@@ -1945,112 +1309,6 @@ Component for managing application updates and version control.
></dees-updater>
```
Key Features:
- Version checking
- Update notifications
- Progress tracking
- Automatic updates
- Rollback support
- Update scheduling
- Dependency management
Best Practices:
1. Code Editor Usage
- Configure appropriate language
- Set reasonable defaults
- Handle content changes
- Manage undo/redo stack
- Consider performance
2. Markdown Editing
- Provide clear toolbar
- Show live preview
- Handle image uploads
- Support shortcuts
- Maintain consistent styling
3. Terminal Implementation
- Clear command documentation
- Handle errors gracefully
- Provide command history
- Support common shortcuts
- Implement auto-completion
4. Update Management
- Regular version checks
- Clear update messaging
- Progress indication
- Error recovery
- User confirmation
Common Use Cases:
1. Code Editor
- Configuration editing
- Script development
- Code snippets
- Documentation
- Teaching tools
2. Markdown Editor
- Documentation
- Content creation
- README files
- Blog posts
- Release notes
3. Terminal
- Command execution
- System monitoring
- Development tools
- Debugging
- Training environments
4. Updater
- Application updates
- Plugin management
- Feature deployment
- Security patches
- Configuration updates
Integration Examples:
```typescript
// Combining components for a development environment
<dees-form>
<dees-editor
.language=${'javascript'}
.value=${code}
@change=${updatePreview}
></dees-editor>
<dees-terminal
.commands=${devCommands}
@command=${executeCommand}
></dees-terminal>
<dees-updater
.currentVersion=${appVersion}
@update-available=${notifyUser}
></dees-updater>
</dees-form>
```
Performance Considerations:
- Lazy loading of heavy components
- Memory management
- Resource cleanup
- Event handling optimization
- Efficient updates
Accessibility Features:
- Keyboard navigation
- Screen reader support
- High contrast themes
- Focus management
- ARIA attributes
### Auth & Utilities Components
#### `DeesSimpleAppdash`
@@ -2073,13 +1331,6 @@ Simple application dashboard component for quick prototyping.
</dees-simple-appdash>
```
Key Features:
- Quick setup dashboard layout
- Built-in navigation
- User profile section
- Responsive design
- Minimal configuration
#### `DeesSimpleLogin`
Simple login form component with validation and customization.
@@ -2096,15 +1347,6 @@ Simple login form component with validation and customization.
></dees-simple-login>
```
Key Features:
- Customizable fields
- Built-in validation
- Remember me option
- Forgot password link
- Custom branding
- Responsive layout
- Loading states
### Shopping Components
#### `DeesShoppingProductcard`
@@ -2133,41 +1375,6 @@ Product card component for e-commerce applications.
></dees-shopping-productcard>
```
Key Features:
- Product image with fallback icon
- Category label
- Product name and description
- Price display with original price strikethrough
- Stock status indicator
- Built-in quantity selector
- Selection mode for bulk operations
- Hover effects
- Responsive design
- Theme-aware styling
Product Data Interface:
```typescript
interface IProductData {
name: string;
category?: string;
description?: string;
price: number;
originalPrice?: number;
currency?: string;
inStock?: boolean;
stockText?: string;
imageUrl?: string;
iconName?: string;
}
```
Common Use Cases:
- Product listings
- Shopping carts
- Order summaries
- Product comparisons
- Wishlist displays
## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the license file within this repository.

Binary file not shown.

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env node
const fs = require('fs');
const path = require('path');
const projectRoot = path.resolve(__dirname, '..');
function resolveMonacoPackageJson() {
try {
const resolvedPath = require.resolve('monaco-editor/package.json', {
paths: [projectRoot],
});
return resolvedPath;
} catch (error) {
console.error('[dees-editor] Unable to resolve monaco-editor/package.json');
throw error;
}
}
function getMonacoVersion() {
const monacoPackagePath = resolveMonacoPackageJson();
const monacoPackage = require(monacoPackagePath);
if (!monacoPackage.version) {
throw new Error('[dees-editor] monaco-editor/package.json does not expose a version field');
}
return monacoPackage.version;
}
function writeVersionModule(version) {
const targetDir = path.join(projectRoot, 'ts_web', 'elements', 'dees-editor');
fs.mkdirSync(targetDir, { recursive: true });
const targetFile = path.join(targetDir, 'version.ts');
const fileContent = `// Auto-generated by scripts/update-monaco-version.cjs\nexport const MONACO_VERSION = '${version}';\n`;
fs.writeFileSync(targetFile, fileContent, 'utf8');
console.log(`[dees-editor] Wrote ${path.relative(projectRoot, targetFile)} with monaco-editor@${version}`);
}
try {
const version = getMonacoVersion();
writeVersionModule(version);
} catch (error) {
console.error('[dees-editor] Failed to update Monaco version module.');
console.error(error instanceof Error ? error.message : error);
process.exitCode = 1;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,28 @@
import { tap, expect } from '@push.rocks/tapbundle';
import {
resolveWidgetPlacement,
collectCollisions,
} from '../ts_web/elements/dees-dashboardgrid/layout.ts';
import type { DashboardWidget } from '../ts_web/elements/dees-dashboardgrid/types.ts';
tap.test('dashboardgrid does not overlap widgets after swap attempt', async () => {
const widgets: DashboardWidget[] = [
{ id: 'w0', x: 6, y: 5, w: 1, h: 3 },
{ id: 'w1', x: 6, y: 1, w: 1, h: 3 },
{ id: 'w2', x: 3, y: 0, w: 2, h: 2 },
{ id: 'w3', x: 9, y: 0, w: 1, h: 2 },
{ id: 'w4', x: 4, y: 3, w: 1, h: 2 },
];
const placement = resolveWidgetPlacement(widgets, 'w0', { x: 6, y: 3 }, 12);
expect(placement).toBeTruthy();
const layout = placement!.widgets;
for (const widget of layout) {
const collisions = collectCollisions(layout, widget, widget.x, widget.y, widget.w, widget.h);
expect(collisions).toBeEmptyArray();
}
});
export default tap.start();

View File

@@ -1,6 +1,6 @@
import { expect, tap, webhelpers } from '@push.rocks/tapbundle';
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
import { WysiwygSelection } from '../ts_web/elements/wysiwyg/wysiwyg.selection.js';
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
import { WysiwygSelection } from '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.selection.js';
tap.test('Shadow DOM containment should work correctly', async () => {
console.log('=== Testing Shadow DOM Containment ===');

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
tap.test('should create wysiwyg editor', async () => {
const editor = new DeesInputWysiwyg();

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
// Initialize the element
DeesInputWysiwyg;

View File

@@ -1,11 +1,11 @@
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
import * as deesCatalog from '../ts_web/index.js';
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
import { BlockRegistry } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/block.registry.js';
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
// Import block registration to ensure handlers are registered
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
import '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.blockregistration.js';
tap.test('Debug: should create empty wysiwyg block component', async () => {
try {

View File

@@ -1,11 +1,11 @@
import { tap, expect, webhelpers } from '@push.rocks/tapbundle';
import * as deesCatalog from '../ts_web/index.js';
import { BlockRegistry } from '../ts_web/elements/wysiwyg/blocks/block.registry.js';
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
import { BlockRegistry } from '../ts_web/elements/00group-input/dees-input-wysiwyg/blocks/block.registry.js';
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
// Import block registration to ensure handlers are registered
import '../ts_web/elements/wysiwyg/wysiwyg.blockregistration.js';
import '../ts_web/elements/00group-input/dees-input-wysiwyg/wysiwyg.blockregistration.js';
tap.test('BlockRegistry should have registered handlers', async () => {
// Test divider handler

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
// Initialize the element
DeesInputWysiwyg;

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
// Initialize the element
DeesInputWysiwyg;

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
// Initialize the element
DeesInputWysiwyg;

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
// Initialize the element
DeesInputWysiwyg;

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
// Initialize the element
DeesInputWysiwyg;

View File

@@ -1,5 +1,5 @@
import { expect, tap } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
// Initialize the element
DeesInputWysiwyg;

View File

@@ -1,6 +1,6 @@
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
tap.test('Keyboard: Arrow navigation between blocks', async () => {
const editor: DeesInputWysiwyg = await webhelpers.fixture(

View File

@@ -1,6 +1,6 @@
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
tap.test('Phase 3: Quote block should render and work correctly', async () => {
const editor: DeesInputWysiwyg = await webhelpers.fixture(

View File

@@ -1,6 +1,6 @@
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
tap.test('Selection highlighting should work consistently for all block types', async () => {
const editor: DeesInputWysiwyg = await webhelpers.fixture(

View File

@@ -1,6 +1,6 @@
import { expect, tap, webhelpers } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
tap.test('Selection highlighting basic test', async () => {
const editor: DeesInputWysiwyg = await webhelpers.fixture(

View File

@@ -1,7 +1,7 @@
import { tap, expect, webhelpers } from '@git.zone/tstest/tapbundle';
import { DeesInputWysiwyg } from '../ts_web/elements/wysiwyg/dees-input-wysiwyg.js';
import { DeesWysiwygBlock } from '../ts_web/elements/wysiwyg/dees-wysiwyg-block.js';
import { DeesInputWysiwyg } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-input-wysiwyg.js';
import { DeesWysiwygBlock } from '../ts_web/elements/00group-input/dees-input-wysiwyg/dees-wysiwyg-block.js';
tap.test('should split paragraph content on Enter key', async () => {
// Create the wysiwyg editor

View File

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

View File

@@ -1,4 +1,4 @@
import * as plugins from './00plugins.js';
import * as plugins from '../../00plugins.js';
import {
DeesElement,
type TemplateResult,
@@ -10,8 +10,8 @@ import {
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { DeesContextmenu } from './dees-contextmenu.js';
import './dees-icon.js';
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
import '../../dees-icon/dees-icon.js';
@customElement('dees-appui-activitylog')
export class DeesAppuiActivitylog extends DeesElement {
@@ -63,13 +63,14 @@ export class DeesAppuiActivitylog extends DeesElement {
.topbar {
position: absolute;
top: 0px;
height: 40px;
height: 48px;
width: 100%;
padding: 0px 16px;
background: ${cssManager.bdTheme('#ffffff', '#09090b')};
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
display: flex;
align-items: center;
box-sizing: border-box;
}
.topbar .heading {
@@ -81,7 +82,7 @@ export class DeesAppuiActivitylog extends DeesElement {
.activityContainer {
position: absolute;
top: 40px;
top: 48px;
bottom: 48px;
width: 100%;
padding: 12px 0px;
@@ -315,7 +316,7 @@ export class DeesAppuiActivitylog extends DeesElement {
position: absolute;
width: 100%;
height: 24px;
top: 40px;
top: 48px;
background: ${cssManager.bdTheme(
'linear-gradient(0deg, transparent 0%, #fafafa 100%)',
'linear-gradient(0deg, transparent 0%, #0a0a0a 100%)'

View File

@@ -0,0 +1 @@
export * from './dees-appui-activitylog.js';

View File

@@ -5,19 +5,19 @@ import {
property,
state,
html,
css,
cssManager,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import * as interfaces from './interfaces/index.js';
import * as plugins from './00plugins.js';
import { demoFunc } from './dees-appui-appbar.demo.js';
import * as interfaces from '../../interfaces/index.js';
import * as plugins from '../../00plugins.js';
import { demoFunc } from './demo.js';
import { appuiAppbarStyles } from './styles.js';
import { renderAppuiAppbar } from './template.js';
// Import required components
import './dees-icon.js';
import './dees-windowcontrols.js';
import './dees-appui-profiledropdown.js';
import '../../dees-icon/dees-icon.js';
import '../../dees-windowcontrols/dees-windowcontrols.js';
import '../dees-appui-profiledropdown/dees-appui-profiledropdown.js';
declare global {
interface HTMLElementTagNameMap {
@@ -31,301 +31,58 @@ export class DeesAppuiBar extends DeesElement {
// INSTANCE PROPERTIES
@property({ type: Array })
public menuItems: interfaces.IAppBarMenuItem[] = [];
accessor menuItems: interfaces.IAppBarMenuItem[] = [];
@property({ type: String })
public breadcrumbs: string = '';
accessor breadcrumbs: string = '';
@property({ type: String })
public breadcrumbSeparator: string = ' > ';
accessor breadcrumbSeparator: string = ' > ';
@property({ type: Boolean })
public showWindowControls: boolean = true;
accessor showWindowControls: boolean = true;
@property({ type: Object })
public user?: {
accessor user: {
name: string;
email?: string;
avatar?: string;
status?: 'online' | 'offline' | 'busy' | 'away';
};
} | undefined = undefined;
@property({ type: Array })
public profileMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = [];
accessor profileMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = [];
@property({ type: Boolean })
public showSearch: boolean = false;
accessor showSearch: boolean = false;
// STATE
@state()
private activeMenu: string | null = null;
accessor activeMenu: string | null = null;
@state()
private openDropdowns: Set<string> = new Set();
accessor openDropdowns: Set<string> = new Set();
@state()
private focusedItem: string | null = null;
accessor focusedItem: string | null = null;
@state()
private focusedDropdownItem: number = -1;
accessor focusedDropdownItem: number = -1;
@state()
private isProfileDropdownOpen: boolean = false;
accessor isProfileDropdownOpen: boolean = false;
public static styles = [
cssManager.defaultStyles,
css`
:host {
/* CSS Variables for theming */
--appbar-height: 40px;
--appbar-font-size: 12px;
display: block;
position: relative;
width: 100%;
height: var(--appbar-height);
border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
background: ${cssManager.bdTheme('#ffffff', '#000000')};
color: ${cssManager.bdTheme('#00000080', '#ffffff80')};
font-size: var(--appbar-font-size);
display: grid;
grid-template-columns: ${cssManager.cssGridColumns(3, 20)};
-webkit-app-region: drag;
user-select: none;
}
.menus {
display: flex;
align-items: center;
gap: 4px;
padding: 0 8px;
cursor: default;
}
.menuItem {
position: relative;
line-height: 24px;
padding: 0px 12px;
margin: 8px 0px;
border-radius: 4px;
-webkit-app-region: no-drag;
transition: all 0.2s ease;
cursor: default;
outline: none;
display: flex;
align-items: center;
gap: 4px;
}
/* Optional: Style for menu items with icons (not typically used for top-level items) */
.menuItem dees-icon {
font-size: 14px;
opacity: 0.8;
}
.menuItem:hover {
background: ${cssManager.bdTheme('#00000010', '#ffffff20')};
color: ${cssManager.bdTheme('#000000', '#ffffff')};
}
.menuItem.active {
background: ${cssManager.bdTheme('#00000020', '#ffffff30')};
color: ${cssManager.bdTheme('#000000', '#ffffff')};
}
.menuItem[disabled] {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
.menuItem:focus-visible {
box-shadow: 0 0 0 2px ${cssManager.bdTheme('#00000080', '#ffffff80')};
}
/* Dropdown styles */
.dropdown {
position: absolute;
top: 100%;
left: 0;
min-width: 200px;
background: ${cssManager.bdTheme('#ffffff', '#000000')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
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;
z-index: 1000;
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.2s, transform 0.2s;
pointer-events: none;
}
.dropdown.open {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.dropdown-item {
padding: 8px 16px;
cursor: default;
display: flex;
align-items: center;
gap: 8px;
transition: background 0.1s;
}
.dropdown-item:hover,
.dropdown-item.focused {
background: ${cssManager.bdTheme('#00000010', '#ffffff20')};
}
.dropdown-divider {
height: 1px;
background: ${cssManager.bdTheme('#e0e0e0', '#202020')};
margin: 4px 0;
}
.dropdown-item[disabled] {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
.dropdown-item .shortcut {
margin-left: auto;
opacity: 0.6;
font-size: 11px;
}
/* Breadcrumbs */
.breadcrumbs {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 0 16px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.breadcrumb-item {
color: ${cssManager.bdTheme('#00000080', '#ffffff80')};
cursor: default;
transition: color 0.2s;
}
.breadcrumb-item:hover {
color: ${cssManager.bdTheme('#000000', '#ffffff')};
}
.breadcrumb-separator {
margin: 0 8px;
opacity: 0.5;
}
/* Account section */
.account {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 16px;
gap: 12px;
}
.search-icon {
cursor: default;
opacity: 0.7;
transition: opacity 0.2s;
}
.search-icon:hover {
opacity: 1;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
cursor: default;
padding: 4px 8px;
border-radius: 4px;
transition: background 0.2s;
}
.user-info:hover {
background: ${cssManager.bdTheme('#00000010', '#ffffff20')};
}
.user-avatar {
position: relative;
width: 24px;
height: 24px;
border-radius: 50%;
background: ${cssManager.bdTheme('#00000020', '#ffffff30')};
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: bold;
}
.user-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.user-status {
position: absolute;
bottom: -2px;
right: -2px;
width: 8px;
height: 8px;
border-radius: 50%;
border: 2px solid ${cssManager.bdTheme('#ffffff', '#000000')};
}
.user-status.online {
background: #4caf50;
}
.user-status.offline {
background: #757575;
}
.user-status.busy {
background: #f44336;
}
.user-status.away {
background: #ff9800;
}
`,
];
public static styles = appuiAppbarStyles;
// INSTANCE
public render(): TemplateResult {
return html`
<div class="menus">
${this.showWindowControls ? html`<dees-windowcontrols></dees-windowcontrols>` : ''}
${this.renderMenuItems()}
</div>
<div class="breadcrumbs">
${this.renderBreadcrumbs()}
</div>
<div class="account">
${this.renderAccountSection()}
</div>
`;
return renderAppuiAppbar(this);
}
private renderMenuItems(): TemplateResult {
public renderMenuItems(): TemplateResult {
return html`
${this.menuItems.map((item, index) => this.renderMenuItem(item, `menu-${index}`))}
`;
@@ -398,7 +155,7 @@ export class DeesAppuiBar extends DeesElement {
`;
}
private renderBreadcrumbs(): TemplateResult {
public renderBreadcrumbs(): TemplateResult {
if (!this.breadcrumbs) {
return html``;
}
@@ -417,7 +174,7 @@ export class DeesAppuiBar extends DeesElement {
`;
}
private renderAccountSection(): TemplateResult {
public renderAccountSection(): TemplateResult {
return html`
${this.showSearch ? html`
<dees-icon

View File

@@ -1,7 +1,8 @@
import { html, css } from '@design.estate/dees-element';
import type { DeesAppuiBar } from './dees-appui-appbar.js';
import type { IAppBarMenuItem } from './interfaces/appbarmenuitem.js';
import type { DeesAppuiBar } from './component.js';
import type { IAppBarMenuItem } from '../../interfaces/appbarmenuitem.js';
import '@design.estate/dees-wcctools/demotools';
import './component.js';
export const demoFunc = () => {
// Sample menu items with various configurations

View File

@@ -0,0 +1 @@
export * from './component.js';

View File

@@ -0,0 +1,238 @@
import { css, cssManager } from '@design.estate/dees-element';
export const appuiAppbarStyles = [
cssManager.defaultStyles,
css`
:host {
/* CSS Variables for theming */
--appbar-height: 40px;
--appbar-font-size: 12px;
display: block;
position: relative;
width: 100%;
height: var(--appbar-height);
border-bottom: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
background: ${cssManager.bdTheme('#ffffff', '#000000')};
color: ${cssManager.bdTheme('#00000080', '#ffffff80')};
font-size: var(--appbar-font-size);
display: grid;
grid-template-columns: ${cssManager.cssGridColumns(3, 20)};
-webkit-app-region: drag;
user-select: none;
}
.menus {
display: flex;
align-items: center;
gap: 4px;
padding: 0 8px;
cursor: default;
}
.menuItem {
position: relative;
line-height: 24px;
padding: 0px 12px;
margin: 8px 0px;
border-radius: 4px;
-webkit-app-region: no-drag;
transition: all 0.2s ease;
cursor: default;
outline: none;
display: flex;
align-items: center;
gap: 4px;
}
/* Optional: Style for menu items with icons (not typically used for top-level items) */
.menuItem dees-icon {
font-size: 14px;
opacity: 0.8;
}
.menuItem:hover {
background: ${cssManager.bdTheme('#00000010', '#ffffff20')};
color: ${cssManager.bdTheme('#000000', '#ffffff')};
}
.menuItem.active {
background: ${cssManager.bdTheme('#00000020', '#ffffff30')};
color: ${cssManager.bdTheme('#000000', '#ffffff')};
}
.menuItem[disabled] {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
.menuItem:focus-visible {
box-shadow: 0 0 0 2px ${cssManager.bdTheme('#00000080', '#ffffff80')};
}
/* Dropdown styles */
.dropdown {
position: absolute;
top: 100%;
left: 0;
min-width: 200px;
background: ${cssManager.bdTheme('#ffffff', '#000000')};
border: 1px solid ${cssManager.bdTheme('#e0e0e0', '#202020')};
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;
z-index: 1000;
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.2s, transform 0.2s;
pointer-events: none;
}
.dropdown.open {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
.dropdown-item {
padding: 8px 16px;
cursor: default;
display: flex;
align-items: center;
gap: 8px;
transition: background 0.1s;
}
.dropdown-item:hover,
.dropdown-item.focused {
background: ${cssManager.bdTheme('#00000010', '#ffffff20')};
}
.dropdown-divider {
height: 1px;
background: ${cssManager.bdTheme('#e0e0e0', '#202020')};
margin: 4px 0;
}
.dropdown-item[disabled] {
opacity: 0.5;
cursor: not-allowed;
pointer-events: none;
}
.dropdown-item .shortcut {
margin-left: auto;
opacity: 0.6;
font-size: 11px;
}
/* Breadcrumbs */
.breadcrumbs {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
padding: 0 16px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.breadcrumb-item {
color: ${cssManager.bdTheme('#00000080', '#ffffff80')};
cursor: default;
transition: color 0.2s;
}
.breadcrumb-item:hover {
color: ${cssManager.bdTheme('#000000', '#ffffff')};
}
.breadcrumb-separator {
margin: 0 8px;
opacity: 0.5;
}
/* Account section */
.account {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 0 16px;
gap: 12px;
}
.search-icon {
cursor: default;
opacity: 0.7;
transition: opacity 0.2s;
}
.search-icon:hover {
opacity: 1;
}
.user-info {
display: flex;
align-items: center;
gap: 8px;
cursor: default;
padding: 4px 8px;
border-radius: 4px;
transition: background 0.2s;
}
.user-info:hover {
background: ${cssManager.bdTheme('#00000010', '#ffffff20')};
}
.user-avatar {
position: relative;
width: 24px;
height: 24px;
border-radius: 50%;
background: ${cssManager.bdTheme('#00000020', '#ffffff30')};
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: bold;
}
.user-avatar img {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
}
.user-status {
position: absolute;
bottom: -2px;
right: -2px;
width: 8px;
height: 8px;
border-radius: 50%;
border: 2px solid ${cssManager.bdTheme('#ffffff', '#000000')};
}
.user-status.online {
background: #4caf50;
}
.user-status.offline {
background: #757575;
}
.user-status.busy {
background: #f44336;
}
.user-status.away {
background: #ff9800;
}
`,
];

View File

@@ -0,0 +1,18 @@
import { html, type TemplateResult } from '@design.estate/dees-element';
import type { DeesAppuiBar } from './component.js';
export const renderAppuiAppbar = (component: DeesAppuiBar): TemplateResult => {
return html`
<div class="menus">
${component.showWindowControls ? html`<dees-windowcontrols></dees-windowcontrols>` : ''}
${component.renderMenuItems()}
</div>
<div class="breadcrumbs">
${component.renderBreadcrumbs()}
</div>
<div class="account">
${component.renderAccountSection()}
</div>
`;
};

View File

@@ -0,0 +1,271 @@
import type { IRoutingConfig, IViewDefinition } from '../../interfaces/appconfig.js';
import type { ViewRegistry } from './view.registry.js';
export type TRouteChangeCallback = (viewId: string, params?: Record<string, string>) => void;
/**
* Router for managing view navigation and URL synchronization
*/
export class AppRouter {
private config: Required<Omit<IRoutingConfig, 'notFound'>> & Pick<IRoutingConfig, 'notFound'>;
private viewRegistry: ViewRegistry;
private listeners: Set<TRouteChangeCallback> = new Set();
private currentViewId: string | null = null;
private isInitialized: boolean = false;
constructor(config: IRoutingConfig, viewRegistry: ViewRegistry) {
this.config = {
mode: config.mode,
basePath: config.basePath || '',
defaultView: config.defaultView || '',
syncUrl: config.syncUrl ?? true,
notFound: config.notFound,
};
this.viewRegistry = viewRegistry;
}
/**
* Initialize the router
*/
public init(): void {
if (this.isInitialized) return;
if (this.config.mode === 'hash') {
window.addEventListener('hashchange', this.handleHashChange);
// Check initial hash
const initialView = this.getViewFromHash();
if (initialView) {
this.navigate(initialView, { source: 'initial' });
} else if (this.config.defaultView) {
this.navigate(this.config.defaultView, { source: 'initial' });
}
} else if (this.config.mode === 'history') {
window.addEventListener('popstate', this.handlePopState);
// Check initial path
const initialView = this.getViewFromPath();
if (initialView) {
this.navigate(initialView, { source: 'initial' });
} else if (this.config.defaultView) {
this.navigate(this.config.defaultView, { source: 'initial' });
}
} else if (this.config.mode === 'none' && this.config.defaultView) {
this.navigate(this.config.defaultView, { source: 'initial' });
}
// For 'external' mode, we don't set up listeners - the external router handles it
this.isInitialized = true;
}
/**
* Navigate to a view by ID
*/
public navigate(
viewId: string,
options: {
source?: 'navigation' | 'popstate' | 'initial' | 'programmatic';
replace?: boolean;
params?: Record<string, string>;
} = {}
): boolean {
const { source = 'programmatic', replace = false, params } = options;
const view = this.viewRegistry.get(viewId);
if (!view) {
console.warn(`Cannot navigate to unknown view: ${viewId}`);
if (this.config.notFound) {
if (typeof this.config.notFound === 'function') {
this.config.notFound();
} else {
return this.navigate(this.config.notFound, { source, replace: true });
}
}
return false;
}
const previousViewId = this.currentViewId;
this.currentViewId = viewId;
// Update URL if configured
if (this.config.syncUrl && this.config.mode !== 'none' && this.config.mode !== 'external') {
this.updateUrl(view, replace);
}
// Notify listeners
this.notifyListeners(viewId, params);
return true;
}
/**
* Navigate back in history
*/
public back(): void {
if (this.config.mode === 'hash' || this.config.mode === 'history') {
window.history.back();
}
}
/**
* Navigate forward in history
*/
public forward(): void {
if (this.config.mode === 'hash' || this.config.mode === 'history') {
window.history.forward();
}
}
/**
* Get current view ID
*/
public getCurrentViewId(): string | null {
return this.currentViewId;
}
/**
* Add a route change listener
*/
public onRouteChange(callback: TRouteChangeCallback): () => void {
this.listeners.add(callback);
return () => this.listeners.delete(callback);
}
/**
* Handle external navigation (for external router mode)
*/
public handleExternalNavigation(viewId: string, params?: Record<string, string>): void {
if (this.config.mode !== 'external') {
console.warn('handleExternalNavigation should only be used in external mode');
}
const previousViewId = this.currentViewId;
this.currentViewId = viewId;
this.notifyListeners(viewId, params);
}
/**
* Sync state with URL (for external router integration)
*/
public syncWithUrl(): string | null {
if (this.config.mode === 'hash') {
return this.getViewFromHash();
} else if (this.config.mode === 'history') {
return this.getViewFromPath();
}
return null;
}
/**
* Get the current route from the URL
*/
public getCurrentRoute(): string {
if (this.config.mode === 'hash') {
return window.location.hash.slice(1) || '';
} else if (this.config.mode === 'history') {
let path = window.location.pathname;
if (this.config.basePath && path.startsWith(this.config.basePath)) {
path = path.slice(this.config.basePath.length);
}
return path.replace(/^\//, '');
}
return '';
}
/**
* Build a URL for a view
*/
public buildUrl(viewId: string): string {
const view = this.viewRegistry.get(viewId);
const route = view?.route || viewId;
if (this.config.mode === 'hash') {
return `#${route}`;
} else if (this.config.mode === 'history') {
return `${this.config.basePath}/${route}`;
}
return '';
}
/**
* Destroy the router
*/
public destroy(): void {
if (this.config.mode === 'hash') {
window.removeEventListener('hashchange', this.handleHashChange);
} else if (this.config.mode === 'history') {
window.removeEventListener('popstate', this.handlePopState);
}
this.listeners.clear();
this.isInitialized = false;
}
// Private methods
private handleHashChange = (): void => {
const viewId = this.getViewFromHash();
if (viewId && viewId !== this.currentViewId) {
this.navigate(viewId, { source: 'popstate' });
}
};
private handlePopState = (): void => {
const viewId = this.getViewFromPath();
if (viewId && viewId !== this.currentViewId) {
this.navigate(viewId, { source: 'popstate' });
}
};
private getViewFromHash(): string | null {
const hash = window.location.hash.slice(1); // Remove #
if (!hash) return null;
// Try to find view by route
const view = this.viewRegistry.findByRoute(hash);
return view?.id || null;
}
private getViewFromPath(): string | null {
let path = window.location.pathname;
// Remove base path if configured
if (this.config.basePath) {
if (path.startsWith(this.config.basePath)) {
path = path.slice(this.config.basePath.length);
}
}
// Remove leading slash
path = path.replace(/^\//, '');
if (!path) return null;
const view = this.viewRegistry.findByRoute(path);
return view?.id || null;
}
private updateUrl(view: IViewDefinition, replace: boolean): void {
const route = view.route || view.id;
if (this.config.mode === 'hash') {
const newHash = `#${route}`;
if (replace) {
window.history.replaceState(null, '', newHash);
} else {
window.history.pushState(null, '', newHash);
}
} else if (this.config.mode === 'history') {
const basePath = this.config.basePath || '';
const newPath = `${basePath}/${route}`;
if (replace) {
window.history.replaceState({ viewId: view.id }, '', newPath);
} else {
window.history.pushState({ viewId: view.id }, '', newPath);
}
}
}
private notifyListeners(viewId: string, params?: Record<string, string>): void {
for (const listener of this.listeners) {
listener(viewId, params);
}
}
}

View File

@@ -0,0 +1,238 @@
import { html, css } from '@design.estate/dees-element';
import type { DeesAppuiBase } from '../dees-appui-base/dees-appui-base.js';
import type { IAppBarMenuItem } from '../../interfaces/appbarmenuitem.js';
import type { ITab } from '../../interfaces/tab.js';
import type { ISelectionOption } from '../../interfaces/selectionoption.js';
import type { IMenuGroup } from '../../interfaces/menugroup.js';
import type { ISecondaryMenuGroup } from '../../interfaces/secondarymenu.js';
import * as plugins from '../../00plugins.js';
import '@design.estate/dees-wcctools/demotools';
export const demoFunc = () => {
// Menu items for the appbar
const menuItems: IAppBarMenuItem[] = [
{
name: 'File',
action: async () => {},
submenu: [
{ name: 'New Project', shortcut: 'Cmd+N', iconName: 'filePlus', action: async () => console.log('New project') },
{ name: 'Open Project...', shortcut: 'Cmd+O', iconName: 'folderOpen', action: async () => console.log('Open project') },
{ name: 'Recent Projects', action: async () => {}, submenu: [
{ name: 'my-app', action: async () => console.log('Open my-app') },
{ name: 'component-lib', action: async () => console.log('Open component-lib') },
{ name: 'api-server', action: async () => console.log('Open api-server') },
]},
{ divider: true },
{ name: 'Save All', shortcut: 'Cmd+Shift+S', iconName: 'save', action: async () => console.log('Save all') },
{ divider: true },
{ name: 'Close Project', action: async () => console.log('Close project') },
]
},
{
name: 'Edit',
action: async () => {},
submenu: [
{ name: 'Undo', shortcut: 'Cmd+Z', iconName: 'undo', action: async () => console.log('Undo') },
{ name: 'Redo', shortcut: 'Cmd+Shift+Z', iconName: 'redo', action: async () => console.log('Redo') },
{ divider: true },
{ name: 'Cut', shortcut: 'Cmd+X', iconName: 'scissors', action: async () => console.log('Cut') },
{ name: 'Copy', shortcut: 'Cmd+C', iconName: 'copy', action: async () => console.log('Copy') },
{ name: 'Paste', shortcut: 'Cmd+V', iconName: 'clipboard', action: async () => console.log('Paste') },
]
},
{
name: 'View',
action: async () => {},
submenu: [
{ name: 'Toggle Sidebar', shortcut: 'Cmd+B', action: async () => console.log('Toggle sidebar') },
{ name: 'Toggle Terminal', shortcut: 'Cmd+J', iconName: 'terminal', action: async () => console.log('Toggle terminal') },
{ divider: true },
{ name: 'Zoom In', shortcut: 'Cmd++', iconName: 'zoomIn', action: async () => console.log('Zoom in') },
{ name: 'Zoom Out', shortcut: 'Cmd+-', iconName: 'zoomOut', action: async () => console.log('Zoom out') },
{ name: 'Reset Zoom', shortcut: 'Cmd+0', action: async () => console.log('Reset zoom') },
]
},
{
name: 'Help',
action: async () => {},
submenu: [
{ name: 'Documentation', iconName: 'book', action: async () => console.log('Documentation') },
{ name: 'Release Notes', iconName: 'fileText', action: async () => console.log('Release notes') },
{ divider: true },
{ name: 'Report Issue', iconName: 'bug', action: async () => console.log('Report issue') },
{ name: 'About', iconName: 'info', action: async () => console.log('About') },
]
}
];
// Main menu groups (left sidebar)
const mainMenuGroups: IMenuGroup[] = [
{
tabs: [
{ key: 'Dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard selected') },
{ key: 'Inbox', iconName: 'lucide:inbox', action: () => console.log('Inbox selected') },
]
},
{
name: 'Workspace',
tabs: [
{ key: 'Projects', iconName: 'lucide:folder', action: () => console.log('Projects selected') },
{ key: 'Tasks', iconName: 'lucide:checkSquare', action: () => console.log('Tasks selected') },
{ key: 'Documents', iconName: 'lucide:fileText', action: () => console.log('Documents selected') },
]
},
{
name: 'Analytics',
tabs: [
{ key: 'Reports', iconName: 'lucide:barChart3', action: () => console.log('Reports selected') },
{ key: 'Insights', iconName: 'lucide:lightbulb', action: () => console.log('Insights selected') },
]
}
];
// Main menu bottom tabs (pinned to bottom)
const mainMenuBottomTabs: ITab[] = [
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings selected') },
{ key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help selected') },
];
// Secondary menu groups (second sidebar with collapsible groups)
// These showcase the new shadcn-style design with badges and collapsible sections
const secondaryMenuGroups: ISecondaryMenuGroup[] = [
{
name: 'Quick Access',
iconName: 'lucide:zap',
items: [
{ key: 'Overview', iconName: 'layoutDashboard', action: () => console.log('Overview selected') },
{ key: 'Recent Activity', iconName: 'clock', action: () => console.log('Recent Activity selected'), badge: 5 },
{ key: 'Favorites', iconName: 'star', action: () => console.log('Favorites selected') },
]
},
{
name: 'Resources',
iconName: 'lucide:layers',
items: [
{ key: 'Components', iconName: 'package', action: () => console.log('Components selected'), badge: 24 },
{ key: 'Services', iconName: 'server', action: () => console.log('Services selected'), badge: 'new', badgeVariant: 'success' },
{ key: 'APIs', iconName: 'globe', action: () => console.log('APIs selected'), badge: 3, badgeVariant: 'warning' },
{ key: 'Webhooks', iconName: 'webhook', action: () => console.log('Webhooks selected') },
]
},
{
name: 'Data Management',
iconName: 'lucide:database',
items: [
{ key: 'Database', iconName: 'database', action: () => console.log('Database selected') },
{ key: 'Storage', iconName: 'hardDrive', action: () => console.log('Storage selected'), badge: '85%', badgeVariant: 'warning' },
{ key: 'Backups', iconName: 'archive', action: () => console.log('Backups selected'), badge: 'OK', badgeVariant: 'success' },
]
},
{
name: 'System',
iconName: 'lucide:settings',
collapsed: true,
items: [
{ key: 'Configuration', iconName: 'sliders', action: () => console.log('Configuration selected') },
{ key: 'Integrations', iconName: 'plug', action: () => console.log('Integrations selected'), badge: 2, badgeVariant: 'error' },
{ key: 'Permissions', iconName: 'shield', action: () => console.log('Permissions selected') },
{ key: 'Logs', iconName: 'fileText', action: () => console.log('Logs selected') },
]
}
];
// Main content tabs
const mainContentTabs: ITab[] = [
{ key: 'Details', iconName: 'lucide:file', action: () => console.log('Details tab') },
{ key: 'Logs', iconName: 'lucide:list', action: () => console.log('Logs tab') },
{ key: 'Metrics', iconName: 'lucide:lineChart', action: () => console.log('Metrics tab') },
];
// Profile menu items
const profileMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = [
{ name: 'Profile Settings', iconName: 'user', action: async () => console.log('Profile settings') },
{ name: 'Account', iconName: 'settings', action: async () => console.log('Account settings') },
{ divider: true },
{ name: 'Help & Support', iconName: 'helpCircle', action: async () => console.log('Help') },
{ name: 'Keyboard Shortcuts', iconName: 'keyboard', shortcut: 'Cmd+K', action: async () => console.log('Shortcuts') },
{ divider: true },
{ name: 'Sign Out', iconName: 'logOut', action: async () => console.log('Sign out') }
];
return html`
<dees-demowrapper>
<style>
${css`
.demo-container {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 100%;
overflow: hidden;
}
`}
</style>
<div class="demo-container">
<dees-appui-base
.appbarMenuItems=${menuItems}
.appbarBreadcrumbs=${'Dashboard'}
.appbarUser=${{
name: 'Jane Smith',
email: 'jane.smith@example.com',
status: 'online' as 'online' | 'offline' | 'busy' | 'away'
}}
.appbarProfileMenuItems=${profileMenuItems}
.appbarShowWindowControls=${true}
.appbarShowSearch=${true}
.mainmenuLogoIcon=${'lucide:box'}
.mainmenuLogoText=${'Acme App'}
.mainmenuGroups=${mainMenuGroups}
.mainmenuBottomTabs=${mainMenuBottomTabs}
.secondarymenuHeading=${'Dashboard'}
.secondarymenuGroups=${secondaryMenuGroups}
.maincontentTabs=${mainContentTabs}
@appbar-menu-select=${(e: CustomEvent) => console.log('Menu selected:', e.detail)}
@appbar-breadcrumb-navigate=${(e: CustomEvent) => console.log('Breadcrumb:', e.detail)}
@appbar-search-click=${() => console.log('Search clicked')}
@appbar-user-menu-open=${() => console.log('User menu opened')}
@appbar-profile-menu-select=${(e: CustomEvent) => console.log('Profile menu selected:', e.detail)}
@mainmenu-tab-select=${(e: CustomEvent) => console.log('Tab selected:', e.detail)}
@secondarymenu-item-select=${(e: CustomEvent) => console.log('Item selected:', e.detail)}
>
<div slot="maincontent" style="padding: 40px; color: #a3a3a3; font-family: 'Geist Sans', 'Inter', -apple-system, sans-serif;">
<h1 style="color: #fafafa; font-weight: 600; font-size: 24px; margin-bottom: 8px;">Welcome to Acme App</h1>
<p style="color: #737373; margin-bottom: 32px;">This demo showcases the AppUI component system with the new SecondaryMenu.</p>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px; margin-bottom: 32px;">
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 20px;">
<h3 style="color: #fafafa; font-size: 14px; font-weight: 600; margin-bottom: 8px;">SecondaryMenu Features</h3>
<ul style="margin: 0; padding-left: 20px; font-size: 13px; line-height: 1.8;">
<li>Collapsible groups with smooth animations</li>
<li>Badge support (counts, status, variants)</li>
<li>Dynamic heading from MainMenu selection</li>
<li>shadcn-inspired modern design</li>
</ul>
</div>
<div style="background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 20px;">
<h3 style="color: #fafafa; font-size: 14px; font-weight: 600; margin-bottom: 8px;">Badge Variants</h3>
<div style="display: flex; flex-wrap: wrap; gap: 8px; font-size: 12px;">
<span style="background: #27272a; color: #a1a1aa; padding: 2px 8px; border-radius: 9px;">default</span>
<span style="background: #14532d; color: #4ade80; padding: 2px 8px; border-radius: 9px;">success</span>
<span style="background: #451a03; color: #fbbf24; padding: 2px 8px; border-radius: 9px;">warning</span>
<span style="background: #450a0a; color: #f87171; padding: 2px 8px; border-radius: 9px;">error</span>
</div>
</div>
</div>
<p style="font-size: 13px; color: #525252;">
Try clicking items in the MainMenu (left) - the SecondaryMenu heading updates automatically.
Click group headers in the SecondaryMenu to collapse/expand sections.
</p>
</div>
</dees-appui-base>
</div>
</dees-demowrapper>
`;
};

View File

@@ -0,0 +1,582 @@
import {
DeesElement,
type TemplateResult,
property,
customElement,
html,
css,
cssManager,
state,
} from '@design.estate/dees-element';
import * as interfaces from '../../interfaces/index.js';
import * as plugins from '../../00plugins.js';
import type { DeesAppuiBar } from '../dees-appui-appbar/index.js';
import type { DeesAppuiMainmenu } from '../dees-appui-mainmenu/dees-appui-mainmenu.js';
import type { DeesAppuiSecondarymenu } from '../dees-appui-secondarymenu/dees-appui-secondarymenu.js';
import type { DeesAppuiMaincontent } from '../dees-appui-maincontent/dees-appui-maincontent.js';
import type { DeesAppuiActivitylog } from '../dees-appui-activitylog/dees-appui-activitylog.js';
import { demoFunc } from './dees-appui-base.demo.js';
// New module imports
import { ViewRegistry } from './view.registry.js';
import { AppRouter } from './app.router.js';
import { StateManager } from './state.manager.js';
// Import child components
import '../dees-appui-appbar/index.js';
import '../dees-appui-mainmenu/dees-appui-mainmenu.js';
import '../dees-appui-secondarymenu/dees-appui-secondarymenu.js';
import '../dees-appui-maincontent/dees-appui-maincontent.js';
import '../dees-appui-activitylog/dees-appui-activitylog.js';
@customElement('dees-appui-base')
export class DeesAppuiBase extends DeesElement {
public static demo = demoFunc;
// Properties for appbar
@property({ type: Array })
accessor appbarMenuItems: interfaces.IAppBarMenuItem[] = [];
@property({ type: String })
accessor appbarBreadcrumbs: string = '';
@property({ type: String })
accessor appbarBreadcrumbSeparator: string = ' > ';
@property({ type: Boolean })
accessor appbarShowWindowControls: boolean = true;
@property({ type: Object })
accessor appbarUser: {
name: string;
email?: string;
avatar?: string;
status?: 'online' | 'offline' | 'busy' | 'away';
} | undefined = undefined;
@property({ type: Array })
accessor appbarProfileMenuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = [];
@property({ type: Boolean })
accessor appbarShowSearch: boolean = false;
// Properties for mainmenu
@property({ type: String })
accessor mainmenuLogoIcon: string = '';
@property({ type: String })
accessor mainmenuLogoText: string = '';
@property({ type: Array })
accessor mainmenuGroups: interfaces.IMenuGroup[] = [];
@property({ type: Array })
accessor mainmenuBottomTabs: interfaces.ITab[] = [];
@property({ type: Array })
accessor mainmenuTabs: interfaces.ITab[] = [];
@property({ type: Object })
accessor mainmenuSelectedTab: interfaces.ITab | undefined = undefined;
// Properties for secondarymenu
@property({ type: String })
accessor secondarymenuHeading: string = 'Menu';
@property({ type: Array })
accessor secondarymenuGroups: interfaces.ISecondaryMenuGroup[] = [];
@property({ type: Object })
accessor secondarymenuSelectedItem: interfaces.ISecondaryMenuItem | undefined = undefined;
/** Legacy support for flat options (backward compatibility) */
@property({ type: Array })
accessor secondarymenuOptions: (interfaces.ISelectionOption | { divider: true })[] = [];
// Collapse states
@property({ type: Boolean })
accessor mainmenuCollapsed: boolean = false;
@property({ type: Boolean })
accessor secondarymenuCollapsed: boolean = false;
// Properties for maincontent
@property({ type: Array })
accessor maincontentTabs: interfaces.ITab[] = [];
// References to child components
@state()
accessor appbar: DeesAppuiBar | undefined = undefined;
@state()
accessor mainmenu: DeesAppuiMainmenu | undefined = undefined;
@state()
accessor secondarymenu: DeesAppuiSecondarymenu | undefined = undefined;
@state()
accessor maincontent: DeesAppuiMaincontent | undefined = undefined;
@state()
accessor activitylog: DeesAppuiActivitylog | undefined = undefined;
// NEW: Unified config property
@property({ type: Object })
accessor config: interfaces.IAppConfig | undefined = undefined;
// NEW: Current view state
@state()
accessor currentView: interfaces.IViewDefinition | undefined = undefined;
// NEW: Internal services (not reactive, managed internally)
private viewRegistry: ViewRegistry = new ViewRegistry();
private router: AppRouter | null = null;
private stateManager: StateManager | null = null;
public static styles = [
cssManager.defaultStyles,
css`
:host {
position: absolute;
height: 100%;
width: 100%;
background: ${cssManager.bdTheme('#f0f0f0', '#1a1a1a')};
}
.maingrid {
position: absolute;
top: 40px;
height: calc(100% - 40px);
width: 100%;
display: grid;
grid-template-columns: auto auto 1fr 240px;
grid-template-rows: 1fr;
}
/* Z-index layering for proper stacking (position: relative required for z-index to work) */
.maingrid > dees-appui-mainmenu {
position: relative;
z-index: 3;
}
.maingrid > dees-appui-secondarymenu {
position: relative;
z-index: 2;
}
.maingrid > dees-appui-maincontent {
position: relative;
z-index: 1;
}
.maingrid > dees-appui-activitylog {
position: relative;
z-index: 1;
}
/* View container for dynamically loaded views */
.view-container {
display: contents;
}
.view-container:empty {
display: none;
}
`,
];
// INSTANCE
public render(): TemplateResult {
return html`
<style></style>
<dees-appui-appbar
.menuItems=${this.appbarMenuItems}
.breadcrumbs=${this.appbarBreadcrumbs}
.breadcrumbSeparator=${this.appbarBreadcrumbSeparator}
.showWindowControls=${this.appbarShowWindowControls}
.user=${this.appbarUser}
.profileMenuItems=${this.appbarProfileMenuItems}
.showSearch=${this.appbarShowSearch}
@menu-select=${(e: CustomEvent) => this.handleAppbarMenuSelect(e)}
@breadcrumb-navigate=${(e: CustomEvent) => this.handleAppbarBreadcrumbNavigate(e)}
@search-click=${() => this.handleAppbarSearchClick()}
@user-menu-open=${() => this.handleAppbarUserMenuOpen()}
@profile-menu-select=${(e: CustomEvent) => this.handleAppbarProfileMenuSelect(e)}
></dees-appui-appbar>
<div class="maingrid">
<dees-appui-mainmenu
.logoIcon=${this.mainmenuLogoIcon}
.logoText=${this.mainmenuLogoText}
.menuGroups=${this.mainmenuGroups}
.bottomTabs=${this.mainmenuBottomTabs}
.tabs=${this.mainmenuTabs}
.selectedTab=${this.mainmenuSelectedTab}
.collapsed=${this.mainmenuCollapsed}
@tab-select=${(e: CustomEvent) => this.handleMainmenuTabSelect(e)}
@collapse-change=${(e: CustomEvent) => this.handleMainmenuCollapseChange(e)}
></dees-appui-mainmenu>
<dees-appui-secondarymenu
.heading=${this.secondarymenuHeading}
.groups=${this.secondarymenuGroups}
.selectionOptions=${this.secondarymenuOptions}
.selectedItem=${this.secondarymenuSelectedItem}
.collapsed=${this.secondarymenuCollapsed}
@item-select=${(e: CustomEvent) => this.handleSecondarymenuItemSelect(e)}
@collapse-change=${(e: CustomEvent) => this.handleSecondarymenuCollapseChange(e)}
></dees-appui-secondarymenu>
<dees-appui-maincontent
.tabs=${this.maincontentTabs}
>
<div class="view-container"></div>
<slot name="maincontent"></slot>
</dees-appui-maincontent>
<dees-appui-activitylog></dees-appui-activitylog>
</div>
`;
}
async firstUpdated() {
// Get references to child components
this.appbar = this.shadowRoot.querySelector('dees-appui-appbar');
this.mainmenu = this.shadowRoot.querySelector('dees-appui-mainmenu');
this.secondarymenu = this.shadowRoot.querySelector('dees-appui-secondarymenu');
this.maincontent = this.shadowRoot.querySelector('dees-appui-maincontent');
this.activitylog = this.shadowRoot.querySelector('dees-appui-activitylog');
// Initialize from config if provided
if (this.config) {
this.applyConfig(this.config);
// Restore state if enabled
if (this.config.statePersistence?.enabled) {
this.loadState();
}
// Initialize router after state restore
this.router?.init();
}
}
async disconnectedCallback() {
await super.disconnectedCallback();
this.router?.destroy();
}
// Event handlers for appbar
private handleAppbarMenuSelect(e: CustomEvent) {
this.dispatchEvent(new CustomEvent('appbar-menu-select', {
detail: e.detail,
bubbles: true,
composed: true
}));
}
private handleAppbarBreadcrumbNavigate(e: CustomEvent) {
this.dispatchEvent(new CustomEvent('appbar-breadcrumb-navigate', {
detail: e.detail,
bubbles: true,
composed: true
}));
}
private handleAppbarSearchClick() {
this.dispatchEvent(new CustomEvent('appbar-search-click', {
bubbles: true,
composed: true
}));
}
private handleAppbarUserMenuOpen() {
this.dispatchEvent(new CustomEvent('appbar-user-menu-open', {
bubbles: true,
composed: true
}));
}
private handleAppbarProfileMenuSelect(e: CustomEvent) {
this.dispatchEvent(new CustomEvent('appbar-profile-menu-select', {
detail: e.detail,
bubbles: true,
composed: true
}));
}
// Event handlers for mainmenu
private handleMainmenuTabSelect(e: CustomEvent) {
this.mainmenuSelectedTab = e.detail.tab;
// Update secondary menu heading based on main menu selection
this.secondarymenuHeading = e.detail.tab.key;
this.dispatchEvent(new CustomEvent('mainmenu-tab-select', {
detail: e.detail,
bubbles: true,
composed: true
}));
}
// Event handlers for secondarymenu
private handleSecondarymenuItemSelect(e: CustomEvent) {
this.secondarymenuSelectedItem = e.detail.item;
this.dispatchEvent(new CustomEvent('secondarymenu-item-select', {
detail: e.detail,
bubbles: true,
composed: true
}));
}
// Event handlers for collapse state changes
private handleMainmenuCollapseChange(e: CustomEvent) {
this.mainmenuCollapsed = e.detail.collapsed;
this.dispatchEvent(new CustomEvent('mainmenu-collapse-change', {
detail: e.detail,
bubbles: true,
composed: true
}));
}
private handleSecondarymenuCollapseChange(e: CustomEvent) {
this.secondarymenuCollapsed = e.detail.collapsed;
this.dispatchEvent(new CustomEvent('secondarymenu-collapse-change', {
detail: e.detail,
bubbles: true,
composed: true
}));
}
// ==========================================
// NEW: Public methods for unified config API
// ==========================================
/**
* Configure the app shell with a unified config object
*/
public configure(config: interfaces.IAppConfig): void {
this.config = config;
this.applyConfig(config);
}
/**
* Navigate to a view by ID
*/
public navigateToView(viewId: string): boolean {
if (this.router) {
return this.router.navigate(viewId);
}
// Fallback for non-routed mode
const view = this.viewRegistry.get(viewId);
if (view) {
this.loadView(view);
return true;
}
return false;
}
/**
* Get the current view
*/
public getCurrentView(): interfaces.IViewDefinition | undefined {
return this.currentView;
}
/**
* Get UI state for serialization
*/
public getUIState(): interfaces.IAppUIState {
return {
currentViewId: this.currentView?.id,
mainMenuCollapsed: this.mainmenuCollapsed,
secondaryMenuCollapsed: this.secondarymenuCollapsed,
secondaryMenuSelectedKey: this.secondarymenuSelectedItem?.key,
collapsedGroups: [], // TODO: Get from secondarymenu if needed
timestamp: Date.now(),
};
}
/**
* Restore UI state from a state object
*/
public restoreUIState(state: interfaces.IAppUIState): void {
if (state.mainMenuCollapsed !== undefined) {
this.mainmenuCollapsed = state.mainMenuCollapsed;
}
if (state.secondaryMenuCollapsed !== undefined) {
this.secondarymenuCollapsed = state.secondaryMenuCollapsed;
}
if (state.currentViewId) {
this.navigateToView(state.currentViewId);
}
}
/**
* Save current UI state
*/
public saveState(): void {
this.stateManager?.save(this.getUIState());
}
/**
* Load and restore saved UI state
*/
public loadState(): boolean {
const state = this.stateManager?.load();
if (state) {
this.restoreUIState(state);
return true;
}
return false;
}
/**
* Get access to the view registry
*/
public getViewRegistry(): ViewRegistry {
return this.viewRegistry;
}
/**
* Get access to the router
*/
public getRouter(): AppRouter | null {
return this.router;
}
// ==========================================
// NEW: Private helper methods
// ==========================================
private applyConfig(config: interfaces.IAppConfig): void {
// Register views
if (config.views) {
this.viewRegistry.clear();
this.viewRegistry.registerAll(config.views);
}
// Apply branding
if (config.branding) {
this.mainmenuLogoIcon = config.branding.logoIcon || '';
this.mainmenuLogoText = config.branding.logoText || '';
}
// Apply app bar config
if (config.appBar) {
this.appbarMenuItems = config.appBar.menuItems || [];
this.appbarBreadcrumbs = config.appBar.breadcrumbs || '';
this.appbarBreadcrumbSeparator = config.appBar.breadcrumbSeparator || ' > ';
this.appbarShowWindowControls = config.appBar.showWindowControls ?? true;
this.appbarShowSearch = config.appBar.showSearch ?? false;
this.appbarUser = config.appBar.user;
this.appbarProfileMenuItems = config.appBar.profileMenuItems || [];
}
// Build main menu from view references
if (config.mainMenu) {
this.mainmenuGroups = this.buildMainMenuGroups(config);
this.mainmenuBottomTabs = this.buildBottomTabs(config);
}
// Initialize state manager
if (config.statePersistence) {
this.stateManager = new StateManager(config.statePersistence);
}
// Initialize router
if (config.routing && config.routing.mode !== 'none') {
this.router = new AppRouter(config.routing, this.viewRegistry);
this.router.onRouteChange((viewId) => {
const view = this.viewRegistry.get(viewId);
if (view) {
this.loadView(view);
}
});
}
// Bind event callbacks
if (config.onViewChange) {
this.addEventListener('view-change', ((e: CustomEvent) => {
config.onViewChange!(e.detail.viewId, e.detail.view);
}) as EventListener);
}
if (config.onSearch) {
this.addEventListener('appbar-search-click', () => {
config.onSearch!();
});
}
}
private buildMainMenuGroups(config: interfaces.IAppConfig): interfaces.IMenuGroup[] {
if (!config.mainMenu?.sections) return [];
return config.mainMenu.sections.map((section) => ({
name: section.name,
tabs: section.views
.map((viewId) => {
const view = this.viewRegistry.get(viewId);
if (!view) {
console.warn(`View "${viewId}" not found in registry`);
return null;
}
return {
key: view.name,
iconName: view.iconName,
action: () => this.navigateToView(viewId),
};
})
.filter(Boolean) as interfaces.ITab[],
}));
}
private buildBottomTabs(config: interfaces.IAppConfig): interfaces.ITab[] {
if (!config.mainMenu?.bottomItems) return [];
return config.mainMenu.bottomItems
.map((viewId) => {
const view = this.viewRegistry.get(viewId);
if (!view) {
console.warn(`View "${viewId}" not found in registry`);
return null;
}
return {
key: view.name,
iconName: view.iconName,
action: () => this.navigateToView(viewId),
};
})
.filter(Boolean) as interfaces.ITab[];
}
private loadView(view: interfaces.IViewDefinition): void {
const previousView = this.currentView;
this.currentView = view;
// Update secondary menu
if (view.secondaryMenu) {
this.secondarymenuGroups = view.secondaryMenu;
this.secondarymenuHeading = view.name;
}
// Update content tabs
if (view.contentTabs) {
this.maincontentTabs = view.contentTabs;
}
// Render view content into the view container
const viewContainer = this.maincontent?.shadowRoot?.querySelector('.view-container')
|| this.shadowRoot?.querySelector('.view-container');
if (viewContainer) {
this.viewRegistry.renderView(view.id, viewContainer as HTMLElement);
}
// Save state if configured
this.stateManager?.update({ currentViewId: view.id });
// Dispatch event
this.dispatchEvent(
new CustomEvent('view-change', {
detail: { viewId: view.id, view, previousView },
bubbles: true,
composed: true,
})
);
}
}

View File

@@ -0,0 +1,4 @@
export * from './dees-appui-base.js';
export * from './view.registry.js';
export * from './app.router.js';
export * from './state.manager.js';

View File

@@ -0,0 +1,185 @@
import type { IStatePersistenceConfig, IAppUIState } from '../../interfaces/appconfig.js';
/**
* Manager for persisting and restoring UI state
*/
export class StateManager {
private config: Required<IStatePersistenceConfig>;
private memoryStorage: Map<string, string> = new Map();
constructor(config: IStatePersistenceConfig = { enabled: false }) {
this.config = {
enabled: config.enabled,
storageKey: config.storageKey || 'dees-appui-state',
storage: config.storage || 'localStorage',
persist: {
mainMenuCollapsed: true,
secondaryMenuCollapsed: true,
selectedView: true,
secondaryMenuSelection: true,
collapsedGroups: true,
...config.persist,
},
};
}
/**
* Check if state persistence is enabled
*/
public isEnabled(): boolean {
return this.config.enabled;
}
/**
* Save current UI state
*/
public save(state: Partial<IAppUIState>): void {
if (!this.config.enabled) return;
const existingState = this.load() || {};
const newState: IAppUIState = {
...existingState,
timestamp: Date.now(),
};
// Only save what's configured
if (this.config.persist.selectedView && state.currentViewId !== undefined) {
newState.currentViewId = state.currentViewId;
}
if (this.config.persist.mainMenuCollapsed && state.mainMenuCollapsed !== undefined) {
newState.mainMenuCollapsed = state.mainMenuCollapsed;
}
if (this.config.persist.secondaryMenuCollapsed && state.secondaryMenuCollapsed !== undefined) {
newState.secondaryMenuCollapsed = state.secondaryMenuCollapsed;
}
if (this.config.persist.secondaryMenuSelection && state.secondaryMenuSelectedKey !== undefined) {
newState.secondaryMenuSelectedKey = state.secondaryMenuSelectedKey;
}
if (this.config.persist.collapsedGroups && state.collapsedGroups !== undefined) {
newState.collapsedGroups = state.collapsedGroups;
}
this.setItem(this.config.storageKey, JSON.stringify(newState));
}
/**
* Load persisted UI state
*/
public load(): IAppUIState | null {
if (!this.config.enabled) return null;
try {
const data = this.getItem(this.config.storageKey);
if (!data) return null;
return JSON.parse(data) as IAppUIState;
} catch (e) {
console.warn('Failed to load UI state:', e);
return null;
}
}
/**
* Clear persisted state
*/
public clear(): void {
this.removeItem(this.config.storageKey);
}
/**
* Check if state exists
*/
public hasState(): boolean {
return this.getItem(this.config.storageKey) !== null;
}
/**
* Get state age in milliseconds
*/
public getStateAge(): number | null {
const state = this.load();
if (!state?.timestamp) return null;
return Date.now() - state.timestamp;
}
/**
* Update specific state properties
*/
public update(updates: Partial<IAppUIState>): void {
const currentState = this.load() || {};
this.save({ ...currentState, ...updates });
}
/**
* Get the storage key being used
*/
public getStorageKey(): string {
return this.config.storageKey;
}
// Storage abstraction methods
private getItem(key: string): string | null {
switch (this.config.storage) {
case 'localStorage':
try {
return localStorage.getItem(key);
} catch {
return null;
}
case 'sessionStorage':
try {
return sessionStorage.getItem(key);
} catch {
return null;
}
case 'memory':
return this.memoryStorage.get(key) || null;
default:
return null;
}
}
private setItem(key: string, value: string): void {
switch (this.config.storage) {
case 'localStorage':
try {
localStorage.setItem(key, value);
} catch (e) {
console.warn('Failed to save to localStorage:', e);
}
break;
case 'sessionStorage':
try {
sessionStorage.setItem(key, value);
} catch (e) {
console.warn('Failed to save to sessionStorage:', e);
}
break;
case 'memory':
this.memoryStorage.set(key, value);
break;
}
}
private removeItem(key: string): void {
switch (this.config.storage) {
case 'localStorage':
try {
localStorage.removeItem(key);
} catch {
// Ignore
}
break;
case 'sessionStorage':
try {
sessionStorage.removeItem(key);
} catch {
// Ignore
}
break;
case 'memory':
this.memoryStorage.delete(key);
break;
}
}
}

View File

@@ -0,0 +1,167 @@
import { html, render, type TemplateResult } from '@design.estate/dees-element';
import type { IViewDefinition } from '../../interfaces/appconfig.js';
/**
* Registry for managing views and their lifecycle
*/
export class ViewRegistry {
private views: Map<string, IViewDefinition> = new Map();
private instances: Map<string, HTMLElement> = new Map();
private currentViewId: string | null = null;
/**
* Register a single view
*/
public register(view: IViewDefinition): void {
if (this.views.has(view.id)) {
console.warn(`View with id "${view.id}" already registered. Overwriting.`);
}
this.views.set(view.id, view);
}
/**
* Register multiple views
*/
public registerAll(views: IViewDefinition[]): void {
views.forEach((view) => this.register(view));
}
/**
* Get a view definition by ID
*/
public get(viewId: string): IViewDefinition | undefined {
return this.views.get(viewId);
}
/**
* Get all registered view IDs
*/
public getViewIds(): string[] {
return Array.from(this.views.keys());
}
/**
* Get all views
*/
public getAll(): IViewDefinition[] {
return Array.from(this.views.values());
}
/**
* Get route for a view
*/
public getRoute(viewId: string): string {
const view = this.views.get(viewId);
return view?.route || view?.id || '';
}
/**
* Find view by route
*/
public findByRoute(route: string): IViewDefinition | undefined {
for (const view of this.views.values()) {
const viewRoute = view.route || view.id;
if (viewRoute === route) {
return view;
}
}
return undefined;
}
/**
* Render a view's content into a container
*/
public renderView(viewId: string, container: HTMLElement): HTMLElement | null {
const view = this.views.get(viewId);
if (!view) {
console.error(`View "${viewId}" not found in registry`);
return null;
}
// Clear container
container.innerHTML = '';
let element: HTMLElement;
if (typeof view.content === 'string') {
// Tag name string
element = document.createElement(view.content);
} else if (typeof view.content === 'function') {
// Check if it's a class constructor or template function
if (view.content.prototype instanceof HTMLElement) {
// Element class constructor
element = new (view.content as new () => HTMLElement)();
} else {
// Template function - wrap in a container and use Lit's render
const wrapper = document.createElement('div');
wrapper.className = 'view-content-wrapper';
wrapper.style.cssText = 'display: contents;';
const template = (view.content as () => TemplateResult)();
render(template, wrapper);
element = wrapper;
}
} else {
console.error(`Invalid content type for view "${viewId}"`);
return null;
}
container.appendChild(element);
this.instances.set(viewId, element);
this.currentViewId = viewId;
return element;
}
/**
* Get currently active view ID
*/
public getCurrentViewId(): string | null {
return this.currentViewId;
}
/**
* Get cached instance of a view
*/
public getInstance(viewId: string): HTMLElement | undefined {
return this.instances.get(viewId);
}
/**
* Clear all instances
*/
public clearInstances(): void {
this.instances.clear();
this.currentViewId = null;
}
/**
* Unregister a view
*/
public unregister(viewId: string): boolean {
this.instances.delete(viewId);
return this.views.delete(viewId);
}
/**
* Clear the registry
*/
public clear(): void {
this.views.clear();
this.instances.clear();
this.currentViewId = null;
}
/**
* Check if a view is registered
*/
public has(viewId: string): boolean {
return this.views.has(viewId);
}
/**
* Get the number of registered views
*/
public get size(): number {
return this.views.size;
}
}

View File

@@ -1,4 +1,4 @@
import * as interfaces from './interfaces/index.js';
import * as interfaces from '../../interfaces/index.js';
import {
DeesElement,
@@ -11,8 +11,8 @@ import {
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import './dees-appui-tabs.js';
import type { DeesAppuiTabs } from './dees-appui-tabs.js';
import '../dees-appui-tabs/dees-appui-tabs.js';
import type { DeesAppuiTabs } from '../dees-appui-tabs/dees-appui-tabs.js';
@customElement('dees-appui-maincontent')
export class DeesAppuiMaincontent extends DeesElement {
@@ -35,12 +35,12 @@ export class DeesAppuiMaincontent extends DeesElement {
@property({
type: Array,
})
public tabs: interfaces.ITab[] = [
accessor tabs: interfaces.ITab[] = [
{ key: '⚠️ Please set tabs', action: () => console.warn('No tabs configured for maincontent') },
];
@property({ type: Object })
public selectedTab: interfaces.ITab | null = null;
accessor selectedTab: interfaces.ITab | null = null;
public static styles = [
cssManager.defaultStyles,

View File

@@ -0,0 +1 @@
export * from './dees-appui-maincontent.js';

View File

@@ -0,0 +1,50 @@
import { html } from '@design.estate/dees-element';
export const demoFunc = () => html`
<style>
.demo-mainmenu-container {
display: flex;
height: 100%;
background: #1a1a1a;
border-radius: 8px;
}
.demo-mainmenu-container .spacer {
flex: 1;
background: #0f0f0f;
}
</style>
<div class="demo-mainmenu-container">
<dees-appui-mainmenu
.logoIcon=${'lucide:box'}
.logoText=${'Acme App'}
.menuGroups=${[
{
tabs: [
{ key: 'Dashboard', iconName: 'lucide:home', action: () => console.log('Dashboard') },
{ key: 'Inbox', iconName: 'lucide:inbox', action: () => console.log('Inbox') },
]
},
{
name: 'Workspace',
tabs: [
{ key: 'Projects', iconName: 'lucide:folder', action: () => console.log('Projects') },
{ key: 'Tasks', iconName: 'lucide:checkSquare', action: () => console.log('Tasks') },
{ key: 'Documents', iconName: 'lucide:fileText', action: () => console.log('Documents') },
]
},
{
name: 'Analytics',
tabs: [
{ key: 'Reports', iconName: 'lucide:barChart3', action: () => console.log('Reports') },
{ key: 'Insights', iconName: 'lucide:lightbulb', action: () => console.log('Insights') },
]
}
]}
.bottomTabs=${[
{ key: 'Settings', iconName: 'lucide:settings', action: () => console.log('Settings') },
{ key: 'Help', iconName: 'lucide:helpCircle', action: () => console.log('Help') },
]}
></dees-appui-mainmenu>
<div class="spacer"></div>
</div>
`;

View File

@@ -0,0 +1,460 @@
import * as plugins from '../../00plugins.js';
import * as interfaces from '../../interfaces/index.js';
import { zIndexLayers } from '../../00zindex.js';
import {
DeesElement,
type TemplateResult,
property,
customElement,
html,
css,
cssManager,
} from '@design.estate/dees-element';
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
import { demoFunc } from './dees-appui-mainmenu.demo.js';
/**
* the most left menu
* usually used as organization selector
*/
@customElement('dees-appui-mainmenu')
export class DeesAppuiMainmenu extends DeesElement {
public static demo = demoFunc;
// INSTANCE
// Logo properties
@property({ type: String })
accessor logoIcon: string = '';
@property({ type: String })
accessor logoText: string = '';
// Menu groups (new way)
@property({ type: Array })
accessor menuGroups: interfaces.IMenuGroup[] = [];
// Bottom tabs (pinned to bottom)
@property({ type: Array })
accessor bottomTabs: interfaces.ITab[] = [];
// Legacy tabs property (for backward compatibility)
@property({ type: Array })
accessor tabs: interfaces.ITab[] = [];
@property()
accessor selectedTab: interfaces.ITab;
@property({ type: Boolean, reflect: true })
accessor collapsed: boolean = false;
public static styles = [
cssManager.defaultStyles,
css`
:host {
--menu-width-expanded: 200px;
--menu-width-collapsed: 56px;
--tooltip-bg: ${cssManager.bdTheme('#18181b', '#fafafa')};
--tooltip-fg: ${cssManager.bdTheme('#fafafa', '#18181b')};
position: relative;
display: block;
height: 100%;
}
.mainContainer {
color: ${cssManager.bdTheme('#666', '#ccc')};
z-index: ${zIndexLayers.fixed.appBar};
display: flex;
flex-direction: column;
position: relative;
width: var(--menu-width-expanded);
height: 100%;
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
user-select: none;
border-right: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
transition: width 0.25s ease;
}
:host([collapsed]) .mainContainer {
width: var(--menu-width-collapsed);
}
/* Floating collapse toggle button */
.collapse-toggle {
position: absolute;
right: -12px;
top: 24px;
transform: translateY(-50%);
width: 24px;
height: 24px;
border-radius: 50%;
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#3f3f46')};
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')};
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')};
}
:host(:hover) .collapse-toggle {
opacity: 1;
}
.collapse-toggle dees-icon {
font-size: 14px;
}
/* Logo Section */
.logoSection {
display: flex;
align-items: center;
gap: 10px;
height: 48px;
padding: 0 14px;
border-bottom: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
flex-shrink: 0;
box-sizing: border-box;
}
.logoSection .logoIcon {
font-size: 22px;
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
flex-shrink: 0;
}
.logoSection .logoText {
flex: 1;
font-size: 15px;
font-weight: 600;
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 0.2s ease, width 0.25s ease;
}
:host([collapsed]) .logoSection {
justify-content: center;
padding: 0;
gap: 0;
}
:host([collapsed]) .logoSection .logoText {
display: none;
}
/* Middle Section (scrollable) */
.menuSection {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 8px 0;
}
.menuSection::-webkit-scrollbar {
width: 6px;
}
.menuSection::-webkit-scrollbar-track {
background: transparent;
}
.menuSection::-webkit-scrollbar-thumb {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.15)', 'rgba(255, 255, 255, 0.15)')};
border-radius: 3px;
}
.menuSection::-webkit-scrollbar-thumb:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.25)', 'rgba(255, 255, 255, 0.25)')};
}
/* Menu Group */
.menuGroup {
padding: 0 8px;
margin-bottom: 8px;
}
.menuGroup:last-child {
margin-bottom: 0;
}
.groupHeader {
padding: 8px 12px 6px;
font-size: 11px;
font-weight: 600;
color: ${cssManager.bdTheme('#737373', '#737373')};
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
overflow: hidden;
transition: opacity 0.2s ease, max-height 0.25s ease;
max-height: 30px;
}
:host([collapsed]) .groupHeader {
opacity: 0;
max-height: 0;
padding: 0;
margin: 0;
}
.groupTabs {
display: flex;
flex-direction: column;
gap: 2px;
}
:host([collapsed]) .menuGroup {
padding: 0 4px;
}
/* Tab Item */
.tab {
position: relative;
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
font-size: 13px;
font-weight: 500;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease;
color: ${cssManager.bdTheme('#525252', '#a3a3a3')};
}
.tab:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.04)', 'rgba(255, 255, 255, 0.06)')};
color: ${cssManager.bdTheme('#262626', '#e5e5e5')};
}
.tab:active {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
}
.tab.selectedTab {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.08)')};
color: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
}
.tab.selectedTab::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 16px;
background: ${cssManager.bdTheme('#0a0a0a', '#fafafa')};
border-radius: 0 2px 2px 0;
}
.tab dees-icon {
font-size: 18px;
opacity: 0.85;
flex-shrink: 0;
}
.tab.selectedTab dees-icon {
opacity: 1;
}
.tab .tabLabel {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 0.2s ease, width 0.25s ease;
}
/* Collapsed tab styles */
:host([collapsed]) .tab {
justify-content: center;
padding: 10px;
gap: 0;
}
:host([collapsed]) .tab .tabLabel {
opacity: 0;
width: 0;
position: absolute;
}
:host([collapsed]) .tab.selectedTab::before {
left: -4px;
}
/* Tooltip for collapsed state */
.tab-tooltip {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
margin-left: 12px;
padding: 6px 12px;
background: var(--tooltip-bg);
color: var(--tooltip-fg);
border-radius: 6px;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s ease;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.tab-tooltip::before {
content: '';
position: absolute;
left: -4px;
top: 50%;
transform: translateY(-50%);
border: 4px solid transparent;
border-right-color: var(--tooltip-bg);
}
:host([collapsed]) .tab:hover .tab-tooltip {
opacity: 1;
transition-delay: 1s;
}
/* Bottom Section */
.bottomSection {
flex-shrink: 0;
padding: 8px;
border-top: 1px solid ${cssManager.bdTheme('#e5e5e5', '#1a1a1a')};
display: flex;
flex-direction: column;
gap: 2px;
}
:host([collapsed]) .bottomSection {
padding: 8px 4px;
}
`,
];
public render(): TemplateResult {
// Get all tabs for selection (from groups or legacy tabs)
const allTabs = this.getAllTabs();
return html`
<div class="mainContainer" @contextmenu=${(eventArg: MouseEvent) => {
DeesContextmenu.openContextMenuWithOptions(eventArg, [{
name: 'app settings',
action: async () => {},
iconName: 'gear',
}])
}}>
${this.logoIcon || this.logoText ? html`
<div class="logoSection">
${this.logoIcon ? html`<dees-icon class="logoIcon" .icon="${this.logoIcon}"></dees-icon>` : ''}
${this.logoText ? html`<span class="logoText">${this.logoText}</span>` : ''}
</div>
` : ''}
<div class="menuSection">
${this.menuGroups.length > 0 ? this.renderMenuGroups() : this.renderLegacyTabs()}
</div>
${this.bottomTabs.length > 0 ? html`
<div class="bottomSection">
${this.bottomTabs.map((tabArg) => this.renderTab(tabArg))}
</div>
` : ''}
</div>
<button class="collapse-toggle" @click="${() => this.toggleCollapse()}">
<dees-icon .icon="${this.collapsed ? 'lucide:chevronRight' : 'lucide:chevronLeft'}"></dees-icon>
</button>
`;
}
private renderMenuGroups(): TemplateResult {
return html`
${this.menuGroups.map((group) => html`
<div class="menuGroup">
${group.name ? html`<div class="groupHeader">${group.name}</div>` : ''}
<div class="groupTabs">
${group.tabs.map((tabArg) => this.renderTab(tabArg))}
</div>
</div>
`)}
`;
}
private renderLegacyTabs(): TemplateResult {
return html`
<div class="menuGroup">
<div class="groupTabs">
${this.tabs.map((tabArg) => this.renderTab(tabArg))}
</div>
</div>
`;
}
private renderTab(tabArg: interfaces.ITab): TemplateResult {
return html`
<div
class="tab ${tabArg === this.selectedTab ? 'selectedTab' : ''}"
@click="${() => {
this.updateTab(tabArg);
}}"
>
<dees-icon .icon="${tabArg.iconName || ''}"></dees-icon>
<span class="tabLabel">${tabArg.key}</span>
<span class="tab-tooltip">${tabArg.key}</span>
</div>
`;
}
private getAllTabs(): interfaces.ITab[] {
if (this.menuGroups.length > 0) {
const groupTabs = this.menuGroups.flatMap(group => group.tabs);
return [...groupTabs, ...this.bottomTabs];
}
return [...this.tabs, ...this.bottomTabs];
}
updateTab(tabArg: interfaces.ITab) {
this.selectedTab = tabArg;
this.selectedTab.action();
// Emit tab-select event
this.dispatchEvent(new CustomEvent('tab-select', {
detail: { tab: tabArg },
bubbles: true,
composed: true
}));
}
firstUpdated() {
const allTabs = this.getAllTabs();
if (allTabs.length > 0) {
this.updateTab(allTabs[0]);
}
}
public toggleCollapse(): void {
this.collapsed = !this.collapsed;
this.dispatchEvent(new CustomEvent('collapse-change', {
detail: { collapsed: this.collapsed },
bubbles: true,
composed: true
}));
}
}

View File

@@ -0,0 +1 @@
export * from './dees-appui-mainmenu.js';

View File

@@ -1,5 +1,5 @@
import * as plugins from './00plugins.js';
import { zIndexLayers } from './00zindex.js';
import * as plugins from '../../00plugins.js';
import { zIndexLayers } from '../../00zindex.js';
import {
DeesElement,
@@ -36,21 +36,21 @@ export class DeesAppuiProfileDropdown extends DeesElement {
`;
@property({ type: Object })
public user?: {
accessor user: {
name: string;
email?: string;
avatar?: string;
status?: 'online' | 'offline' | 'busy' | 'away';
};
} | undefined = undefined;
@property({ type: Array })
public menuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = [];
accessor menuItems: (plugins.tsclass.website.IMenuItem & { shortcut?: string } | { divider: true })[] = [];
@property({ type: Boolean, reflect: true })
public isOpen: boolean = false;
accessor isOpen: boolean = false;
@property({ type: String })
public position: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' = 'top-right';
accessor position: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left' = 'top-right';
public static styles = [
cssManager.defaultStyles,

View File

@@ -0,0 +1 @@
export * from './dees-appui-profiledropdown.js';

View File

@@ -0,0 +1,52 @@
import { html } from '@design.estate/dees-element';
import type * as interfaces from '../../interfaces/index.js';
export const demoFunc = () => html`
<style>
.demo-secondarymenu-container {
display: flex;
height: 100%;
background: #1a1a1a;
border-radius: 8px;
}
.demo-secondarymenu-container .spacer {
flex: 1;
background: #0f0f0f;
}
</style>
<div class="demo-secondarymenu-container">
<dees-appui-secondarymenu
.heading=${'Projects'}
.groups=${[
{
name: 'Active',
iconName: 'lucide:folder',
items: [
{ key: 'Frontend App', iconName: 'code', action: () => console.log('Frontend'), badge: 3, badgeVariant: 'warning' },
{ key: 'API Server', iconName: 'server', action: () => console.log('API'), badge: 'new', badgeVariant: 'success' },
{ key: 'Database', iconName: 'database', action: () => console.log('Database') },
]
},
{
name: 'Archived',
iconName: 'lucide:archive',
collapsed: true,
items: [
{ key: 'Legacy System', iconName: 'box', action: () => console.log('Legacy') },
{ key: 'Old API', iconName: 'server', action: () => console.log('Old API') },
]
},
{
name: 'Settings',
iconName: 'lucide:settings',
items: [
{ key: 'Configuration', iconName: 'sliders', action: () => console.log('Config') },
{ key: 'Integrations', iconName: 'plug', action: () => console.log('Integrations'), badge: 5, badgeVariant: 'error' },
]
}
] as interfaces.ISecondaryMenuGroup[]}
@item-select=${(e: CustomEvent) => console.log('Selected:', e.detail)}
></dees-appui-secondarymenu>
<div class="spacer"></div>
</div>
`;

View File

@@ -0,0 +1,601 @@
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 {
DeesElement,
type TemplateResult,
property,
state,
customElement,
html,
css,
cssManager,
} from '@design.estate/dees-element';
import { demoFunc } from './dees-appui-secondarymenu.demo.js';
/**
* Secondary navigation menu for sub-navigation within MainMenu views
* Supports collapsible groups, badges, and dynamic headings
*/
@customElement('dees-appui-secondarymenu')
export class DeesAppuiSecondarymenu extends DeesElement {
public static demo = demoFunc;
// INSTANCE
/** Dynamic heading - typically shows the selected MainMenu item */
@property({ type: String })
accessor heading: string = 'Menu';
/** Grouped items with collapse support */
@property({ type: Array })
accessor groups: interfaces.ISecondaryMenuGroup[] = [];
/** Legacy flat list support for backward compatibility */
@property({ type: Array })
accessor selectionOptions: (interfaces.ISelectionOption | { divider: true })[] = [];
/** Currently selected item */
@property({ type: Object })
accessor selectedItem: interfaces.ISecondaryMenuItem | null = null;
/** Internal state for collapsed groups */
@state()
accessor collapsedGroups: Set<string> = new Set();
/** Horizontal collapse state */
@property({ type: Boolean, reflect: true })
accessor collapsed: boolean = false;
public static styles = [
cssManager.defaultStyles,
css`
: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')};
/* 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')};
position: relative;
display: block;
height: 100%;
width: var(--sidebar-width-expanded);
background: var(--sidebar-bg);
border-right: 1px solid var(--sidebar-border);
font-family: 'Geist Sans', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
user-select: none;
transition: width 0.25s ease;
}
:host([collapsed]) {
width: var(--sidebar-width-collapsed);
}
.maincontainer {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
position: relative;
}
/* Floating collapse toggle button */
.collapse-toggle {
position: absolute;
right: -12px;
top: 24px;
transform: translateY(-50%);
width: 24px;
height: 24px;
border-radius: 50%;
background: ${cssManager.bdTheme('#ffffff', '#27272a')};
border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#3f3f46')};
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')};
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')};
}
:host(:hover) .collapse-toggle {
opacity: 1;
}
.collapse-toggle dees-icon {
font-size: 14px;
}
/* Header Section */
.header {
display: flex;
align-items: center;
justify-content: space-between;
height: 48px;
padding: 0 16px;
border-bottom: 1px solid var(--sidebar-border);
flex-shrink: 0;
box-sizing: border-box;
}
.header .heading {
flex: 1;
font-size: 14px;
font-weight: 600;
color: var(--sidebar-fg-active);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 0.2s ease, width 0.25s ease;
}
:host([collapsed]) .header {
justify-content: center;
padding: 0 8px;
}
:host([collapsed]) .header .heading {
opacity: 0;
width: 0;
overflow: hidden;
}
/* Scrollable Menu Section */
.menuSection {
flex: 1;
overflow-y: auto;
overflow-x: hidden;
padding: 8px 0;
}
.menuSection::-webkit-scrollbar {
width: 6px;
}
.menuSection::-webkit-scrollbar-track {
background: transparent;
}
.menuSection::-webkit-scrollbar-thumb {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.15)', 'rgba(255, 255, 255, 0.15)')};
border-radius: 3px;
}
.menuSection::-webkit-scrollbar-thumb:hover {
background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.25)', 'rgba(255, 255, 255, 0.25)')};
}
/* Menu Group */
.menuGroup {
padding: 0 8px;
margin-bottom: 4px;
}
:host([collapsed]) .menuGroup {
padding: 0 4px;
}
.groupHeader {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 8px;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s ease, opacity 0.2s ease, max-height 0.25s ease;
max-height: 40px;
}
.groupHeader:hover {
background: var(--sidebar-hover);
}
.groupHeader .groupTitle {
display: flex;
align-items: center;
gap: 8px;
font-size: 11px;
font-weight: 600;
color: var(--sidebar-fg-muted);
text-transform: uppercase;
letter-spacing: 0.5px;
white-space: nowrap;
overflow: hidden;
}
.groupHeader .groupTitle dees-icon {
font-size: 14px;
opacity: 0.7;
}
.groupHeader .chevron {
font-size: 12px;
transition: transform 0.2s ease;
color: var(--sidebar-fg-muted);
}
.groupHeader.collapsed .chevron {
transform: rotate(-90deg);
}
/* Hide group headers when horizontally collapsed */
:host([collapsed]) .groupHeader {
opacity: 0;
max-height: 0;
padding: 0;
margin: 0;
pointer-events: none;
}
/* Group Items Container */
.groupItems {
overflow: hidden;
transition: max-height 0.25s ease, opacity 0.2s ease;
max-height: 500px;
opacity: 1;
}
.groupItems.collapsed {
max-height: 0;
opacity: 0;
}
/* Always show items when horizontally collapsed (regardless of group collapse state) */
:host([collapsed]) .groupItems {
max-height: none;
opacity: 1;
}
/* Menu Item */
.menuItem {
position: relative;
display: flex;
align-items: center;
gap: 10px;
padding: 8px 12px;
margin: 2px 0;
font-size: 13px;
font-weight: 450;
border-radius: 6px;
cursor: pointer;
transition: all 0.15s ease;
color: var(--sidebar-fg);
}
.menuItem:hover {
background: var(--sidebar-hover);
color: var(--sidebar-fg-active);
}
.menuItem:active {
background: var(--sidebar-active);
}
.menuItem.selected {
background: var(--sidebar-active);
color: var(--sidebar-fg-active);
font-weight: 500;
}
.menuItem.selected::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 16px;
background: var(--sidebar-accent);
border-radius: 0 2px 2px 0;
}
.menuItem dees-icon {
font-size: 16px;
opacity: 0.7;
flex-shrink: 0;
}
.menuItem.selected dees-icon {
opacity: 1;
}
.menuItem .itemLabel {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
transition: opacity 0.2s ease, width 0.25s ease;
}
/* Collapsed menu item styles */
:host([collapsed]) .menuItem {
justify-content: center;
padding: 8px;
gap: 0;
}
:host([collapsed]) .menuItem .itemLabel {
opacity: 0;
width: 0;
position: absolute;
}
:host([collapsed]) .menuItem.selected::before {
left: -4px;
}
/* Tooltip for collapsed state */
.item-tooltip {
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
margin-left: 12px;
padding: 6px 12px;
background: var(--tooltip-bg);
color: var(--tooltip-fg);
border-radius: 6px;
font-size: 13px;
font-weight: 500;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.15s ease;
z-index: 1000;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
.item-tooltip::before {
content: '';
position: absolute;
left: -4px;
top: 50%;
transform: translateY(-50%);
border: 4px solid transparent;
border-right-color: var(--tooltip-bg);
}
:host([collapsed]) .menuItem:hover .item-tooltip {
opacity: 1;
transition-delay: 1s;
}
/* Badge Styles */
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding: 0 6px;
font-size: 10px;
font-weight: 600;
border-radius: 9px;
flex-shrink: 0;
}
.badge.default {
background: var(--badge-default-bg);
color: var(--badge-default-fg);
}
.badge.success {
background: var(--badge-success-bg);
color: var(--badge-success-fg);
}
.badge.warning {
background: var(--badge-warning-bg);
color: var(--badge-warning-fg);
}
.badge.error {
background: var(--badge-error-bg);
color: var(--badge-error-fg);
}
:host([collapsed]) .badge {
display: none;
}
/* Divider */
.divider {
height: 1px;
background: var(--sidebar-border);
margin: 8px 12px;
}
/* Legacy options container */
.legacyOptions {
padding: 0 8px;
}
`,
];
public render(): TemplateResult {
return html`
<div class="maincontainer">
<div class="header">
<span class="heading">${this.heading}</span>
</div>
<div class="menuSection">
${this.groups.length > 0
? this.renderGroups()
: this.renderLegacyOptions()}
</div>
</div>
<button class="collapse-toggle" @click="${() => this.toggleCollapse()}">
<dees-icon .icon="${this.collapsed ? 'lucide:chevronRight' : 'lucide:chevronLeft'}"></dees-icon>
</button>
`;
}
private renderGroups(): TemplateResult {
return html`
${this.groups.map((group) => html`
<div class="menuGroup">
<div
class="groupHeader ${this.collapsedGroups.has(group.name) ? 'collapsed' : ''}"
@click="${() => this.toggleGroup(group.name)}"
>
<span class="groupTitle">
${group.iconName ? html`<dees-icon .icon="${group.iconName.startsWith('lucide:') ? group.iconName : `lucide:${group.iconName}`}"></dees-icon>` : ''}
${group.name}
</span>
<dees-icon class="chevron" .icon="${'lucide:chevronDown'}"></dees-icon>
</div>
<div class="groupItems ${this.collapsedGroups.has(group.name) ? 'collapsed' : ''}">
${group.items.map((item) => this.renderMenuItem(item, group))}
</div>
</div>
`)}
`;
}
private renderMenuItem(item: interfaces.ISecondaryMenuItem, group?: interfaces.ISecondaryMenuGroup): TemplateResult {
const isSelected = this.selectedItem?.key === item.key;
return html`
<div
class="menuItem ${isSelected ? 'selected' : ''}"
@click="${() => this.selectItem(item, group)}"
@contextmenu="${(e: MouseEvent) => this.handleContextMenu(e, item)}"
>
${item.iconName ? html`<dees-icon .icon="${item.iconName.startsWith('lucide:') ? item.iconName : `lucide:${item.iconName}`}"></dees-icon>` : ''}
<span class="itemLabel">${item.key}</span>
${item.badge !== undefined ? html`
<span class="badge ${item.badgeVariant || 'default'}">${item.badge}</span>
` : ''}
<span class="item-tooltip">${item.key}</span>
</div>
`;
}
private renderLegacyOptions(): TemplateResult {
return html`
<div class="legacyOptions">
${this.selectionOptions.map((option) => {
if ('divider' in option && option.divider) {
return html`<div class="divider"></div>`;
}
const item = option as interfaces.ISelectionOption;
return this.renderMenuItem({
key: item.key,
iconName: item.iconName,
action: item.action,
});
})}
</div>
`;
}
private toggleGroup(groupName: string): void {
const newCollapsed = new Set(this.collapsedGroups);
if (newCollapsed.has(groupName)) {
newCollapsed.delete(groupName);
} else {
newCollapsed.add(groupName);
}
this.collapsedGroups = newCollapsed;
}
public toggleCollapse(): void {
this.collapsed = !this.collapsed;
this.dispatchEvent(new CustomEvent('collapse-change', {
detail: { collapsed: this.collapsed },
bubbles: true,
composed: true
}));
}
private selectItem(item: interfaces.ISecondaryMenuItem, group?: interfaces.ISecondaryMenuGroup): void {
this.selectedItem = item;
item.action();
this.dispatchEvent(new CustomEvent('item-select', {
detail: { item, group },
bubbles: true,
composed: true
}));
}
private handleContextMenu(event: MouseEvent, item: interfaces.ISecondaryMenuItem): void {
DeesContextmenu.openContextMenuWithOptions(event, [
{
name: 'View details',
action: async () => {},
iconName: 'lucide:eye',
},
{
name: 'Edit',
action: async () => {},
iconName: 'lucide:pencil',
},
]);
}
async firstUpdated(_changedProperties: Map<string | number | symbol, unknown>) {
await super.firstUpdated(_changedProperties);
// Initialize collapsed state from group defaults
if (this.groups.length > 0) {
const initialCollapsed = new Set<string>();
this.groups.forEach(group => {
if (group.collapsed) {
initialCollapsed.add(group.name);
}
});
this.collapsedGroups = initialCollapsed;
// Auto-select first item if none selected
if (!this.selectedItem && this.groups[0]?.items.length > 0) {
this.selectItem(this.groups[0].items[0], this.groups[0]);
}
} else if (this.selectionOptions.length > 0) {
// Legacy mode: select first non-divider option
const firstOption = this.selectionOptions.find(opt => !('divider' in opt)) as interfaces.ISelectionOption;
if (firstOption && !this.selectedItem) {
this.selectItem({
key: firstOption.key,
iconName: firstOption.iconName,
action: firstOption.action,
});
}
}
}
}
declare global {
interface HTMLElementTagNameMap {
'dees-appui-secondarymenu': DeesAppuiSecondarymenu;
}
}

View File

@@ -0,0 +1 @@
export * from './dees-appui-secondarymenu.js';

View File

@@ -1,4 +1,4 @@
import * as interfaces from './interfaces/index.js';
import * as interfaces from '../../interfaces/index.js';
import {
DeesElement,
@@ -107,16 +107,16 @@ export class DeesAppuiTabs extends DeesElement {
@property({
type: Array,
})
public tabs: interfaces.ITab[] = [];
accessor tabs: interfaces.ITab[] = [];
@property({ type: Object })
public selectedTab: interfaces.ITab | null = null;
accessor selectedTab: interfaces.ITab | null = null;
@property({ type: Boolean })
public showTabIndicator: boolean = true;
accessor showTabIndicator: boolean = true;
@property({ type: String })
public tabStyle: 'horizontal' | 'vertical' = 'horizontal';
accessor tabStyle: 'horizontal' | 'vertical' = 'horizontal';
public static styles = [
cssManager.defaultStyles,
@@ -132,7 +132,9 @@ export class DeesAppuiTabs extends DeesElement {
}
.tabs-wrapper.horizontal-wrapper {
height: 48px;
border-bottom: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
box-sizing: border-box;
}
.tabsContainer {
@@ -146,7 +148,7 @@ export class DeesAppuiTabs extends DeesElement {
font-size: 14px;
overflow-x: auto;
scrollbar-width: none;
height: 48px;
height: 100%;
padding: 0 16px;
gap: 4px;
}

View File

@@ -0,0 +1 @@
export * from './dees-appui-tabs.js';

View File

@@ -1,4 +1,4 @@
import * as interfaces from './interfaces/index.js';
import * as interfaces from '../../interfaces/index.js';
import {
DeesElement,
@@ -11,8 +11,8 @@ import {
state,
} from '@design.estate/dees-element';
import './dees-appui-tabs.js';
import type { DeesAppuiTabs } from './dees-appui-tabs.js';
import '../dees-appui-tabs/dees-appui-tabs.js';
import type { DeesAppuiTabs } from '../dees-appui-tabs/dees-appui-tabs.js';
export interface IAppViewTab extends interfaces.ITab {
content?: TemplateResult | (() => TemplateResult);
@@ -60,13 +60,13 @@ export class DeesAppuiView extends DeesElement {
// INSTANCE
@property({ type: Object })
public viewConfig: IAppView;
accessor viewConfig: IAppView;
@state()
private selectedTab: IAppViewTab | null = null;
accessor selectedTab: IAppViewTab | null = null;
@state()
private tabs: DeesAppuiTabs;
accessor tabs: DeesAppuiTabs;
public static styles = [
cssManager.defaultStyles,

View File

@@ -0,0 +1 @@
export * from './dees-appui-view.js';

View File

@@ -0,0 +1,10 @@
// App UI Components
export * from './dees-appui-activitylog/index.js';
export * from './dees-appui-appbar/index.js';
export * from './dees-appui-base/index.js';
export * from './dees-appui-maincontent/index.js';
export * from './dees-appui-mainmenu/index.js';
export * from './dees-appui-secondarymenu/index.js';
export * from './dees-appui-profiledropdown/index.js';
export * from './dees-appui-tabs/index.js';
export * from './dees-appui-view/index.js';

View File

@@ -21,7 +21,7 @@ export class DeesButtonExit extends DeesElement {
@property({
type: Number
})
public size: number = 24;
accessor size: number = 24;
public styles = [
cssManager.defaultStyles,

View File

@@ -0,0 +1 @@
export * from './dees-button-exit.js';

View File

@@ -22,10 +22,10 @@ export class DeesButtonGroup extends DeesElement {
public static demo = demoFunc;
@property()
public label: string = '';
accessor label: string = '';
@property()
public direction: 'horizontal' | 'vertical' = 'horizontal';
accessor direction: 'horizontal' | 'vertical' = 'horizontal';
constructor() {
super();

View File

@@ -0,0 +1 @@
export * from './dees-button-group.js';

View File

@@ -1,11 +1,11 @@
import { html, css, cssManager, domtools } from '@design.estate/dees-element';
import '@design.estate/dees-wcctools/demotools';
import './dees-panel.js';
import './dees-form.js';
import './dees-form-submit.js';
import './dees-input-text.js';
import './dees-icon.js';
import type { DeesButton } from './dees-button.js';
import '../../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 type { DeesButton } from '../dees-button/dees-button.js';
export const demoFunc = () => html`
<style>

View File

@@ -29,42 +29,42 @@ export class DeesButton extends DeesElement {
return true;
}
})
public text: string;
accessor text: string;
@property()
public eventDetailData: string;
accessor eventDetailData: string;
@property({
type: Boolean,
reflect: true,
})
public disabled = false;
accessor disabled = false;
@property({
type: Boolean
})
public isHidden = false;
accessor isHidden = false;
@property({
type: String
})
public type: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' | 'normal' | 'highlighted' | 'discreet' | 'big' = 'default';
@property({
type: String
})
public size: 'default' | 'sm' | 'lg' | 'icon' = 'default';
accessor type: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost' | 'link' | 'normal' | 'highlighted' | 'discreet' | 'big' = 'default';
@property({
type: String
})
public status: 'normal' | 'pending' | 'success' | 'error' = 'normal';
accessor size: 'default' | 'sm' | 'lg' | 'icon' = 'default';
@property({
type: String
})
accessor status: 'normal' | 'pending' | 'success' | 'error' = 'normal';
@property({
type: Boolean,
reflect: true
})
public insideForm: boolean = false;
accessor insideForm: boolean = false;
constructor() {
super();

View File

@@ -0,0 +1 @@
export * from './dees-button.js';

View File

@@ -0,0 +1,4 @@
// Button Components
export * from './dees-button/index.js';
export * from './dees-button-exit/index.js';
export * from './dees-button-group/index.js';

View File

@@ -1,16 +1,15 @@
import {
DeesElement,
css,
cssManager,
customElement,
html,
property,
state,
type TemplateResult,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { demoFunc } from './dees-chart-area.demo.js';
import { demoFunc } from './demo.js';
import { chartAreaStyles } from './styles.js';
import { renderChartArea } from './template.js';
import ApexCharts from 'apexcharts';
@@ -26,36 +25,36 @@ export class DeesChartArea extends DeesElement {
// instance
@state()
public chart: ApexCharts;
accessor chart: ApexCharts;
@property()
public label: string = 'Untitled Chart';
accessor label: string = 'Untitled Chart';
@property({ type: Array })
public series: ApexAxisChartSeries = [];
accessor series: ApexAxisChartSeries = [];
// Override getter to return internal chart data
get chartSeries(): ApexAxisChartSeries {
return this.internalChartData.length > 0 ? this.internalChartData : this.series;
}
@property({ attribute: false })
public yAxisFormatter: (value: number) => string = (val) => `${val} Mbps`;
accessor yAxisFormatter: (value: number) => string = (val) => `${val} Mbps`;
@property({ type: Number })
public rollingWindow: number = 0; // 0 means no rolling window
accessor rollingWindow: number = 0; // 0 means no rolling window
@property({ type: Boolean })
public realtimeMode: boolean = false;
accessor realtimeMode: boolean = false;
@property({ type: String })
public yAxisScaling: 'fixed' | 'dynamic' | 'percentage' = 'dynamic';
accessor yAxisScaling: 'fixed' | 'dynamic' | 'percentage' = 'dynamic';
@property({ type: Number })
public yAxisMax: number = 100; // Used when yAxisScaling is 'fixed' or 'percentage'
accessor yAxisMax: number = 100; // Used when yAxisScaling is 'fixed' or 'percentage'
@property({ type: Number })
public autoScrollInterval: number = 1000; // Auto-scroll interval in milliseconds (0 to disable)
accessor autoScrollInterval: number = 1000; // Auto-scroll interval in milliseconds (0 to disable)
private resizeObserver: ResizeObserver;
private resizeTimeout: number;
@@ -141,73 +140,14 @@ export class DeesChartArea extends DeesElement {
}
}
public static styles = [
cssManager.defaultStyles,
css`
:host {
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;
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;
}
.chartTitle {
position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: left;
padding: 16px 24px;
z-index: 10;
font-size: 14px;
font-weight: 500;
letter-spacing: -0.01em;
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 63.9%)')};
}
.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 */
}
/* ApexCharts theme overrides */
.apexcharts-canvas {
background: transparent !important;
}
.apexcharts-inner {
background: transparent !important;
}
.apexcharts-graphical {
background: transparent !important;
}
`,
];
public static styles = chartAreaStyles;
public render(): TemplateResult {
return html`
<div class="mainbox">
<div class="chartTitle">${this.label}</div>
<div class="chartContainer"></div>
</div>
`;
return renderChartArea(this);
}
public async firstUpdated() {
await this.domtoolsPromise;

View File

@@ -1,6 +1,7 @@
import { html, css, cssManager } from '@design.estate/dees-element';
import type { DeesChartArea } from './dees-chart-area.js';
import type { DeesChartArea } from './component.js';
import '@design.estate/dees-wcctools/demotools';
import './component.js';
export const demoFunc = () => {
// Initial dataset values

View File

@@ -0,0 +1 @@
export * from './component.js';

View File

@@ -0,0 +1,60 @@
import { css, cssManager } from '@design.estate/dees-element';
export const chartAreaStyles = [
cssManager.defaultStyles,
css`
:host {
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;
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;
}
.chartTitle {
position: absolute;
top: 0;
left: 0;
width: 100%;
text-align: left;
padding: 16px 24px;
z-index: 10;
font-size: 14px;
font-weight: 500;
letter-spacing: -0.01em;
color: ${cssManager.bdTheme('hsl(0 0% 20%)', 'hsl(0 0% 63.9%)')};
}
.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 */
}
/* ApexCharts theme overrides */
.apexcharts-canvas {
background: transparent !important;
}
.apexcharts-inner {
background: transparent !important;
}
.apexcharts-graphical {
background: transparent !important;
}
`,
];

View File

@@ -0,0 +1,12 @@
import { html, type TemplateResult } from '@design.estate/dees-element';
import type { DeesChartArea } from './component.js';
export const renderChartArea = (component: DeesChartArea): TemplateResult => {
return html`
<div class="mainbox">
<div class="chartTitle">${component.label}</div>
<div class="chartContainer"></div>
</div>
`;
};

View File

@@ -1,5 +1,5 @@
import { html } from '@design.estate/dees-element';
import type { DeesChartLog } from './dees-chart-log.js';
import type { DeesChartLog } from '../dees-chart-log/dees-chart-log.js';
import '@design.estate/dees-wcctools/demotools';
export const demoFunc = () => {

View File

@@ -30,16 +30,16 @@ export class DeesChartLog extends DeesElement {
public static demo = demoFunc;
@property()
public label: string = 'Server Logs';
accessor label: string = 'Server Logs';
@property({ type: Array })
public logEntries: ILogEntry[] = [];
accessor logEntries: ILogEntry[] = [];
@property({ type: Boolean })
public autoScroll: boolean = true;
accessor autoScroll: boolean = true;
@property({ type: Number })
public maxEntries: number = 1000;
accessor maxEntries: number = 1000;
private logContainer: HTMLDivElement;

View File

@@ -0,0 +1 @@
export * from './dees-chart-log.js';

View File

@@ -0,0 +1,3 @@
// Chart Components
export * from './dees-chart-area/index.js';
export * from './dees-chart-log/index.js';

View File

@@ -8,14 +8,14 @@ import {
state,
cssManager,
} from '@design.estate/dees-element';
import { cssGeistFontFamily, cssMonoFontFamily } from './00fonts.js';
import { cssGeistFontFamily, cssMonoFontFamily } from '../../00fonts.js';
import hlight from 'highlight.js';
import * as smartstring from '@push.rocks/smartstring';
import * as domtools from '@design.estate/dees-domtools';
import { DeesContextmenu } from './dees-contextmenu.js';
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
declare global {
interface HTMLElementTagNameMap {
@@ -28,13 +28,13 @@ export class DeesDataviewCodebox extends DeesElement {
public static demo = demoFunc;
@property()
public progLang: string = 'typescript';
accessor progLang: string = 'typescript';
@property({
type: String,
reflect: true,
})
public codeToDisplay: string = '';
accessor codeToDisplay: string = '';
constructor() {
super();
@@ -228,7 +228,6 @@ export class DeesDataviewCodebox extends DeesElement {
`;
}
@state()
private codeToDisplayStore = '';
public async updated(_changedProperties) {

View File

@@ -0,0 +1 @@
export * from './dees-dataview-codebox.js';

View File

@@ -1,5 +1,5 @@
import * as colors from './00colors.js';
import * as plugins from './00plugins.js';
import * as colors from '../../00colors.js';
import * as plugins from '../../00plugins.js';
import { demoFunc } from './dees-dataview-statusobject.demo.js';
import {
@@ -15,7 +15,7 @@ import {
} from '@design.estate/dees-element';
import * as tsclass from '@tsclass/tsclass';
import { DeesContextmenu } from './dees-contextmenu.js';
import { DeesContextmenu } from '../../dees-contextmenu/dees-contextmenu.js';
declare global {
interface HTMLElementTagNameMap {
@@ -27,7 +27,7 @@ declare global {
export class DeesDataviewStatusobject extends DeesElement {
public static demo = demoFunc;
@property({ type: Object }) statusObject: tsclass.code.IStatusObject;
@property({ type: Object }) accessor statusObject: tsclass.code.IStatusObject;
public static styles = [
cssManager.defaultStyles,

View File

@@ -0,0 +1 @@
export * from './dees-dataview-statusobject.js';

View File

@@ -0,0 +1,3 @@
// Data View Components
export * from './dees-dataview-codebox/index.js';
export * from './dees-dataview-statusobject/index.js';

View File

@@ -0,0 +1 @@
export * from './dees-editor-markdown.js';

View File

@@ -0,0 +1 @@
export * from './dees-editor-markdownoutlet.js';

View File

@@ -8,6 +8,7 @@ import {
cssManager,
} from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { MONACO_VERSION } from './version.js';
import type * as monaco from 'monaco-editor';
@@ -32,17 +33,17 @@ export class DeesEditor extends DeesElement {
@property({
type: String
})
public content = "function hello() {\n\talert('Hello world!');\n}";
accessor content = "function hello() {\n\talert('Hello world!');\n}";
@property({
type: Object
})
public contentSubject = new domtools.plugins.smartrx.rxjs.Subject<string>();
accessor contentSubject = new domtools.plugins.smartrx.rxjs.Subject<string>();
@property({
type: Boolean
})
public wordWrap: monaco.editor.IStandaloneEditorConstructionOptions['wordWrap'] = 'off';
accessor wordWrap: monaco.editor.IStandaloneEditorConstructionOptions['wordWrap'] = 'off';
constructor() {
super();
@@ -80,10 +81,11 @@ export class DeesEditor extends DeesElement {
): Promise<void> {
super.firstUpdated(_changedProperties);
const container = this.shadowRoot.getElementById('container');
const monacoCdnBase = `https://cdn.jsdelivr.net/npm/monaco-editor@${MONACO_VERSION}`;
if (!DeesEditor.monacoDeferred) {
DeesEditor.monacoDeferred = domtools.plugins.smartpromise.defer();
const scriptUrl = `https://cdn.jsdelivr.net/npm/monaco-editor/min/vs/loader.js`;
const scriptUrl = `${monacoCdnBase}/min/vs/loader.js`;
const script = document.createElement('script');
script.src = scriptUrl;
script.onload = () => {
@@ -94,7 +96,7 @@ export class DeesEditor extends DeesElement {
await DeesEditor.monacoDeferred.promise;
(window as any).require.config({
paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor/min/vs' },
paths: { vs: `${monacoCdnBase}/min/vs` },
});
(window as any).require(['vs/editor/editor.main'], async () => {
const editor = ((window as any).monaco.editor as typeof monaco.editor).create(container, {
@@ -109,7 +111,7 @@ export class DeesEditor extends DeesElement {
this.editorDeferred.resolve(editor);
});
const css = await (
await fetch('https://cdn.jsdelivr.net/npm/monaco-editor/min/vs/editor/editor.main.css')
await fetch(`${monacoCdnBase}/min/vs/editor/editor.main.css`)
).text();
const styleElement = document.createElement('style');
styleElement.textContent = css;

View File

@@ -0,0 +1 @@
export * from './dees-editor.js';

View File

@@ -0,0 +1,2 @@
// Auto-generated by scripts/update-monaco-version.cjs
export const MONACO_VERSION = '0.52.2';

View File

@@ -0,0 +1,4 @@
// Editor Components
export * from './dees-editor/index.js';
export * from './dees-editor-markdown/index.js';
export * from './dees-editor-markdownoutlet/index.js';

View File

@@ -7,7 +7,7 @@ import {
cssManager,
property,
} from '@design.estate/dees-element';
import type { DeesForm } from './dees-form.js';
import type { DeesForm } from '../dees-form/dees-form.js';
declare global {
interface HTMLElementTagNameMap {
@@ -23,17 +23,17 @@ export class DeesFormSubmit extends DeesElement {
type: Boolean,
reflect: true,
})
public disabled = false;
accessor disabled = false;
@property({
type: String,
})
public text: string;
accessor text: string;
@property({
type: String,
})
public status: 'normal' | 'pending' | 'success' | 'error' = 'normal';
accessor status: 'normal' | 'pending' | 'success' | 'error' = 'normal';
constructor() {
super();
@@ -57,9 +57,10 @@ export class DeesFormSubmit extends DeesElement {
if (this.disabled) {
return;
}
const parentElement: DeesForm = this.parentElement as DeesForm;
if (parentElement && parentElement.gatherAndDispatch) {
parentElement.gatherAndDispatch();
// Walk up the DOM tree to find the nearest dees-form element
const parentFormElement = this.closest('dees-form') as DeesForm;
if (parentFormElement && parentFormElement.gatherAndDispatch) {
parentFormElement.gatherAndDispatch();
}
}

View File

@@ -0,0 +1 @@
export * from './dees-form-submit.js';

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