Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
923bedc4fc | |||
e8b771bde4 | |||
7a248993bc | |||
03f215e0f1 | |||
216cb0288d | |||
65acda3de1 | |||
88ff74bb86 | |||
98a5b1b5a3 | |||
bbf738d4e2 | |||
4f8ca7061a | |||
d26d99dbff | |||
c1d8e347de | |||
b6c41caf44 | |||
b858b3b9e2 | |||
4ed37086ae | |||
b4c0de47b9 | |||
e11f0df950 | |||
c64b106569 | |||
3d1948b93e | |||
21b7158a35 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,7 +3,6 @@
|
|||||||
# artifacts
|
# artifacts
|
||||||
coverage/
|
coverage/
|
||||||
public/
|
public/
|
||||||
pages/
|
|
||||||
|
|
||||||
# installs
|
# installs
|
||||||
node_modules/
|
node_modules/
|
||||||
|
57
changelog.md
Normal file
57
changelog.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 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.)
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@design.estate/dees-wcctools",
|
"name": "@design.estate/dees-wcctools",
|
||||||
"version": "1.0.96",
|
"version": "1.1.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": {
|
||||||
|
120
readme.hints.md
120
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
|
||||||
@ -12,16 +114,24 @@ The properties panel had timing issues detecting rendered elements because:
|
|||||||
1. Added a 100ms initial delay to allow render completion
|
1. Added a 100ms initial delay to allow render completion
|
||||||
2. Implemented recursive element search that:
|
2. Implemented recursive element search that:
|
||||||
- Searches through nested children up to 5 levels deep
|
- Searches through nested children up to 5 levels deep
|
||||||
- Checks shadow roots of elements
|
- Checks both light DOM and shadow DOM for all elements
|
||||||
- Handles complex DOM structures
|
- Handles complex DOM structures generically
|
||||||
|
- Works with any wrapper elements, not specific to dees-demowrapper
|
||||||
3. Added retry mechanism with up to 5 attempts (200ms between retries)
|
3. Added retry mechanism with up to 5 attempts (200ms between retries)
|
||||||
4. Improved error messages to show retry count
|
4. Improved error messages to show retry count
|
||||||
|
5. Comprehensive error handling:
|
||||||
|
- Errors in element search don't break the update cycle
|
||||||
|
- Individual property errors don't prevent other properties from rendering
|
||||||
|
- scheduleUpdate always completes even if createProperties fails
|
||||||
|
- Clears warnings and property content appropriately on errors
|
||||||
|
|
||||||
### Code Flow
|
### Code Flow
|
||||||
1. Dashboard renders element demo into viewport using `render(anonItem.demo(), viewport)`
|
1. Dashboard renders element demo into viewport using `render(anonItem.demo(), viewport)`
|
||||||
2. Properties panel waits, then searches recursively for the element instance
|
2. Properties panel waits 200ms for demo wrappers to run and set initial values
|
||||||
3. If not found, retries with delays to handle async rendering
|
3. Searches recursively for the element instance
|
||||||
4. Once found, extracts and displays element properties
|
4. If not found, retries with delays to handle async rendering
|
||||||
|
5. Once found, extracts and displays element properties
|
||||||
|
6. Uses property binding (`.value=`) instead of attribute binding to prevent input events during initialization
|
||||||
|
|
||||||
## Demo Tools
|
## Demo Tools
|
||||||
|
|
||||||
|
@ -61,4 +61,40 @@ The properties panel has timing issues detecting rendered elements because:
|
|||||||
- Access children via wrapper.children property
|
- Access children via wrapper.children property
|
||||||
- Updated documentation with correct import path (lowercase 'demotools')
|
- Updated documentation with correct import path (lowercase 'demotools')
|
||||||
- Examples show how to use querySelector for powerful element selection
|
- Examples show how to use querySelector for powerful element selection
|
||||||
- Added clarifying comment about querySelector working on slotted content
|
- Added clarifying comment about querySelector working on slotted content
|
||||||
|
|
||||||
|
## Fixed Properties Panel Compatibility:
|
||||||
|
- Made element search generic - works with any container elements
|
||||||
|
- Searches both light DOM and shadow DOM recursively
|
||||||
|
- Improved error handling to prevent breaking the update cycle
|
||||||
|
- Errors in one property don't prevent others from rendering
|
||||||
|
- Detection continues working even after errors occur
|
||||||
|
- Maintains compatibility with all element structures
|
||||||
|
|
||||||
|
# Test Elements Created (COMPLETED)
|
||||||
|
|
||||||
|
## Created comprehensive test elements:
|
||||||
|
1. **test-noprops** - Element with no @property decorators
|
||||||
|
2. **test-complextypes** - Element with arrays, objects, dates, and complex nested data
|
||||||
|
3. **test-withwrapper** - Element that uses dees-demowrapper in its demo
|
||||||
|
4. **test-edgecases** - Element with edge cases (null, undefined, NaN, Infinity, circular refs)
|
||||||
|
5. **test-nested** - Element with deeply nested structure to test recursive search
|
||||||
|
|
||||||
|
These test various scenarios:
|
||||||
|
- Properties panel handling of elements without properties
|
||||||
|
- Complex data type display and editing
|
||||||
|
- Element detection inside dees-demowrapper
|
||||||
|
- Error handling for problematic values
|
||||||
|
- Deep nesting and shadow DOM traversal
|
||||||
|
|
||||||
|
# Fixed Demo Value Overwriting (COMPLETED)
|
||||||
|
|
||||||
|
## Issue:
|
||||||
|
Properties panel was overwriting values set by demo functions
|
||||||
|
|
||||||
|
## Solution:
|
||||||
|
1. Changed from attribute binding (`value=`) to property binding (`.value=`)
|
||||||
|
2. This prevents browser from firing input events during initialization
|
||||||
|
3. Added proper number parsing for number inputs
|
||||||
|
4. Increased initial wait to 200ms for demo wrappers to complete
|
||||||
|
5. Simplified select element handling to use property binding
|
@ -1 +1,6 @@
|
|||||||
export * from './test-demoelement.js';
|
export * from './test-demoelement.js';
|
||||||
|
export * from './test-noprops.js';
|
||||||
|
export * from './test-complextypes.js';
|
||||||
|
export * from './test-withwrapper.js';
|
||||||
|
export * from './test-edgecases.js';
|
||||||
|
export * from './test-nested.js';
|
||||||
|
137
test/elements/test-complextypes.ts
Normal file
137
test/elements/test-complextypes.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import {
|
||||||
|
DeesElement,
|
||||||
|
customElement,
|
||||||
|
type TemplateResult,
|
||||||
|
html,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
interface IComplexData {
|
||||||
|
name: string;
|
||||||
|
age: number;
|
||||||
|
tags: string[];
|
||||||
|
metadata: {
|
||||||
|
created: Date;
|
||||||
|
modified: Date;
|
||||||
|
author: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('test-complextypes')
|
||||||
|
export class TestComplexTypes extends DeesElement {
|
||||||
|
public static demo = () => html`
|
||||||
|
<test-complextypes
|
||||||
|
.complexData=${{
|
||||||
|
name: 'Test User',
|
||||||
|
age: 25,
|
||||||
|
tags: ['developer', 'designer'],
|
||||||
|
metadata: {
|
||||||
|
created: new Date(),
|
||||||
|
modified: new Date(),
|
||||||
|
author: 'System'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
></test-complextypes>
|
||||||
|
`;
|
||||||
|
|
||||||
|
@property({ type: Array })
|
||||||
|
public stringArray: string[] = ['apple', 'banana', 'cherry'];
|
||||||
|
|
||||||
|
@property({ type: Array })
|
||||||
|
public numberArray: number[] = [1, 2, 3, 4, 5];
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public complexData: IComplexData = {
|
||||||
|
name: 'Default Name',
|
||||||
|
age: 0,
|
||||||
|
tags: [],
|
||||||
|
metadata: {
|
||||||
|
created: new Date(),
|
||||||
|
modified: new Date(),
|
||||||
|
author: 'Unknown'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@property({ type: Object })
|
||||||
|
public simpleObject = {
|
||||||
|
key1: 'value1',
|
||||||
|
key2: 'value2',
|
||||||
|
key3: 123
|
||||||
|
};
|
||||||
|
|
||||||
|
@property({ attribute: false })
|
||||||
|
public functionProperty = () => {
|
||||||
|
console.log('This is a function property');
|
||||||
|
};
|
||||||
|
|
||||||
|
@property({ type: Date })
|
||||||
|
public dateProperty = new Date();
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
.section {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
color: #666;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
pre {
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
];
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div class="section">
|
||||||
|
<span class="label">String Array:</span>
|
||||||
|
<span class="value">${this.stringArray.join(', ')}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<span class="label">Number Array:</span>
|
||||||
|
<span class="value">${this.numberArray.join(', ')}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<span class="label">Complex Data:</span>
|
||||||
|
<pre>${JSON.stringify(this.complexData, null, 2)}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<span class="label">Simple Object:</span>
|
||||||
|
<pre>${JSON.stringify(this.simpleObject, null, 2)}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<span class="label">Date Property:</span>
|
||||||
|
<span class="value">${this.dateProperty.toLocaleString()}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<span class="label">Function Property:</span>
|
||||||
|
<span class="value">${typeof this.functionProperty}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
195
test/elements/test-edgecases.ts
Normal file
195
test/elements/test-edgecases.ts
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
import {
|
||||||
|
DeesElement,
|
||||||
|
customElement,
|
||||||
|
type TemplateResult,
|
||||||
|
html,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
@customElement('test-edgecases')
|
||||||
|
export class TestEdgeCases extends DeesElement {
|
||||||
|
public static demo = () => html`<test-edgecases></test-edgecases>`;
|
||||||
|
|
||||||
|
// Property with null value
|
||||||
|
@property({ type: String })
|
||||||
|
public nullableString: string | null = null;
|
||||||
|
|
||||||
|
// Property with undefined value
|
||||||
|
@property({ type: Number })
|
||||||
|
public undefinedNumber: number | undefined = undefined;
|
||||||
|
|
||||||
|
// Very long string
|
||||||
|
@property({ type: String })
|
||||||
|
public longString: string = 'Lorem ipsum '.repeat(50);
|
||||||
|
|
||||||
|
// Property with special characters
|
||||||
|
@property({ type: String })
|
||||||
|
public specialChars: string = '!@#$%^&*()_+-=[]{}|;\':",./<>?`~';
|
||||||
|
|
||||||
|
// Property that could cause rendering issues
|
||||||
|
@property({ type: String })
|
||||||
|
public htmlString: string = '<script>alert("test")</script><b>Bold text</b>';
|
||||||
|
|
||||||
|
// Numeric edge cases
|
||||||
|
@property({ type: Number })
|
||||||
|
public infinityNumber: number = Infinity;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public nanNumber: number = NaN;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public veryLargeNumber: number = Number.MAX_SAFE_INTEGER;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public verySmallNumber: number = Number.MIN_SAFE_INTEGER;
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public floatNumber: number = 3.14159265359;
|
||||||
|
|
||||||
|
// Boolean-like values
|
||||||
|
@property({ type: String })
|
||||||
|
public booleanString: string = 'false';
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public booleanNumber: number = 0;
|
||||||
|
|
||||||
|
// Empty values
|
||||||
|
@property({ type: String })
|
||||||
|
public emptyString: string = '';
|
||||||
|
|
||||||
|
@property({ type: Array })
|
||||||
|
public emptyArray: any[] = [];
|
||||||
|
|
||||||
|
@property({ type: Object })
|
||||||
|
public emptyObject: {} = {};
|
||||||
|
|
||||||
|
// Circular reference (should not break properties panel)
|
||||||
|
@property({ attribute: false })
|
||||||
|
public circularRef: any = (() => {
|
||||||
|
const obj: any = { name: 'circular' };
|
||||||
|
obj.self = obj;
|
||||||
|
return obj;
|
||||||
|
})();
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 20px;
|
||||||
|
background: #fff3e0;
|
||||||
|
border: 2px solid #ff9800;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.warning {
|
||||||
|
background: #ffe0b2;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #e65100;
|
||||||
|
}
|
||||||
|
.property {
|
||||||
|
margin: 5px 0;
|
||||||
|
padding: 5px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 2px;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #f57c00;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.special {
|
||||||
|
background: #ffccbc;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
];
|
||||||
|
|
||||||
|
private formatValue(value: any): string {
|
||||||
|
if (value === null) return 'null';
|
||||||
|
if (value === undefined) return 'undefined';
|
||||||
|
if (value === Infinity) return 'Infinity';
|
||||||
|
if (Number.isNaN(value)) return 'NaN';
|
||||||
|
if (typeof value === 'string' && value.length > 50) {
|
||||||
|
return value.substring(0, 50) + '...';
|
||||||
|
}
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
try {
|
||||||
|
return JSON.stringify(value);
|
||||||
|
} catch (e) {
|
||||||
|
return '[Circular Reference]';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div class="warning">
|
||||||
|
⚠️ This element tests edge cases and problematic values
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">Nullable String:</span>
|
||||||
|
<span class="value special">${this.formatValue(this.nullableString)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">Undefined Number:</span>
|
||||||
|
<span class="value special">${this.formatValue(this.undefinedNumber)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">Long String:</span>
|
||||||
|
<span class="value">${this.formatValue(this.longString)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">Special Characters:</span>
|
||||||
|
<span class="value">${this.specialChars}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">HTML String (escaped):</span>
|
||||||
|
<span class="value">${this.htmlString}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">Infinity:</span>
|
||||||
|
<span class="value special">${this.formatValue(this.infinityNumber)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">NaN:</span>
|
||||||
|
<span class="value special">${this.formatValue(this.nanNumber)}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">Very Large Number:</span>
|
||||||
|
<span class="value">${this.veryLargeNumber}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">Float Number:</span>
|
||||||
|
<span class="value">${this.floatNumber}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">Empty String:</span>
|
||||||
|
<span class="value special">[empty]</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="property">
|
||||||
|
<span class="label">Circular Reference:</span>
|
||||||
|
<span class="value special">${this.formatValue(this.circularRef)}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
127
test/elements/test-nested.ts
Normal file
127
test/elements/test-nested.ts
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
import {
|
||||||
|
DeesElement,
|
||||||
|
customElement,
|
||||||
|
type TemplateResult,
|
||||||
|
html,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
// Helper component for nesting
|
||||||
|
@customElement('test-nested-wrapper')
|
||||||
|
class TestNestedWrapper extends DeesElement {
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div style="border: 1px dashed #ccc; padding: 10px; margin: 5px;">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The actual test element deeply nested
|
||||||
|
@customElement('test-nested-target')
|
||||||
|
class TestNestedTarget extends DeesElement {
|
||||||
|
@property({ type: String })
|
||||||
|
public message: string = 'I am deeply nested!';
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public depth: number = 0;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public found: boolean = false;
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 15px;
|
||||||
|
background: #e1f5fe;
|
||||||
|
border: 2px solid #0288d1;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
font-family: monospace;
|
||||||
|
color: #01579b;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
];
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div class="info">
|
||||||
|
<strong>Nested Target Element</strong><br>
|
||||||
|
Message: ${this.message}<br>
|
||||||
|
Depth: ${this.depth}<br>
|
||||||
|
Found by properties panel: ${this.found ? '✅' : '❌'}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@customElement('test-nested')
|
||||||
|
export class TestNested extends DeesElement {
|
||||||
|
public static demo = () => html`
|
||||||
|
<test-nested></test-nested>
|
||||||
|
`;
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
public testId: string = 'nested-test';
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 2px solid #999;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.explanation {
|
||||||
|
background: #fff;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.structure {
|
||||||
|
background: #f0f0f0;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
];
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div class="explanation">
|
||||||
|
<h3>Nested Structure Test</h3>
|
||||||
|
<p>The actual element with properties is nested deep inside multiple layers:</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="structure">
|
||||||
|
<test-nested-wrapper>
|
||||||
|
<div style="padding: 10px; background: #ffe;">
|
||||||
|
<test-nested-wrapper>
|
||||||
|
<div style="padding: 10px; background: #efe;">
|
||||||
|
<test-nested-wrapper>
|
||||||
|
<div style="padding: 10px; background: #eef;">
|
||||||
|
<!-- The target element is here, 3 levels deep -->
|
||||||
|
<test-nested-target
|
||||||
|
.message=${'Found me at depth 3!'}
|
||||||
|
.depth=${3}
|
||||||
|
></test-nested-target>
|
||||||
|
</div>
|
||||||
|
</test-nested-wrapper>
|
||||||
|
</div>
|
||||||
|
</test-nested-wrapper>
|
||||||
|
</div>
|
||||||
|
</test-nested-wrapper>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin-top: 10px; font-style: italic; color: #666;">
|
||||||
|
Properties panel should find the test-nested-target element despite the deep nesting.
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
37
test/elements/test-noprops.ts
Normal file
37
test/elements/test-noprops.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import {
|
||||||
|
DeesElement,
|
||||||
|
customElement,
|
||||||
|
type TemplateResult,
|
||||||
|
html,
|
||||||
|
css,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
@customElement('test-noprops')
|
||||||
|
export class TestNoProps extends DeesElement {
|
||||||
|
public static demo = () => html`<test-noprops></test-noprops>`;
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 20px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.message {
|
||||||
|
font-family: monospace;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
];
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div class="message">
|
||||||
|
This element has no @property decorators.
|
||||||
|
Properties panel should handle this gracefully.
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
111
test/elements/test-withwrapper.ts
Normal file
111
test/elements/test-withwrapper.ts
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
import {
|
||||||
|
DeesElement,
|
||||||
|
customElement,
|
||||||
|
type TemplateResult,
|
||||||
|
html,
|
||||||
|
property,
|
||||||
|
css,
|
||||||
|
} from '@design.estate/dees-element';
|
||||||
|
|
||||||
|
// Import from local demotools
|
||||||
|
import '../../ts_demotools/demotools.js';
|
||||||
|
|
||||||
|
@customElement('test-withwrapper')
|
||||||
|
export class TestWithWrapper extends DeesElement {
|
||||||
|
public static demo = () => html`
|
||||||
|
<dees-demowrapper .runAfterRender=${async (wrapper) => {
|
||||||
|
console.log('DemoWrapper: Found wrapper element', wrapper);
|
||||||
|
|
||||||
|
const testElement = wrapper.querySelector('test-withwrapper');
|
||||||
|
if (testElement) {
|
||||||
|
console.log('DemoWrapper: Found test-withwrapper element');
|
||||||
|
testElement.dynamicValue = 'Set by demo wrapper!';
|
||||||
|
testElement.counter = 100;
|
||||||
|
|
||||||
|
// Test querySelector functionality
|
||||||
|
const innerDiv = wrapper.querySelector('.inner-content');
|
||||||
|
console.log('DemoWrapper: Found inner div:', innerDiv);
|
||||||
|
|
||||||
|
// Test querySelectorAll
|
||||||
|
const allButtons = wrapper.querySelectorAll('button');
|
||||||
|
console.log(`DemoWrapper: Found ${allButtons.length} buttons`);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<test-withwrapper></test-withwrapper>
|
||||||
|
<div style="margin-top: 10px; padding: 10px; background: #e0e0e0;">
|
||||||
|
This div is also inside the wrapper
|
||||||
|
</div>
|
||||||
|
</dees-demowrapper>
|
||||||
|
`;
|
||||||
|
|
||||||
|
@property({ type: String })
|
||||||
|
public dynamicValue: string = 'Initial value';
|
||||||
|
|
||||||
|
@property({ type: Number })
|
||||||
|
public counter: number = 0;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public isActive: boolean = false;
|
||||||
|
|
||||||
|
public static styles = [
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
padding: 20px;
|
||||||
|
background: #e8f5e9;
|
||||||
|
border: 2px solid #4caf50;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
.wrapper-info {
|
||||||
|
background: #c8e6c9;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.inner-content {
|
||||||
|
background: white;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
background: #4caf50;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
button:hover {
|
||||||
|
background: #45a049;
|
||||||
|
}
|
||||||
|
.status {
|
||||||
|
margin-top: 10px;
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
];
|
||||||
|
|
||||||
|
public render() {
|
||||||
|
return html`
|
||||||
|
<div class="wrapper-info">
|
||||||
|
This element is wrapped with dees-demowrapper in its demo
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inner-content">
|
||||||
|
<h3>Dynamic Value: ${this.dynamicValue}</h3>
|
||||||
|
<p>Counter: ${this.counter}</p>
|
||||||
|
<p>Active: ${this.isActive ? 'Yes' : 'No'}</p>
|
||||||
|
|
||||||
|
<button @click=${() => this.counter++}>Increment</button>
|
||||||
|
<button @click=${() => this.isActive = !this.isActive}>Toggle Active</button>
|
||||||
|
<button @click=${() => this.dynamicValue = 'Clicked!'}>Change Value</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status">
|
||||||
|
Properties panel should detect this element inside the wrapper
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
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>
|
||||||
|
`;
|
@ -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.1.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.'
|
||||||
}
|
}
|
||||||
|
@ -38,6 +38,10 @@ 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>;
|
||||||
|
|
||||||
@ -95,6 +99,13 @@ 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();
|
||||||
|
}
|
||||||
|
}}
|
||||||
></wcc-properties>
|
></wcc-properties>
|
||||||
<wcc-frame id="wccFrame" viewport=${this.selectedViewport}>
|
<wcc-frame id="wccFrame" viewport=${this.selectedViewport}>
|
||||||
</wcc-frame>
|
</wcc-frame>
|
||||||
@ -113,6 +124,12 @@ export class WccDashboard extends DeesElement {
|
|||||||
|
|
||||||
public async firstUpdated() {
|
public async firstUpdated() {
|
||||||
this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
|
||||||
|
|
||||||
|
// 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 +142,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 +172,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')) {
|
||||||
@ -173,8 +208,96 @@ export class WccDashboard extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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,9 @@ export class WccFrame extends DeesElement {
|
|||||||
@property()
|
@property()
|
||||||
public viewport: string;
|
public viewport: string;
|
||||||
|
|
||||||
|
@property({ type: Boolean })
|
||||||
|
public advancedEditorOpen: boolean = false;
|
||||||
|
|
||||||
public static styles = [
|
public static styles = [
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
@ -22,7 +25,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,6 +43,8 @@ export class WccFrame extends DeesElement {
|
|||||||
return html`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
:host {
|
:host {
|
||||||
|
bottom: ${this.advancedEditorOpen ? '400px' : '100px'};
|
||||||
|
transition: bottom 0.3s ease;
|
||||||
${(() => {
|
${(() => {
|
||||||
switch (this.viewport) {
|
switch (this.viewport) {
|
||||||
case 'desktop':
|
case 'desktop':
|
||||||
|
@ -34,31 +34,65 @@ export class WccProperties extends DeesElement {
|
|||||||
@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);
|
||||||
}
|
}
|
||||||
.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,175 +111,571 @@ 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">Docs</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}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async findElementRecursively(container: Element, elementClass: any, maxDepth: number = 5): Promise<HTMLElement | null> {
|
private async findElementRecursively(container: Element, elementClass: any, maxDepth: number = 5): Promise<HTMLElement | null> {
|
||||||
if (maxDepth <= 0) return null;
|
if (maxDepth <= 0) return null;
|
||||||
|
|
||||||
// Check direct children
|
try {
|
||||||
for (const child of Array.from(container.children)) {
|
// Check direct children
|
||||||
if (child instanceof elementClass) {
|
for (const child of Array.from(container.children)) {
|
||||||
return child as HTMLElement;
|
if (child instanceof elementClass) {
|
||||||
}
|
return child as HTMLElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check shadow roots of children
|
|
||||||
for (const child of Array.from(container.children)) {
|
|
||||||
if (child.shadowRoot) {
|
|
||||||
const found = await this.findElementRecursively(child.shadowRoot as any, elementClass, maxDepth - 1);
|
|
||||||
if (found) return found;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also check nested children
|
// Search in all children recursively
|
||||||
const found = await this.findElementRecursively(child, elementClass, maxDepth - 1);
|
for (const child of Array.from(container.children)) {
|
||||||
if (found) return found;
|
// First, always check the light DOM children
|
||||||
|
const found = await this.findElementRecursively(child, elementClass, maxDepth - 1);
|
||||||
|
if (found) return found;
|
||||||
|
|
||||||
|
// Also check shadow root if it exists
|
||||||
|
if (child.shadowRoot) {
|
||||||
|
const shadowFound = await this.findElementRecursively(child.shadowRoot as any, elementClass, maxDepth - 1);
|
||||||
|
if (shadowFound) return shadowFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in findElementRecursively:', error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -254,6 +684,9 @@ export class WccProperties extends DeesElement {
|
|||||||
public async createProperties() {
|
public async createProperties() {
|
||||||
console.log('creating properties for:');
|
console.log('creating properties for:');
|
||||||
console.log(this.selectedItem);
|
console.log(this.selectedItem);
|
||||||
|
|
||||||
|
// Clear any previous warnings
|
||||||
|
this.warning = null;
|
||||||
const isEnumeration = (propertyArg): boolean => {
|
const isEnumeration = (propertyArg): boolean => {
|
||||||
const keys = Object.keys(propertyArg.type);
|
const keys = Object.keys(propertyArg.type);
|
||||||
const values = [];
|
const values = [];
|
||||||
@ -301,8 +734,8 @@ export class WccProperties extends DeesElement {
|
|||||||
console.log(anonItem.elementProperties);
|
console.log(anonItem.elementProperties);
|
||||||
const wccFrame = await this.dashboardRef.wccFrame;
|
const wccFrame = await this.dashboardRef.wccFrame;
|
||||||
|
|
||||||
// Wait for render to complete
|
// Wait for render to complete and any demo wrappers to run
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
|
|
||||||
// Try to find the element with recursive search
|
// Try to find the element with recursive search
|
||||||
const viewport = await wccFrame.getViewportElement();
|
const viewport = await wccFrame.getViewportElement();
|
||||||
@ -315,15 +748,20 @@ export class WccProperties extends DeesElement {
|
|||||||
let retries = 0;
|
let retries = 0;
|
||||||
while (!firstFoundInstantiatedElement && retries < 5) {
|
while (!firstFoundInstantiatedElement && retries < 5) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 200));
|
await new Promise(resolve => setTimeout(resolve, 200));
|
||||||
firstFoundInstantiatedElement = await this.findElementRecursively(
|
try {
|
||||||
viewport,
|
firstFoundInstantiatedElement = await this.findElementRecursively(
|
||||||
this.selectedItem as any
|
viewport,
|
||||||
);
|
this.selectedItem as any
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during element search retry:', error);
|
||||||
|
}
|
||||||
retries++;
|
retries++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!firstFoundInstantiatedElement) {
|
if (!firstFoundInstantiatedElement) {
|
||||||
this.warning = `no first instantiated element found for >>${anonItem.name}<< after ${retries} retries`;
|
this.warning = `no first instantiated element found for >>${anonItem.name}<< after ${retries} retries`;
|
||||||
|
this.propertyContent = [];
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const classProperties: Map<string, any> = anonItem.elementProperties;
|
const classProperties: Map<string, any> = anonItem.elementProperties;
|
||||||
@ -337,12 +775,13 @@ export class WccProperties extends DeesElement {
|
|||||||
if (key === 'goBright' || key === 'domtools') {
|
if (key === 'goBright' || key === 'domtools') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const property = classProperties.get(key);
|
try {
|
||||||
const propertyTypeString = await determinePropertyType(property);
|
const property = classProperties.get(key);
|
||||||
propertyArray.push(
|
const propertyTypeString = await determinePropertyType(property);
|
||||||
|
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':
|
||||||
@ -356,7 +795,7 @@ export class WccProperties extends DeesElement {
|
|||||||
case 'String':
|
case 'String':
|
||||||
return html`<input
|
return html`<input
|
||||||
type="text"
|
type="text"
|
||||||
value=${firstFoundInstantiatedElement[key]}
|
.value=${firstFoundInstantiatedElement[key] || ''}
|
||||||
@input="${(eventArg: any) => {
|
@input="${(eventArg: any) => {
|
||||||
firstFoundInstantiatedElement[key] = eventArg.target.value;
|
firstFoundInstantiatedElement[key] = eventArg.target.value;
|
||||||
}}"
|
}}"
|
||||||
@ -364,14 +803,15 @@ export class WccProperties extends DeesElement {
|
|||||||
case 'Number':
|
case 'Number':
|
||||||
return html`<input
|
return html`<input
|
||||||
type="number"
|
type="number"
|
||||||
value=${firstFoundInstantiatedElement[key]}
|
.value=${firstFoundInstantiatedElement[key] ?? ''}
|
||||||
@input="${(eventArg: any) => {
|
@input="${(eventArg: any) => {
|
||||||
firstFoundInstantiatedElement[key] = eventArg.target.value;
|
firstFoundInstantiatedElement[key] = parseFloat(eventArg.target.value) || 0;
|
||||||
}}"
|
}}"
|
||||||
/>`;
|
/>`;
|
||||||
case 'Enum':
|
case 'Enum':
|
||||||
const enumValues: any[] = getEnumValues(property);
|
const enumValues: any[] = getEnumValues(property);
|
||||||
return html`<select
|
return html`<select
|
||||||
|
.value=${firstFoundInstantiatedElement[key] || ''}
|
||||||
@change="${(eventArg: any) => {
|
@change="${(eventArg: any) => {
|
||||||
firstFoundInstantiatedElement[key] = eventArg.target.value;
|
firstFoundInstantiatedElement[key] = eventArg.target.value;
|
||||||
}}"
|
}}"
|
||||||
@ -379,19 +819,33 @@ export class WccProperties extends DeesElement {
|
|||||||
${enumValues.map((valueArg) => {
|
${enumValues.map((valueArg) => {
|
||||||
return html`
|
return html`
|
||||||
<option
|
<option
|
||||||
?selected=${valueArg === firstFoundInstantiatedElement[key] ? true : false}
|
value="${valueArg}"
|
||||||
name="${valueArg}"
|
|
||||||
>
|
>
|
||||||
${valueArg}
|
${valueArg}
|
||||||
</option>
|
</option>
|
||||||
`;
|
`;
|
||||||
})}
|
})}
|
||||||
</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>
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error processing property ${key}:`, error);
|
||||||
|
// Continue with next property even if this one fails
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.propertyContent = propertyArray;
|
this.propertyContent = propertyArray;
|
||||||
} else {
|
} else {
|
||||||
@ -413,7 +867,14 @@ export class WccProperties extends DeesElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async scheduleUpdate() {
|
public async scheduleUpdate() {
|
||||||
await this.createProperties();
|
try {
|
||||||
|
await this.createProperties();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating properties:', error);
|
||||||
|
// Clear property content on error to show clean state
|
||||||
|
this.propertyContent = [];
|
||||||
|
}
|
||||||
|
// Always call super.scheduleUpdate to ensure component updates
|
||||||
super.scheduleUpdate();
|
super.scheduleUpdate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -427,4 +888,93 @@ 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
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,31 +17,68 @@ export class WccSidebar extends DeesElement {
|
|||||||
|
|
||||||
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 {
|
||||||
|
/* CSS Variables - Always dark theme to match wcc-properties */
|
||||||
|
--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;
|
||||||
|
|
||||||
display: block;
|
display: block;
|
||||||
border-right: 1px solid #999;
|
border-right: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
font-family: 'Roboto', 'Inter', sans-serif;
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
font-size: 12px;
|
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 +86,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>
|
||||||
|
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