Compare commits

..

22 Commits

Author SHA1 Message Date
923bedc4fc 1.1.0
Some checks failed
Default (tags) / security (push) Failing after 25s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-27 20:52:27 +00:00
e8b771bde4 feat(wcctools): Enhance component tools with an advanced property editor, improved element detection and modernized UI styling for a more responsive dashboard experience. 2025-06-27 20:52:27 +00:00
7a248993bc update 2025-06-27 20:51:31 +00:00
03f215e0f1 update 2025-06-27 20:50:32 +00:00
216cb0288d update 2025-06-27 20:41:24 +00:00
65acda3de1 update 2025-06-27 20:40:06 +00:00
88ff74bb86 update styling 2025-06-27 20:28:47 +00:00
98a5b1b5a3 update 2025-06-27 20:26:42 +00:00
bbf738d4e2 update 2025-06-27 20:22:28 +00:00
4f8ca7061a update 2025-06-27 20:03:54 +00:00
d26d99dbff 1.0.101
Some checks failed
Default (tags) / security (push) Failing after 22s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-26 23:11:58 +00:00
c1d8e347de fix(wcc-dashboard): Improve scroll listener management and add new test pages 2025-06-26 23:11:58 +00:00
b6c41caf44 1.0.100
Some checks failed
Default (tags) / security (push) Failing after 24s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-26 22:52:54 +00:00
b858b3b9e2 fix(wcc-dashboard): Prevent duplicate application of scroll positions in dashboard to avoid interfering with user scrolling 2025-06-26 22:52:54 +00:00
4ed37086ae 1.0.99
Some checks failed
Default (tags) / security (push) Failing after 20s
Default (tags) / test (push) Failing after 20s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-26 20:18:20 +00:00
b4c0de47b9 fix(dashboard): Fix scroll state preservation in dashboard by tracking frame and sidebar scroll positions and updating the URL accordingly. 2025-06-26 20:18:20 +00:00
e11f0df950 1.0.98
Some checks failed
Default (tags) / security (push) Failing after 40s
Default (tags) / test (push) Failing after 35s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-16 17:57:37 +00:00
c64b106569 fix(properties-panel): improve element detection timing and value handling in properties panel 2025-06-16 17:56:14 +00:00
3d1948b93e 1.0.97
Some checks failed
Default (tags) / security (push) Failing after 41s
Default (tags) / test (push) Failing after 37s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-16 16:03:28 +00:00
21b7158a35 fix(properties-panel): enhance element detection and error handling for nested structures 2025-06-16 15:56:12 +00:00
992af2668e 1.0.96
Some checks failed
Default (tags) / security (push) Failing after 42s
Default (tags) / test (push) Failing after 39s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-06-16 15:05:47 +00:00
0cd84b28b4 fix(package): correct path for demotools export in package.json 2025-06-16 15:05:43 +00:00
20 changed files with 1883 additions and 187 deletions

1
.gitignore vendored
View File

@ -3,7 +3,6 @@
# artifacts # artifacts
coverage/ coverage/
public/ public/
pages/
# installs # installs
node_modules/ node_modules/

57
changelog.md Normal file
View 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.891.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.)

View File

@ -1,11 +1,11 @@
{ {
"name": "@design.estate/dees-wcctools", "name": "@design.estate/dees-wcctools",
"version": "1.0.95", "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": {
".": "./dist_ts_web/index.js", ".": "./dist_ts_web/index.js",
"./demotools": "./dist_ts_demotools" "./demotools": "./dist_ts_demotools/index.js"
}, },
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@ -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

View File

@ -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

View File

@ -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';

View 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>
`;
}
}

View 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>
`;
}
}

View 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>
`;
}
}

View 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>
`;
}
}

View 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
View File

@ -0,0 +1,2 @@
export * from './page1.js';
export * from './pageLongScroll.js';

3
test/pages/page1.ts Normal file
View File

@ -0,0 +1,3 @@
import { html } from '@design.estate/dees-element';
export const page1 = () => html` <test-demoelement></test-demoelement> `;

View 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>
`;

View File

@ -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.'
} }

View File

@ -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;
} }
} }

View File

@ -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':

View File

@ -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
})
);
}
} }

View File

@ -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
View File

@ -0,0 +1 @@
export const page1 = null;