Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
4c23739d9a | |||
dd048d42a8 | |||
ca28dbd9db | |||
7148b12066 | |||
309d708830 | |||
923bedc4fc | |||
e8b771bde4 | |||
7a248993bc | |||
03f215e0f1 | |||
216cb0288d | |||
65acda3de1 | |||
88ff74bb86 | |||
98a5b1b5a3 | |||
bbf738d4e2 | |||
4f8ca7061a | |||
d26d99dbff | |||
c1d8e347de | |||
b6c41caf44 | |||
b858b3b9e2 | |||
4ed37086ae | |||
b4c0de47b9 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,7 +3,6 @@
|
|||||||
# artifacts
|
# artifacts
|
||||||
coverage/
|
coverage/
|
||||||
public/
|
public/
|
||||||
pages/
|
|
||||||
|
|
||||||
# installs
|
# installs
|
||||||
node_modules/
|
node_modules/
|
||||||
|
68
changelog.md
Normal file
68
changelog.md
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-09-19 - 1.2.0 - feat(wcc-properties)
|
||||||
|
Add advanced property editors, recursive element detection, demo wrapper, UI refresh and test fixtures
|
||||||
|
|
||||||
|
- Advanced JSON property editor: multiple side-by-side editors with save/cancel, syntax validation and inline error display; editors affect frame layout (frame bottom increases when editors open).
|
||||||
|
- Improved properties panel element detection: recursive search through nested children and shadow DOM, initial delay and retry mechanism to handle async Lit rendering.
|
||||||
|
- Add dees-demowrapper component in ts_demotools to run post-render callbacks and support async demo setup and DOM access for demos.
|
||||||
|
- UI refresh with shadcn-like styles: CSS variables for theming, redesigned properties panel and sidebar, improved form controls, theme and viewport selectors.
|
||||||
|
- Viewport and frame improvements: responsive padding based on viewport type, theme-aware background rendering, and scroll position tracking with URL/state restoration for frame and sidebar.
|
||||||
|
- Add test fixtures and demo elements/pages under test/ to exercise properties, complex types, nested elements and scroll restoration; include node test for resolveTemplateFactory.
|
||||||
|
- Expose setupWccTools entry point and plugin wiring (wcctools.plugins exports for dees-domtools and smartdelay) for easier integration.
|
||||||
|
|
||||||
|
## 2025-06-27 - 1.1.0 - feat(wcctools)
|
||||||
|
Enhance component tools with an advanced property editor, improved element detection and modernized UI styling for a more responsive dashboard experience.
|
||||||
|
|
||||||
|
- Updated documentation and in-code hints with new shadcn-like design patterns for the dashboard UI.
|
||||||
|
- Introduced an advanced complex properties editor supporting JSON validation and multi-editor handling.
|
||||||
|
- Refined recursive element search in the properties panel to improve asynchronous rendering detection.
|
||||||
|
- Expanded test coverage with scenarios for edge cases, nested elements and wrapper components.
|
||||||
|
|
||||||
|
## 2025-06-26 - 1.0.101 - fix(wcc-dashboard)
|
||||||
|
Improve scroll listener management and add new test pages
|
||||||
|
|
||||||
|
- Removed the pages/ directory entry from .gitignore to allow test pages to be tracked
|
||||||
|
- Added new test pages: page1 and pageLongScroll for enhanced scroll and navigation testing
|
||||||
|
- Refactored wcc-dashboard: changed scroll position properties to private variables and added a flag to prevent duplicate scroll listener attachment
|
||||||
|
|
||||||
|
## 2025-06-26 - 1.0.100 - fix(wcc-dashboard)
|
||||||
|
Prevent duplicate application of scroll positions in dashboard to avoid interfering with user scrolling
|
||||||
|
|
||||||
|
- Added a private 'scrollPositionsApplied' property to track if scroll positions have already been applied
|
||||||
|
- Introduced a guard in the applyScrollPositions method to ensure the scroll state is applied only once
|
||||||
|
|
||||||
|
## 2025-06-26 - 1.0.99 - fix(dashboard)
|
||||||
|
Fix scroll state preservation in dashboard by tracking frame and sidebar scroll positions and updating the URL accordingly.
|
||||||
|
|
||||||
|
- Added frameScrollY and sidebarScrollY properties to capture scroll positions.
|
||||||
|
- Set up scroll listeners on wcc-frame and wcc-sidebar to update scroll state.
|
||||||
|
- Implemented debounced updates to modify the URL with current scroll positions without navigation.
|
||||||
|
- Restored scroll positions from URL query parameters during initialization.
|
||||||
|
|
||||||
|
## 2025-06-16 - 1.0.97 - properties-panel
|
||||||
|
- Improve element detection timing and value handling in properties panel
|
||||||
|
|
||||||
|
## 2025-06-16 - 1.0.96 - properties-panel
|
||||||
|
- Enhance element detection and error handling for nested structures
|
||||||
|
|
||||||
|
## 2025-06-16 - 1.0.95 - package
|
||||||
|
- Correct path for demotools export in package.json
|
||||||
|
|
||||||
|
## 2025-06-16 - 1.0.94 - demotools
|
||||||
|
- Enhance runAfterRender to provide full DOM API access and improve element selection
|
||||||
|
|
||||||
|
## 2025-06-16 - 1.0.92 - demotools
|
||||||
|
- Update DeesDemoWrapper to handle multiple slotted elements in runAfterRender callback
|
||||||
|
|
||||||
|
## 2025-06-16 - 1.0.91 - readme
|
||||||
|
- Update documentation with comprehensive overview, quick start guide, and detailed feature descriptions
|
||||||
|
|
||||||
|
## 2025-06-16 - 1.0.90 - demo/properties/refactor
|
||||||
|
- Add DeesDemoWrapper component for enhanced demo element handling
|
||||||
|
- Enhance element detection in properties panel with recursive search and retry mechanism
|
||||||
|
- Refactor code structure for improved readability and maintainability
|
||||||
|
|
||||||
|
## 2024-05-06 to 2020-05-10 - 1.0.89–1.0.17 - core
|
||||||
|
- Over a series of releases, trivial core fixes and updates were applied.
|
||||||
|
- (Note: Version 1.0.87 also included an update to the documentation.)
|
20
package.json
20
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@design.estate/dees-wcctools",
|
"name": "@design.estate/dees-wcctools",
|
||||||
"version": "1.0.98",
|
"version": "1.2.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.",
|
"description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -17,18 +17,20 @@
|
|||||||
"author": "Lossless GmbH",
|
"author": "Lossless GmbH",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@design.estate/dees-domtools": "^2.0.57",
|
"@design.estate/dees-domtools": "^2.3.3",
|
||||||
"@design.estate/dees-element": "^2.0.34",
|
"@design.estate/dees-element": "^2.1.2",
|
||||||
"@push.rocks/smartdelay": "^3.0.5",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"lit": "^3.1.3"
|
"lit": "^3.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@api.global/typedserver": "^3.0.29",
|
"@api.global/typedserver": "^3.0.79",
|
||||||
"@git.zone/tsbuild": "^2.1.72",
|
"@git.zone/tsbuild": "^2.6.8",
|
||||||
"@git.zone/tsbundle": "^2.0.15",
|
"@git.zone/tsbundle": "^2.5.1",
|
||||||
"@git.zone/tsrun": "^1.2.44",
|
"@git.zone/tsrun": "^1.2.44",
|
||||||
"@git.zone/tswatch": "^2.0.23",
|
"@git.zone/tstest": "^2.3.8",
|
||||||
"@push.rocks/projectinfo": "^5.0.2"
|
"@git.zone/tswatch": "^2.2.1",
|
||||||
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
|
"@types/node": "^22.18.6"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
|
5624
pnpm-lock.yaml
generated
5624
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
|
- mongodb-memory-server
|
||||||
|
- puppeteer
|
102
readme.hints.md
102
readme.hints.md
@@ -1,5 +1,107 @@
|
|||||||
# Project Hints and Findings
|
# Project Hints and Findings
|
||||||
|
|
||||||
|
## UI Redesign with Shadcn-like Styles (2025-06-27)
|
||||||
|
|
||||||
|
### Changes Made
|
||||||
|
Updated the WCC Dashboard UI components (properties and sidebar) to use shadcn-like design patterns:
|
||||||
|
|
||||||
|
1. **Color System**: Implemented CSS variables for theming:
|
||||||
|
- `--background`, `--foreground`, `--card`, `--primary`, `--secondary`
|
||||||
|
- `--muted`, `--accent`, `--border`, `--input`, `--ring`
|
||||||
|
- Consistent dark theme with subtle borders and proper contrast
|
||||||
|
- Dynamic theme switching between light and dark modes
|
||||||
|
|
||||||
|
2. **Properties Panel Improvements (Updated)**:
|
||||||
|
- Changed from fixed 3-column grid to flexible flexbox layout
|
||||||
|
- Properties now wrap and use space more efficiently
|
||||||
|
- Added rounded corners (using --radius-md) and better spacing
|
||||||
|
- Property items use flexbox with min-width for responsive layout
|
||||||
|
- Property labels now show as styled headers with type info
|
||||||
|
- Form controls updated with shadcn-style focus states and transitions
|
||||||
|
- Complex properties (Objects/Arrays) show "Edit" button
|
||||||
|
- Advanced JSON editor appears above properties panel when editing complex types
|
||||||
|
- Dynamic height adjustment (50px when editor is open, 120px normally)
|
||||||
|
|
||||||
|
3. **Sidebar Styling**:
|
||||||
|
- Updated with consistent color scheme
|
||||||
|
- Added rounded corners to menu items
|
||||||
|
- Improved hover states with smooth transitions
|
||||||
|
- Better typography with proper font weights
|
||||||
|
|
||||||
|
4. **Advanced Property Editor**:
|
||||||
|
- JSON editor for complex types (Objects and Arrays)
|
||||||
|
- Monaco-style monospace font for code editing
|
||||||
|
- Live updates to element properties
|
||||||
|
- Positioned above the properties panel with smooth transitions
|
||||||
|
|
||||||
|
5. **Theme and Viewport Selectors (New)**:
|
||||||
|
- Redesigned buttons with flexbox layout for better icon/text alignment
|
||||||
|
- Added hover effects with transform and shadow
|
||||||
|
- Smooth transitions on all interactive elements
|
||||||
|
- Selected state uses primary color variables
|
||||||
|
- Icons reduced in size for better balance
|
||||||
|
|
||||||
|
6. **Form Controls (New)**:
|
||||||
|
- Input fields and selects now have:
|
||||||
|
- Rounded corners (--radius-sm)
|
||||||
|
- Consistent padding (0.5rem 0.75rem)
|
||||||
|
- Focus states with ring effect using box-shadow
|
||||||
|
- Smooth transition animations
|
||||||
|
- Checkboxes use accent-color for theming
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
- Uses system font stack ('Inter' preferred) for better native appearance
|
||||||
|
- Subtle borders with CSS variables for consistency
|
||||||
|
- Consistent spacing using rem units
|
||||||
|
- Smooth transitions (0.2s ease) for interactive elements
|
||||||
|
- Custom scrollbar styling for better visual integration
|
||||||
|
- Grid layout with 1px gaps creating subtle dividers
|
||||||
|
- Warning display with backdrop blur and rounded corners
|
||||||
|
|
||||||
|
## Advanced Complex Properties Editor (2025-06-27)
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
Implemented an advanced editor for complex properties (Arrays and Objects) that appears between the wcc-properties panel and frame when activated.
|
||||||
|
|
||||||
|
### Features
|
||||||
|
1. **Dynamic Layout**: Frame shrinks by 300px from bottom when editor opens
|
||||||
|
2. **Multiple Editors**: Can edit multiple properties simultaneously side by side
|
||||||
|
3. **JSON Editor**:
|
||||||
|
- Monospace font for code editing
|
||||||
|
- Tab key support for indentation
|
||||||
|
- Syntax validation with error messages
|
||||||
|
- Live preview of changes
|
||||||
|
4. **Smooth Transitions**: Animated opening/closing with 0.3s ease
|
||||||
|
5. **Error Handling**: Invalid JSON shows clear error messages that disappear on typing
|
||||||
|
6. **Close All Button**: Single button to close all open editors at once
|
||||||
|
|
||||||
|
### Technical Implementation (Updated)
|
||||||
|
- **State Management**: Changed from single editor to array of editors with unique IDs
|
||||||
|
- **Editor Structure**: Each editor instance contains:
|
||||||
|
- `id`: Unique identifier (`propertyName-timestamp`)
|
||||||
|
- `name`: Property name
|
||||||
|
- `value`: Original value
|
||||||
|
- `element`: Reference to the element
|
||||||
|
- `editorValue`: Current JSON string
|
||||||
|
- `editorError`: Validation error message
|
||||||
|
- **Event System**: Uses custom 'editorStateChanged' event to communicate with parent dashboard
|
||||||
|
- **Dynamic Styling**: wcc-frame's bottom position changes from 100px to 400px when any editor is open
|
||||||
|
- **Property Types**: Object and Array properties show "Edit Object/Array" button instead of inline controls
|
||||||
|
|
||||||
|
### User Flow
|
||||||
|
1. Click "Edit Object/Array" button on complex property
|
||||||
|
2. Editor slides up between properties panel and frame
|
||||||
|
3. Click additional "Edit" buttons to open more properties side by side
|
||||||
|
4. Each editor can be saved/cancelled independently
|
||||||
|
5. "Close All" button dismisses all editors at once
|
||||||
|
6. Frame automatically resizes back when all editors are closed
|
||||||
|
|
||||||
|
### Layout Details
|
||||||
|
- **Container**: Flexbox with horizontal scrolling when multiple editors overflow
|
||||||
|
- **Editor Width**: Min 300px, max 500px, flexible between
|
||||||
|
- **Scrollbar**: Custom styled thin scrollbar for horizontal overflow
|
||||||
|
- **Header Bar**: Fixed top bar with "Property Editors" title and "Close All" button
|
||||||
|
|
||||||
## Properties Panel Element Detection Issue (Fixed)
|
## Properties Panel Element Detection Issue (Fixed)
|
||||||
|
|
||||||
### Problem
|
### Problem
|
||||||
|
15
readme.md
15
readme.md
@@ -211,6 +211,19 @@ export class MyComponent extends DeesElement {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### ⏳ Async Demos
|
||||||
|
|
||||||
|
If your catalogue needs additional setup before rendering, return a `Promise` from the `demo` function. The dashboard waits for the result before inserting it into the viewport:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
public static demo = async () => {
|
||||||
|
await Promise.resolve(); // e.g. fetch data, load fixtures, or await wrappers
|
||||||
|
return html`<my-component .value=${'Loaded asynchronously'}></my-component>`;
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The same pattern works for page factories you pass into `setupWccTools`, enabling asynchronous data preparation across the entire demo surface.
|
||||||
|
|
||||||
### 🎭 Container Queries Support
|
### 🎭 Container Queries Support
|
||||||
|
|
||||||
Components can respond to their container size:
|
Components can respond to their container size:
|
||||||
@@ -389,4 +402,4 @@ Registered at District court Bremen HRB 35230 HB, Germany
|
|||||||
|
|
||||||
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
|
@@ -97,4 +97,11 @@ Properties panel was overwriting values set by demo functions
|
|||||||
2. This prevents browser from firing input events during initialization
|
2. This prevents browser from firing input events during initialization
|
||||||
3. Added proper number parsing for number inputs
|
3. Added proper number parsing for number inputs
|
||||||
4. Increased initial wait to 200ms for demo wrappers to complete
|
4. Increased initial wait to 200ms for demo wrappers to complete
|
||||||
5. Simplified select element handling to use property binding
|
5. Simplified select element handling to use property binding
|
||||||
|
# Async Demo Support (IN PROGRESS)
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
- [ ] Allow dashboard-selected items to return Promise-based TemplateResults
|
||||||
|
- [ ] Await async demos/pages before rendering them into the viewport
|
||||||
|
- [ ] Add regression test covering async demo usage
|
||||||
|
- [ ] Document async demo pattern in README and verify with pnpm scripts
|
||||||
|
2
test/pages/index.ts
Normal file
2
test/pages/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './page1.js';
|
||||||
|
export * from './pageLongScroll.js';
|
3
test/pages/page1.ts
Normal file
3
test/pages/page1.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import { html } from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
export const page1 = () => html` <test-demoelement></test-demoelement> `;
|
138
test/pages/pageLongScroll.ts
Normal file
138
test/pages/pageLongScroll.ts
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
import { html } from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
export const pageLongScroll = () => html`
|
||||||
|
<style>
|
||||||
|
.long-scroll-container {
|
||||||
|
padding: 40px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
margin-bottom: 60px;
|
||||||
|
padding: 20px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.8em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #ccc;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder-content {
|
||||||
|
height: 300px;
|
||||||
|
background: linear-gradient(135deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0.05) 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #888;
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 20px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="long-scroll-container">
|
||||||
|
<h1>Long Scroll Test Page</h1>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Section 1: Introduction</h2>
|
||||||
|
<p>This is a long page designed to test scroll position preservation. Scroll down to see more content and then reload the page to verify that the scroll position is restored correctly.</p>
|
||||||
|
<p>The URL should update with scroll position parameters as you scroll, and when you reload the page, it should automatically scroll to the last position.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="placeholder-content">
|
||||||
|
<span>Placeholder Content Block 1</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Section 2: Testing Scroll Behavior</h2>
|
||||||
|
<p>As you scroll through this page, the dashboard should track your scroll position and update the URL accordingly. The updates should be debounced to avoid excessive URL changes.</p>
|
||||||
|
<p>Try scrolling quickly and slowly to see how the debouncing works. The URL should update smoothly without interfering with your scrolling experience.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="placeholder-content">
|
||||||
|
<span>Placeholder Content Block 2</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Section 3: Reload Testing</h2>
|
||||||
|
<p>Once you've scrolled to this section, try reloading the page. The page should automatically scroll back to this position after the content loads.</p>
|
||||||
|
<p>This demonstrates that the scroll position is being preserved across page reloads using URL parameters.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="placeholder-content">
|
||||||
|
<span>Placeholder Content Block 3</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Section 4: Navigation Testing</h2>
|
||||||
|
<p>Try navigating to a different element or page in the sidebar, then use the browser's back button to return here. The scroll position should be preserved.</p>
|
||||||
|
<p>This tests that the browser history correctly maintains scroll state for each navigation entry.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="placeholder-content">
|
||||||
|
<span>Placeholder Content Block 4</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Section 5: Deep Scroll Testing</h2>
|
||||||
|
<p>Keep scrolling! This page has plenty of content to ensure we can test scroll positions at various depths.</p>
|
||||||
|
<p>The scroll tracking should work reliably regardless of how far down the page you scroll.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="placeholder-content">
|
||||||
|
<span>Placeholder Content Block 5</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Section 6: Performance Testing</h2>
|
||||||
|
<p>Even with continuous scroll tracking, the page should remain responsive and smooth. The debouncing mechanism ensures that URL updates don't impact scrolling performance.</p>
|
||||||
|
<p>Try scrolling rapidly up and down to verify that the scrolling remains smooth.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="placeholder-content">
|
||||||
|
<span>Placeholder Content Block 6</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Section 7: Sidebar Scroll Testing</h2>
|
||||||
|
<p>Don't forget to test the sidebar scrolling as well! If the sidebar has enough items to scroll, its position should also be tracked and restored.</p>
|
||||||
|
<p>Both the main content and sidebar scroll positions should be preserved independently.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="placeholder-content">
|
||||||
|
<span>Placeholder Content Block 7</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Section 8: Edge Cases</h2>
|
||||||
|
<p>This section tests edge cases like scrolling to the very bottom of the page, then reloading.</p>
|
||||||
|
<p>The scroll restoration should handle these cases gracefully without any visual glitches or errors.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="placeholder-content">
|
||||||
|
<span>Placeholder Content Block 8</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Section 9: Final Section</h2>
|
||||||
|
<p>You've reached the end of the scroll test page! Try reloading from here to ensure that even the bottom-most scroll positions are correctly preserved.</p>
|
||||||
|
<p>The scroll position tracking has been successfully implemented if you can reload and return to this exact position.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
22
test/test.demoresolver.node.ts
Normal file
22
test/test.demoresolver.node.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import { resolveTemplateFactory } from '../ts_web/elements/wcctools.helpers.js';
|
||||||
|
import { html } from 'lit';
|
||||||
|
|
||||||
|
const waitFor = (durationMs: number) => new Promise(resolve => setTimeout(resolve, durationMs));
|
||||||
|
|
||||||
|
tap.test('resolveTemplateFactory returns sync TemplateResult', async () => {
|
||||||
|
const template = html`<p>sync demo</p>`;
|
||||||
|
const resolvedTemplate = await resolveTemplateFactory(() => template);
|
||||||
|
expect(resolvedTemplate).toEqual(template);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('resolveTemplateFactory awaits async TemplateResult', async () => {
|
||||||
|
const template = html`<p>async demo</p>`;
|
||||||
|
const resolvedTemplate = await resolveTemplateFactory(async () => {
|
||||||
|
await waitFor(5);
|
||||||
|
return template;
|
||||||
|
});
|
||||||
|
expect(resolvedTemplate).toEqual(template);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* autocreated commitinfo by @pushrocks/commitinfo
|
* autocreated commitinfo by @push.rocks/commitinfo
|
||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-wcctools',
|
name: '@design.estate/dees-wcctools',
|
||||||
version: '1.0.90',
|
version: '1.2.0',
|
||||||
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
|
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
|
||||||
}
|
}
|
||||||
|
@@ -1,4 +1,6 @@
|
|||||||
import { DeesElement, property, html, customElement, type TemplateResult, queryAsync, render, domtools } from '@design.estate/dees-element';
|
import { DeesElement, property, html, customElement, type TemplateResult, queryAsync, render, domtools } from '@design.estate/dees-element';
|
||||||
|
import { resolveTemplateFactory } from './wcctools.helpers.js';
|
||||||
|
import type { TTemplateFactory } from './wcctools.helpers.js';
|
||||||
|
|
||||||
import * as plugins from '../wcctools.plugins.js';
|
import * as plugins from '../wcctools.plugins.js';
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@ export class WccDashboard extends DeesElement {
|
|||||||
public selectedItemName: string;
|
public selectedItemName: string;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
public selectedItem: (() => TemplateResult) | DeesElement;
|
public selectedItem: TTemplateFactory | DeesElement;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
public selectedViewport: plugins.deesDomtools.breakpoints.TViewport = 'desktop';
|
public selectedViewport: plugins.deesDomtools.breakpoints.TViewport = 'desktop';
|
||||||
@@ -30,7 +32,10 @@ export class WccDashboard extends DeesElement {
|
|||||||
public selectedTheme: TTheme = 'dark';
|
public selectedTheme: TTheme = 'dark';
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
public pages: { [key: string]: () => TemplateResult } = {};
|
public isFullscreen: boolean = false;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
public pages: Record<string, TTemplateFactory> = {};
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
public elements: { [key: string]: DeesElement } = {};
|
public elements: { [key: string]: DeesElement } = {};
|
||||||
@@ -38,12 +43,16 @@ export class WccDashboard extends DeesElement {
|
|||||||
@property()
|
@property()
|
||||||
public warning: string = null;
|
public warning: string = null;
|
||||||
|
|
||||||
|
private frameScrollY: number = 0;
|
||||||
|
private sidebarScrollY: number = 0;
|
||||||
|
private scrollPositionsApplied: boolean = false;
|
||||||
|
|
||||||
@queryAsync('wcc-frame')
|
@queryAsync('wcc-frame')
|
||||||
public wccFrame: Promise<WccFrame>;
|
public wccFrame: Promise<WccFrame>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
elementsArg?: { [key: string]: DeesElement },
|
elementsArg?: { [key: string]: DeesElement },
|
||||||
pagesArg?: { [key: string]: () => TemplateResult }
|
pagesArg?: Record<string, TTemplateFactory>
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
if (elementsArg) {
|
if (elementsArg) {
|
||||||
@@ -72,6 +81,7 @@ export class WccDashboard extends DeesElement {
|
|||||||
<wcc-sidebar
|
<wcc-sidebar
|
||||||
.dashboardRef=${this}
|
.dashboardRef=${this}
|
||||||
.selectedItem=${this.selectedItem}
|
.selectedItem=${this.selectedItem}
|
||||||
|
.isFullscreen=${this.isFullscreen}
|
||||||
@selectedType=${(eventArg) => {
|
@selectedType=${(eventArg) => {
|
||||||
this.selectedType = eventArg.detail;
|
this.selectedType = eventArg.detail;
|
||||||
}}
|
}}
|
||||||
@@ -88,6 +98,7 @@ export class WccDashboard extends DeesElement {
|
|||||||
.selectedItem=${this.selectedItem}
|
.selectedItem=${this.selectedItem}
|
||||||
.selectedViewport=${this.selectedViewport}
|
.selectedViewport=${this.selectedViewport}
|
||||||
.selectedTheme=${this.selectedTheme}
|
.selectedTheme=${this.selectedTheme}
|
||||||
|
.isFullscreen=${this.isFullscreen}
|
||||||
@selectedViewport=${(eventArg) => {
|
@selectedViewport=${(eventArg) => {
|
||||||
this.selectedViewport = eventArg.detail;
|
this.selectedViewport = eventArg.detail;
|
||||||
this.scheduleUpdate();
|
this.scheduleUpdate();
|
||||||
@@ -95,8 +106,18 @@ export class WccDashboard extends DeesElement {
|
|||||||
@selectedTheme=${(eventArg) => {
|
@selectedTheme=${(eventArg) => {
|
||||||
this.selectedTheme = eventArg.detail;
|
this.selectedTheme = eventArg.detail;
|
||||||
}}
|
}}
|
||||||
|
@editorStateChanged=${async (eventArg) => {
|
||||||
|
const frame = await this.wccFrame;
|
||||||
|
if (frame) {
|
||||||
|
frame.advancedEditorOpen = eventArg.detail.isOpen;
|
||||||
|
frame.requestUpdate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
@toggleFullscreen=${() => {
|
||||||
|
this.toggleFullscreen();
|
||||||
|
}}
|
||||||
></wcc-properties>
|
></wcc-properties>
|
||||||
<wcc-frame id="wccFrame" viewport=${this.selectedViewport}>
|
<wcc-frame id="wccFrame" viewport=${this.selectedViewport} .isFullscreen=${this.isFullscreen}>
|
||||||
</wcc-frame>
|
</wcc-frame>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
@@ -111,8 +132,25 @@ export class WccDashboard extends DeesElement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public toggleFullscreen() {
|
||||||
|
this.isFullscreen = !this.isFullscreen;
|
||||||
|
}
|
||||||
|
|
||||||
public async firstUpdated() {
|
public async firstUpdated() {
|
||||||
this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
||||||
|
|
||||||
|
// Add ESC key handler for fullscreen mode
|
||||||
|
document.addEventListener('keydown', (event) => {
|
||||||
|
if (event.key === 'Escape' && this.isFullscreen) {
|
||||||
|
this.isFullscreen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up scroll listeners after DOM is ready
|
||||||
|
setTimeout(() => {
|
||||||
|
this.setupScrollListeners();
|
||||||
|
}, 500);
|
||||||
|
|
||||||
this.domtools.router.on(
|
this.domtools.router.on(
|
||||||
'/wcctools-route/:itemType/:itemName/:viewport/:theme',
|
'/wcctools-route/:itemType/:itemName/:viewport/:theme',
|
||||||
async (routeInfo) => {
|
async (routeInfo) => {
|
||||||
@@ -125,6 +163,25 @@ export class WccDashboard extends DeesElement {
|
|||||||
} else if (routeInfo.params.itemType === 'page') {
|
} else if (routeInfo.params.itemType === 'page') {
|
||||||
this.selectedItem = this.pages[routeInfo.params.itemName];
|
this.selectedItem = this.pages[routeInfo.params.itemName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restore scroll positions from query parameters
|
||||||
|
if (routeInfo.queryParams) {
|
||||||
|
const frameScrollY = routeInfo.queryParams.frameScrollY;
|
||||||
|
const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
|
||||||
|
|
||||||
|
if (frameScrollY) {
|
||||||
|
this.frameScrollY = parseInt(frameScrollY);
|
||||||
|
}
|
||||||
|
if (sidebarScrollY) {
|
||||||
|
this.sidebarScrollY = parseInt(sidebarScrollY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply scroll positions after a short delay to ensure DOM is ready
|
||||||
|
setTimeout(() => {
|
||||||
|
this.applyScrollPositions();
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
|
||||||
const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
|
const domtoolsInstance = await plugins.deesDomtools.elementBasic.setup();
|
||||||
this.selectedTheme === 'bright'
|
this.selectedTheme === 'bright'
|
||||||
? domtoolsInstance.themeManager.goBright()
|
? domtoolsInstance.themeManager.goBright()
|
||||||
@@ -136,7 +193,6 @@ export class WccDashboard extends DeesElement {
|
|||||||
public async updated(changedPropertiesArg: Map<string, any>) {
|
public async updated(changedPropertiesArg: Map<string, any>) {
|
||||||
this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
||||||
await this.domtools.router._handleRouteState();
|
await this.domtools.router._handleRouteState();
|
||||||
const storeElement = this.selectedItem;
|
|
||||||
const wccFrame: WccFrame = this.shadowRoot.querySelector('wcc-frame');
|
const wccFrame: WccFrame = this.shadowRoot.querySelector('wcc-frame');
|
||||||
|
|
||||||
if (changedPropertiesArg.has('selectedItemName')) {
|
if (changedPropertiesArg.has('selectedItemName')) {
|
||||||
@@ -147,7 +203,9 @@ export class WccDashboard extends DeesElement {
|
|||||||
if (typeof this.selectedItem === 'function') {
|
if (typeof this.selectedItem === 'function') {
|
||||||
console.log('slotting page.');
|
console.log('slotting page.');
|
||||||
const viewport = await wccFrame.getViewportElement();
|
const viewport = await wccFrame.getViewportElement();
|
||||||
render(this.selectedItem(), viewport);
|
const pageFactory = this.selectedItem as TTemplateFactory;
|
||||||
|
const pageTemplate = await resolveTemplateFactory(pageFactory);
|
||||||
|
render(pageTemplate, viewport);
|
||||||
console.log('rendered page.');
|
console.log('rendered page.');
|
||||||
} else {
|
} else {
|
||||||
console.error('The selected item looks strange:');
|
console.error('The selected item looks strange:');
|
||||||
@@ -168,13 +226,102 @@ export class WccDashboard extends DeesElement {
|
|||||||
}
|
}
|
||||||
this.setWarning(null);
|
this.setWarning(null);
|
||||||
const viewport = await wccFrame.getViewportElement();
|
const viewport = await wccFrame.getViewportElement();
|
||||||
render(anonItem.demo(), viewport);;
|
const demoTemplate = await resolveTemplateFactory(() => anonItem.demo());
|
||||||
|
render(demoTemplate, viewport);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public buildUrl() {
|
public buildUrl() {
|
||||||
this.domtools.router.pushUrl(
|
const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`;
|
||||||
`/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`
|
const queryParams = new URLSearchParams();
|
||||||
);
|
|
||||||
|
if (this.frameScrollY > 0) {
|
||||||
|
queryParams.set('frameScrollY', this.frameScrollY.toString());
|
||||||
|
}
|
||||||
|
if (this.sidebarScrollY > 0) {
|
||||||
|
queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
||||||
|
|
||||||
|
this.domtools.router.pushUrl(fullUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
private scrollUpdateTimeout: NodeJS.Timeout;
|
||||||
|
private scrollListenersAttached: boolean = false;
|
||||||
|
|
||||||
|
public async setupScrollListeners() {
|
||||||
|
// Prevent duplicate listeners
|
||||||
|
if (this.scrollListenersAttached) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wccFrame = await this.wccFrame;
|
||||||
|
const wccSidebar = this.shadowRoot.querySelector('wcc-sidebar');
|
||||||
|
|
||||||
|
if (wccFrame) {
|
||||||
|
// The frame element itself is the scrollable container
|
||||||
|
wccFrame.addEventListener('scroll', () => {
|
||||||
|
this.frameScrollY = wccFrame.scrollTop;
|
||||||
|
this.debouncedScrollUpdate();
|
||||||
|
});
|
||||||
|
this.scrollListenersAttached = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wccSidebar) {
|
||||||
|
// The sidebar element itself is the scrollable container
|
||||||
|
wccSidebar.addEventListener('scroll', () => {
|
||||||
|
this.sidebarScrollY = wccSidebar.scrollTop;
|
||||||
|
this.debouncedScrollUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private debouncedScrollUpdate() {
|
||||||
|
clearTimeout(this.scrollUpdateTimeout);
|
||||||
|
this.scrollUpdateTimeout = setTimeout(() => {
|
||||||
|
this.updateUrlWithScrollState();
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateUrlWithScrollState() {
|
||||||
|
const baseUrl = `/wcctools-route/${this.selectedType}/${this.selectedItemName}/${this.selectedViewport}/${this.selectedTheme}`;
|
||||||
|
const queryParams = new URLSearchParams();
|
||||||
|
|
||||||
|
if (this.frameScrollY > 0) {
|
||||||
|
queryParams.set('frameScrollY', this.frameScrollY.toString());
|
||||||
|
}
|
||||||
|
if (this.sidebarScrollY > 0) {
|
||||||
|
queryParams.set('sidebarScrollY', this.sidebarScrollY.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
const queryString = queryParams.toString();
|
||||||
|
const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
||||||
|
|
||||||
|
// Use replaceState to update URL without navigation
|
||||||
|
window.history.replaceState(null, '', fullUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async applyScrollPositions() {
|
||||||
|
// Only apply scroll positions once to avoid interfering with user scrolling
|
||||||
|
if (this.scrollPositionsApplied) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wccFrame = await this.wccFrame;
|
||||||
|
const wccSidebar = this.shadowRoot.querySelector('wcc-sidebar');
|
||||||
|
|
||||||
|
if (wccFrame && this.frameScrollY > 0) {
|
||||||
|
// The frame element itself is the scrollable container
|
||||||
|
wccFrame.scrollTop = this.frameScrollY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wccSidebar && this.sidebarScrollY > 0) {
|
||||||
|
// The sidebar element itself is the scrollable container
|
||||||
|
wccSidebar.scrollTop = this.sidebarScrollY;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scrollPositionsApplied = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,12 @@ export class WccFrame extends DeesElement {
|
|||||||
@property()
|
@property()
|
||||||
public viewport: string;
|
public viewport: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public advancedEditorOpen: boolean = false;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public isFullscreen: boolean = false;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
@@ -22,7 +28,6 @@ export class WccFrame extends DeesElement {
|
|||||||
left: 200px;
|
left: 200px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
bottom: 100px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
@@ -41,7 +46,19 @@ export class WccFrame extends DeesElement {
|
|||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
${(() => {
|
${this.isFullscreen ? `
|
||||||
|
border: none !important;
|
||||||
|
left: 0px !important;
|
||||||
|
right: 0px !important;
|
||||||
|
top: 0px !important;
|
||||||
|
bottom: 0px !important;
|
||||||
|
` : `
|
||||||
|
bottom: ${this.advancedEditorOpen ? '400px' : '100px'};
|
||||||
|
border: 10px solid #ffaeaf;
|
||||||
|
left: 200px;
|
||||||
|
`}
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
${this.isFullscreen ? 'padding: 0px;' : (() => {
|
||||||
switch (this.viewport) {
|
switch (this.viewport) {
|
||||||
case 'desktop':
|
case 'desktop':
|
||||||
return `
|
return `
|
||||||
@@ -70,7 +87,7 @@ export class WccFrame extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.viewport {
|
.viewport {
|
||||||
${this.viewport !== 'desktop'
|
${!this.isFullscreen && this.viewport !== 'desktop'
|
||||||
? html` border-right: 1px dotted #444; border-left: 1px dotted #444; `
|
? html` border-right: 1px dotted #444; border-left: 1px dotted #444; `
|
||||||
: html``
|
: html``
|
||||||
}
|
}
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
|
import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
|
||||||
import { WccDashboard } from './wcc-dashboard.js';
|
import { WccDashboard } from './wcc-dashboard.js';
|
||||||
|
import type { TTemplateFactory } from './wcctools.helpers.js';
|
||||||
|
|
||||||
export type TPropertyType = 'String' | 'Number' | 'Boolean' | 'Object' | 'Enum' | 'Array';
|
export type TPropertyType = 'String' | 'Number' | 'Boolean' | 'Object' | 'Enum' | 'Array';
|
||||||
|
|
||||||
@@ -20,7 +21,7 @@ export class WccProperties extends DeesElement {
|
|||||||
public dashboardRef: WccDashboard;
|
public dashboardRef: WccDashboard;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
public selectedItem: (() => TemplateResult) | DeesElement;
|
public selectedItem: TTemplateFactory | DeesElement;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
public selectedViewport: TEnvironment = 'native';
|
public selectedViewport: TEnvironment = 'native';
|
||||||
@@ -31,34 +32,72 @@ export class WccProperties extends DeesElement {
|
|||||||
@property()
|
@property()
|
||||||
public warning: string = null;
|
public warning: string = null;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
public isFullscreen: boolean = false;
|
||||||
|
|
||||||
@state()
|
@state()
|
||||||
propertyContent: TemplateResult[] = [];
|
propertyContent: TemplateResult[] = [];
|
||||||
|
|
||||||
|
@state()
|
||||||
|
editingProperties: Array<{
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
value: any;
|
||||||
|
element: HTMLElement;
|
||||||
|
editorValue: string;
|
||||||
|
editorError: string;
|
||||||
|
}> = [];
|
||||||
|
|
||||||
|
public editorHeight: number = 300;
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
font-family: 'Roboto', sans-serif;
|
/* CSS Variables - Always dark theme */
|
||||||
|
--background: #0a0a0a;
|
||||||
|
--foreground: #e5e5e5;
|
||||||
|
--card: #0f0f0f;
|
||||||
|
--card-foreground: #f0f0f0;
|
||||||
|
--muted: #1a1a1a;
|
||||||
|
--muted-foreground: #666;
|
||||||
|
--accent: #222;
|
||||||
|
--accent-foreground: #fff;
|
||||||
|
--border: rgba(255, 255, 255, 0.06);
|
||||||
|
--input: #141414;
|
||||||
|
--primary: #3b82f6;
|
||||||
|
--primary-foreground: #fff;
|
||||||
|
--ring: #3b82f6;
|
||||||
|
--radius: 4px;
|
||||||
|
--radius-sm: 2px;
|
||||||
|
--radius-md: 4px;
|
||||||
|
--radius-lg: 6px;
|
||||||
|
|
||||||
|
/* Base styles */
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 200px;
|
left: 200px;
|
||||||
height: 100px;
|
height: ${this.editingProperties.length > 0 ? 100 + this.editorHeight : 100}px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
right: 0px;
|
right: 0px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #111;
|
background: var(--background);
|
||||||
color: #fff;
|
color: var(--foreground);
|
||||||
|
display: ${this.isFullscreen ? 'none' : 'block'};
|
||||||
}
|
}
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto 150px 300px 70px;
|
grid-template-columns: 1fr 150px 300px 70px;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
.properties {
|
.properties {
|
||||||
border-right: 1px solid #999;
|
background: transparent;
|
||||||
height: 100px;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 33% 33% 33%;
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
|
align-content: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-symbols-outlined {
|
.material-symbols-outlined {
|
||||||
@@ -77,152 +116,547 @@ export class WccProperties extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.properties .property {
|
.properties .property {
|
||||||
padding: 5px;
|
padding: 0.4rem;
|
||||||
background: #444;
|
background: transparent;
|
||||||
border: 1px solid #000;
|
border-right: 1px solid var(--border);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.properties .property:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.properties .property-label {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 400;
|
||||||
|
color: #888;
|
||||||
|
margin-bottom: 0.2rem;
|
||||||
|
text-transform: capitalize;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.properties input,
|
.properties input[type="text"],
|
||||||
|
.properties input[type="number"],
|
||||||
.properties select {
|
.properties select {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background: #333;
|
padding: 0.25rem 0.4rem;
|
||||||
border: none;
|
background: var(--input);
|
||||||
color: #fff;
|
border: 1px solid transparent;
|
||||||
|
color: var(--foreground);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.properties input[type="text"]:focus,
|
||||||
|
.properties input[type="number"]:focus,
|
||||||
|
.properties select:focus {
|
||||||
|
border-color: var(--primary);
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.properties input[type="checkbox"] {
|
||||||
|
width: 1rem;
|
||||||
|
height: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
accent-color: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.properties .editor-button {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
background: var(--input);
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.properties .editor-button:hover {
|
||||||
|
border-color: var(--primary);
|
||||||
|
background: rgba(59, 130, 246, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.viewportSelector,
|
.viewportSelector,
|
||||||
.themeSelector {
|
.themeSelector {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
border-right: 1px solid #999;
|
background: transparent;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-right: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
.selectorButtons2 {
|
.selectorButtons2 {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 50% 50%;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
.selectorButtons4 {
|
.selectorButtons4 {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 25% 25% 25% 25%;
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
.button {
|
.button {
|
||||||
padding: 10px;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0.5rem 0.25rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
border: 1px solid #000;
|
background: transparent;
|
||||||
transition: all 0.2s;
|
border: 1px solid var(--border);
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
gap: 0.2rem;
|
||||||
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button:hover {
|
.button:hover {
|
||||||
color: #333;
|
background: rgba(59, 130, 246, 0.05);
|
||||||
background: #fff;
|
color: #bbb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.selected {
|
.button.selected {
|
||||||
background: #455a64;
|
background: rgba(59, 130, 246, 0.15);
|
||||||
|
color: var(--primary);
|
||||||
|
border-color: rgba(59, 130, 246, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.button.selected:hover {
|
.button.selected:hover {
|
||||||
color: #ffffff;
|
background: rgba(59, 130, 246, 0.2);
|
||||||
background: #455a64;
|
}
|
||||||
|
|
||||||
|
.button .material-symbols-outlined {
|
||||||
|
font-size: 18px;
|
||||||
|
font-variation-settings: 'FILL' 0, 'wght' 300;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.selected .material-symbols-outlined {
|
||||||
|
font-variation-settings: 'FILL' 1, 'wght' 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panelheading {
|
.panelheading {
|
||||||
padding: 5px;
|
padding: 0.3rem 0.5rem;
|
||||||
font-weight: bold;
|
font-weight: 500;
|
||||||
background: #444;
|
font-size: 0.65rem;
|
||||||
border: 1px solid #000;
|
background: rgba(59, 130, 246, 0.03);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
color: #888;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
}
|
}
|
||||||
.docs {
|
.docs {
|
||||||
text-align: center;
|
display: flex;
|
||||||
line-height: 100px;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.docs:hover {
|
.docs:hover {
|
||||||
color: #333;
|
background: rgba(59, 130, 246, 0.05);
|
||||||
background: #fff;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning {
|
.warning {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
background: #222;
|
background: rgba(20, 20, 20, 0.8);
|
||||||
color: #CCC;
|
color: #888;
|
||||||
top: 0px;
|
top: 0.5rem;
|
||||||
bottom: 0px;
|
bottom: 0.5rem;
|
||||||
left: 0px;
|
left: 0.5rem;
|
||||||
right: 520px;
|
right: calc(520px + 0.5rem);
|
||||||
text-align: center;
|
display: flex;
|
||||||
padding: 35px;
|
align-items: center;
|
||||||
font-size: 25px;
|
justify-content: center;
|
||||||
|
padding: 1.5rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.advanced-editor-container {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
height: ${this.editorHeight}px;
|
||||||
|
background: #050505;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header-bar {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: transparent;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header-title {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #666;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-close-all {
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
background: transparent;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: #999;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-close-all:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
border-color: rgba(255, 255, 255, 0.12);
|
||||||
|
color: #f87171;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editors-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
gap: 0;
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editors-container::-webkit-scrollbar {
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editors-container::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editors-container::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editors-container::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-instance {
|
||||||
|
min-width: 320px;
|
||||||
|
flex: 1;
|
||||||
|
max-width: 480px;
|
||||||
|
background: rgba(10, 10, 10, 0.6);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
overflow: hidden;
|
||||||
|
margin-right: 0.75rem;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-instance:hover {
|
||||||
|
border-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-instance:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-header {
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: rgba(255, 255, 255, 0.02);
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-title {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #999;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
font-family: 'Consolas', 'Monaco', monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-button {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #666;
|
||||||
|
font-size: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-button:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-button.primary {
|
||||||
|
color: #4ade80;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-button.primary:hover {
|
||||||
|
background: rgba(74, 222, 128, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
min-height: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: #d0d0d0;
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
padding: 0.75rem;
|
||||||
|
resize: none;
|
||||||
|
outline: none;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-textarea:focus {
|
||||||
|
background: rgba(255, 255, 255, 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-textarea::selection {
|
||||||
|
background: rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-error {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
background: rgba(239, 68, 68, 0.9);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 500;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.375rem;
|
||||||
|
border-top: 1px solid rgba(239, 68, 68, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-error::before {
|
||||||
|
content: '!';
|
||||||
|
display: inline-flex;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="grid">
|
${this.editingProperties.length > 0 ? html`
|
||||||
<div class="properties">
|
<div class="advanced-editor-container">
|
||||||
<div class="panelheading">Properties</div>
|
<div class="editor-header-bar">
|
||||||
${this.propertyContent}
|
<div class="editor-header-title">Property Editors</div>
|
||||||
</div>
|
<button class="editor-close-all" @click=${this.closeAllEditors}>
|
||||||
<div class="themeSelector">
|
Close All
|
||||||
<div class="panelheading">Theme</div>
|
</button>
|
||||||
<div class="selectorButtons2">
|
</div>
|
||||||
<div
|
<div class="editors-container">
|
||||||
class="button ${this.selectedTheme === 'dark' ? 'selected' : null}"
|
${this.editingProperties.length === 0 ? html`
|
||||||
@click=${() => {
|
<div style="
|
||||||
this.selectTheme('dark');
|
flex: 1;
|
||||||
}}
|
display: flex;
|
||||||
>
|
align-items: center;
|
||||||
Dark<br /><i class="material-symbols-outlined">brightness_3</i>
|
justify-content: center;
|
||||||
</div>
|
color: #666;
|
||||||
<div
|
font-size: 0.875rem;
|
||||||
class="button ${this.selectedTheme === 'bright' ? 'selected' : null}"
|
text-align: center;
|
||||||
@click=${() => {
|
padding: 2rem;
|
||||||
this.selectTheme('bright');
|
">
|
||||||
}}
|
<div>
|
||||||
>
|
<div style="margin-bottom: 0.5rem; font-size: 1.5rem; opacity: 0.5;">{ }</div>
|
||||||
Bright<br /><i class="material-symbols-outlined">flare</i>
|
<div>No properties being edited</div>
|
||||||
</div>
|
<div style="font-size: 0.75rem; margin-top: 0.25rem; opacity: 0.7;">Click "Edit Object/Array" buttons to start editing</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
` : null}
|
||||||
|
${this.editingProperties.map(prop => html`
|
||||||
|
<div class="editor-instance">
|
||||||
|
<div class="editor-header">
|
||||||
|
<div class="editor-title">${prop.name}</div>
|
||||||
|
<div class="editor-actions">
|
||||||
|
<button class="editor-button" @click=${() => this.handleEditorCancel(prop.id)}>✕</button>
|
||||||
|
<button class="editor-button primary" @click=${() => this.handleEditorSave(prop.id)}>✓</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="editor-content">
|
||||||
|
<textarea
|
||||||
|
class="editor-textarea"
|
||||||
|
.value=${prop.editorValue}
|
||||||
|
@input=${(e: InputEvent) => {
|
||||||
|
const editor = this.editingProperties.find(p => p.id === prop.id);
|
||||||
|
if (editor) {
|
||||||
|
editor.editorValue = (e.target as HTMLTextAreaElement).value;
|
||||||
|
editor.editorError = '';
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
@keydown=${(e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Tab') {
|
||||||
|
e.preventDefault();
|
||||||
|
const target = e.target as HTMLTextAreaElement;
|
||||||
|
const start = target.selectionStart;
|
||||||
|
const end = target.selectionEnd;
|
||||||
|
const value = target.value;
|
||||||
|
target.value = value.substring(0, start) + ' ' + value.substring(end);
|
||||||
|
target.selectionStart = target.selectionEnd = start + 2;
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></textarea>
|
||||||
|
${prop.editorError ? html`
|
||||||
|
<div class="editor-error">${prop.editorError}</div>
|
||||||
|
` : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="viewportSelector">
|
` : null}
|
||||||
<div class="panelheading">Viewport</div>
|
<div class="main-content">
|
||||||
<div class="selectorButtons4">
|
<div class="grid">
|
||||||
<div
|
<div class="properties">
|
||||||
class="button ${this.selectedViewport === 'phone' ? 'selected' : null}"
|
${this.propertyContent}
|
||||||
@click=${() => {
|
</div>
|
||||||
this.selectViewport('phone');
|
<div class="themeSelector">
|
||||||
}}
|
<div class="panelheading">Theme</div>
|
||||||
>
|
<div class="selectorButtons2">
|
||||||
Phone<br /><i class="material-symbols-outlined">smartphone</i>
|
<div
|
||||||
</div>
|
class="button ${this.selectedTheme === 'dark' ? 'selected' : null}"
|
||||||
<div
|
@click=${() => {
|
||||||
class="button ${this.selectedViewport === 'phablet' ? 'selected' : null}"
|
this.selectTheme('dark');
|
||||||
@click=${() => {
|
}}
|
||||||
this.selectViewport('phablet');
|
>
|
||||||
}}
|
Dark<i class="material-symbols-outlined">brightness_3</i>
|
||||||
>
|
</div>
|
||||||
Phablet<br /><i class="material-symbols-outlined">smartphone</i>
|
<div
|
||||||
</div>
|
class="button ${this.selectedTheme === 'bright' ? 'selected' : null}"
|
||||||
<div
|
@click=${() => {
|
||||||
class="button ${this.selectedViewport === 'tablet' ? 'selected' : null}"
|
this.selectTheme('bright');
|
||||||
@click=${() => {
|
}}
|
||||||
this.selectViewport('tablet');
|
>
|
||||||
}}
|
Bright<i class="material-symbols-outlined">flare</i>
|
||||||
>
|
</div>
|
||||||
Tablet<br /><i class="material-symbols-outlined">tablet</i>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="button ${this.selectedViewport === 'desktop' ||
|
|
||||||
this.selectedViewport === 'native'
|
|
||||||
? 'selected'
|
|
||||||
: null}"
|
|
||||||
@click=${() => {
|
|
||||||
this.selectViewport('native');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Desktop<br /><i class="material-symbols-outlined">desktop_windows</i>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="viewportSelector">
|
||||||
|
<div class="panelheading">Viewport</div>
|
||||||
|
<div class="selectorButtons4">
|
||||||
|
<div
|
||||||
|
class="button ${this.selectedViewport === 'phone' ? 'selected' : null}"
|
||||||
|
@click=${() => {
|
||||||
|
this.selectViewport('phone');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Phone<i class="material-symbols-outlined">smartphone</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="button ${this.selectedViewport === 'phablet' ? 'selected' : null}"
|
||||||
|
@click=${() => {
|
||||||
|
this.selectViewport('phablet');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Phablet<i class="material-symbols-outlined">smartphone</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="button ${this.selectedViewport === 'tablet' ? 'selected' : null}"
|
||||||
|
@click=${() => {
|
||||||
|
this.selectViewport('tablet');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Tablet<i class="material-symbols-outlined">tablet</i>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="button ${this.selectedViewport === 'desktop' ||
|
||||||
|
this.selectedViewport === 'native'
|
||||||
|
? 'selected'
|
||||||
|
: null}"
|
||||||
|
@click=${() => {
|
||||||
|
this.selectViewport('native');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Desktop<i class="material-symbols-outlined">desktop_windows</i>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="docs" @click=${() => this.toggleFullscreen()}>
|
||||||
|
<i class="material-symbols-outlined" style="font-size: 20px;">
|
||||||
|
${this.isFullscreen ? 'fullscreen_exit' : 'fullscreen'}
|
||||||
|
</i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="docs">Docs</div>
|
${this.warning ? html`<div class="warning">${this.warning}</div>` : null}
|
||||||
</div>
|
</div>
|
||||||
${this.warning ? html`<div class="warning">${this.warning}</div>` : null}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,7 +790,7 @@ export class WccProperties extends DeesElement {
|
|||||||
propertyArray.push(
|
propertyArray.push(
|
||||||
html`
|
html`
|
||||||
<div class="property">
|
<div class="property">
|
||||||
${key} / ${propertyTypeString}<br />
|
<div class="property-label">${key} (${propertyTypeString})</div>
|
||||||
${(() => {
|
${(() => {
|
||||||
switch (propertyTypeString) {
|
switch (propertyTypeString) {
|
||||||
case 'Boolean':
|
case 'Boolean':
|
||||||
@@ -401,6 +835,17 @@ export class WccProperties extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
</select>`;
|
</select>`;
|
||||||
|
case 'Object':
|
||||||
|
case 'Array':
|
||||||
|
return html`<button
|
||||||
|
class="editor-button"
|
||||||
|
style="width: 100%; margin-top: 0.25rem;"
|
||||||
|
@click="${() => this.openAdvancedEditor(key, firstFoundInstantiatedElement[key], firstFoundInstantiatedElement)}"
|
||||||
|
>
|
||||||
|
Edit ${propertyTypeString}
|
||||||
|
</button>`;
|
||||||
|
default:
|
||||||
|
return html`<div style="color: #666; font-size: 0.7rem;">Unsupported type</div>`;
|
||||||
}
|
}
|
||||||
})()}
|
})()}
|
||||||
</div>
|
</div>
|
||||||
@@ -452,4 +897,101 @@ export class WccProperties extends DeesElement {
|
|||||||
);
|
);
|
||||||
this.dashboardRef.buildUrl();
|
this.dashboardRef.buildUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private openAdvancedEditor(propertyName: string, value: any, element: HTMLElement) {
|
||||||
|
// Check if this property is already being edited
|
||||||
|
const existingEditor = this.editingProperties.find(p => p.name === propertyName && p.element === element);
|
||||||
|
if (existingEditor) {
|
||||||
|
return; // Property is already open for editing
|
||||||
|
}
|
||||||
|
|
||||||
|
const newEditor = {
|
||||||
|
id: `${propertyName}-${Date.now()}`,
|
||||||
|
name: propertyName,
|
||||||
|
value: value,
|
||||||
|
element: element,
|
||||||
|
editorValue: JSON.stringify(value, null, 2),
|
||||||
|
editorError: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
this.editingProperties = [...this.editingProperties, newEditor];
|
||||||
|
|
||||||
|
// Notify parent to resize frame if this is the first editor
|
||||||
|
if (this.editingProperties.length === 1) {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('editorStateChanged', {
|
||||||
|
detail: { isOpen: true },
|
||||||
|
bubbles: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleEditorSave(editorId: string) {
|
||||||
|
const editor = this.editingProperties.find(p => p.id === editorId);
|
||||||
|
if (!editor) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsedValue = JSON.parse(editor.editorValue);
|
||||||
|
editor.element[editor.name] = parsedValue;
|
||||||
|
|
||||||
|
// Remove this editor from the list
|
||||||
|
this.editingProperties = this.editingProperties.filter(p => p.id !== editorId);
|
||||||
|
|
||||||
|
// If no more editors, notify parent to resize frame
|
||||||
|
if (this.editingProperties.length === 0) {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('editorStateChanged', {
|
||||||
|
detail: { isOpen: false },
|
||||||
|
bubbles: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh properties display
|
||||||
|
this.createProperties();
|
||||||
|
} catch (error) {
|
||||||
|
// Update error for this specific editor
|
||||||
|
const editorIndex = this.editingProperties.findIndex(p => p.id === editorId);
|
||||||
|
if (editorIndex !== -1) {
|
||||||
|
this.editingProperties[editorIndex].editorError = `Invalid JSON: ${error.message}`;
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleEditorCancel(editorId: string) {
|
||||||
|
// Remove this editor from the list
|
||||||
|
this.editingProperties = this.editingProperties.filter(p => p.id !== editorId);
|
||||||
|
|
||||||
|
// If no more editors, notify parent to resize frame
|
||||||
|
if (this.editingProperties.length === 0) {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('editorStateChanged', {
|
||||||
|
detail: { isOpen: false },
|
||||||
|
bubbles: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private closeAllEditors() {
|
||||||
|
this.editingProperties = [];
|
||||||
|
|
||||||
|
// Notify parent to resize frame back
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('editorStateChanged', {
|
||||||
|
detail: { isOpen: false },
|
||||||
|
bubbles: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private toggleFullscreen() {
|
||||||
|
this.dispatchEvent(
|
||||||
|
new CustomEvent('toggleFullscreen', {
|
||||||
|
bubbles: true
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,13 +1,14 @@
|
|||||||
import * as plugins from '../wcctools.plugins.js';
|
import * as plugins from '../wcctools.plugins.js';
|
||||||
import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
|
import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
|
||||||
import { WccDashboard } from './wcc-dashboard.js';
|
import { WccDashboard } from './wcc-dashboard.js';
|
||||||
|
import type { TTemplateFactory } from './wcctools.helpers.js';
|
||||||
|
|
||||||
export type TElementType = 'element' | 'page';
|
export type TElementType = 'element' | 'page';
|
||||||
|
|
||||||
@customElement('wcc-sidebar')
|
@customElement('wcc-sidebar')
|
||||||
export class WccSidebar extends DeesElement {
|
export class WccSidebar extends DeesElement {
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public selectedItem: DeesElement | (() => TemplateResult);
|
public selectedItem: DeesElement | TTemplateFactory;
|
||||||
|
|
||||||
@property({ attribute: false })
|
@property({ attribute: false })
|
||||||
public selectedType: TElementType;
|
public selectedType: TElementType;
|
||||||
@@ -15,33 +16,73 @@ export class WccSidebar extends DeesElement {
|
|||||||
@property()
|
@property()
|
||||||
public dashboardRef: WccDashboard;
|
public dashboardRef: WccDashboard;
|
||||||
|
|
||||||
|
@property()
|
||||||
|
public isFullscreen: boolean = false;
|
||||||
|
|
||||||
public render(): TemplateResult {
|
public render(): TemplateResult {
|
||||||
return html`
|
return html`
|
||||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" rel="stylesheet" />
|
||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
/* CSS Variables - Always dark theme to match wcc-properties */
|
||||||
border-right: 1px solid #999;
|
--background: #0a0a0a;
|
||||||
font-family: 'Roboto', 'Inter', sans-serif;
|
--foreground: #e5e5e5;
|
||||||
font-size: 12px;
|
--card: #0f0f0f;
|
||||||
|
--card-foreground: #f0f0f0;
|
||||||
|
--muted: #1a1a1a;
|
||||||
|
--muted-foreground: #666;
|
||||||
|
--accent: #222;
|
||||||
|
--accent-foreground: #fff;
|
||||||
|
--border: rgba(255, 255, 255, 0.06);
|
||||||
|
--input: #141414;
|
||||||
|
--primary: #3b82f6;
|
||||||
|
--primary-foreground: #fff;
|
||||||
|
--ring: #3b82f6;
|
||||||
|
--radius: 4px;
|
||||||
|
|
||||||
|
display: ${this.isFullscreen ? 'none' : 'block'};
|
||||||
|
border-right: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 0px;
|
left: 0px;
|
||||||
width: 200px;
|
width: 200px;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
overflow-y: scroll;
|
overflow-y: auto;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
background: #222;
|
background: var(--background);
|
||||||
color: #fff;
|
color: var(--foreground);
|
||||||
padding: 5px;
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
padding: 0.3rem 0.75rem;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: #888;
|
||||||
|
margin: 0;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
background: rgba(59, 130, 246, 0.03);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
h3:first-child {
|
||||||
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.material-symbols-outlined {
|
.material-symbols-outlined {
|
||||||
font-family: 'Material Symbols Outlined';
|
font-family: 'Material Symbols Outlined';
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-size: 24px; /* Preferred icon size */
|
font-size: 16px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
text-transform: none;
|
text-transform: none;
|
||||||
@@ -49,51 +90,75 @@ export class WccSidebar extends DeesElement {
|
|||||||
word-wrap: normal;
|
word-wrap: normal;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
direction: ltr;
|
direction: ltr;
|
||||||
font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 48;
|
font-variation-settings: 'FILL' 0, 'wght' 300, 'GRAD' 0, 'opsz' 24;
|
||||||
|
opacity: 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectOption {
|
.selectOption {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
line-height: 24px;
|
margin: 0.125rem 0.5rem;
|
||||||
padding: 5px;
|
padding: 0.5rem 0.75rem;
|
||||||
transition: all 0.2s;
|
transition: all 0.15s ease;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 28px auto;
|
grid-template-columns: 20px 1fr;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #999;
|
||||||
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectOption:hover {
|
.selectOption:hover {
|
||||||
padding: 5px;
|
background: rgba(59, 130, 246, 0.05);
|
||||||
color: #333;
|
color: #bbb;
|
||||||
background: #fff;
|
}
|
||||||
|
|
||||||
|
.selectOption:hover .material-symbols-outlined {
|
||||||
|
opacity: 0.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectOption.selected {
|
.selectOption.selected {
|
||||||
background: #455A64;;
|
background: rgba(59, 130, 246, 0.15);
|
||||||
}
|
color: var(--primary);
|
||||||
|
|
||||||
.selectOption.selected:hover {
|
|
||||||
color: #ffffff;
|
|
||||||
background: #455A64;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selectOption .material-symbols-outlined {
|
|
||||||
color: #666;
|
|
||||||
display: block;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectOption.selected .material-symbols-outlined {
|
.selectOption.selected .material-symbols-outlined {
|
||||||
color: #000;
|
opacity: 1;
|
||||||
|
font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectOption.selected:hover {
|
||||||
|
background: rgba(59, 130, 246, 0.2);
|
||||||
|
color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectOption .text {
|
.selectOption .text {
|
||||||
display: block;
|
display: block;
|
||||||
word-wrap: break-word;
|
overflow: hidden;
|
||||||
word-break: break-all;
|
text-overflow: ellipsis;
|
||||||
max-width: 100%;
|
white-space: nowrap;
|
||||||
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<h3>Pages</h3>
|
<h3>Pages</h3>
|
||||||
@@ -138,7 +203,7 @@ export class WccSidebar extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: (() => TemplateResult) | DeesElement) {
|
public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement) {
|
||||||
console.log('selected item');
|
console.log('selected item');
|
||||||
console.log(itemNameArg);
|
console.log(itemNameArg);
|
||||||
console.log(itemArg);
|
console.log(itemArg);
|
||||||
|
9
ts_web/elements/wcctools.helpers.ts
Normal file
9
ts_web/elements/wcctools.helpers.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import type { TemplateResult } from 'lit';
|
||||||
|
|
||||||
|
export type TTemplateFactory = () => TemplateResult | Promise<TemplateResult>;
|
||||||
|
|
||||||
|
export const resolveTemplateFactory = async (
|
||||||
|
factoryArg: TTemplateFactory
|
||||||
|
): Promise<TemplateResult> => {
|
||||||
|
return await Promise.resolve(factoryArg());
|
||||||
|
};
|
@@ -1,7 +1,11 @@
|
|||||||
import { WccDashboard } from './elements/wcc-dashboard.js';
|
import { WccDashboard } from './elements/wcc-dashboard.js';
|
||||||
import { LitElement, type TemplateResult } from 'lit';
|
import { LitElement } from 'lit';
|
||||||
|
import type { TTemplateFactory } from './elements/wcctools.helpers.js';
|
||||||
|
|
||||||
const setupWccTools = (elementsArg?: { [key: string]: LitElement }, pagesArg?: { [key: string]: () => TemplateResult }) => {
|
const setupWccTools = (
|
||||||
|
elementsArg?: { [key: string]: LitElement },
|
||||||
|
pagesArg?: Record<string, TTemplateFactory>
|
||||||
|
) => {
|
||||||
let hasRun = false;
|
let hasRun = false;
|
||||||
const runWccToolsSetup = async () => {
|
const runWccToolsSetup = async () => {
|
||||||
if (document.readyState === 'complete' && !hasRun) {
|
if (document.readyState === 'complete' && !hasRun) {
|
||||||
|
1
ts_web/pages/index.ts
Normal file
1
ts_web/pages/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const page1 = null;
|
Reference in New Issue
Block a user