Compare commits

...

15 Commits

Author SHA1 Message Date
4c23739d9a 1.2.0
Some checks failed
Default (tags) / security (push) Failing after 23s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-09-19 13:24:46 +00:00
dd048d42a8 feat(wcc-properties): Add advanced property editors, recursive element detection, demo wrapper, UI refresh and test fixtures 2025-09-19 13:24:46 +00:00
ca28dbd9db feat: add async demo support and enhance template resolution
- Introduced async demo functionality in the README, allowing for asynchronous data preparation before rendering components.
- Updated WccDashboard, WccProperties, and WccSidebar to support Promise-based template factories.
- Implemented resolveTemplateFactory to handle both synchronous and asynchronous template results.
- Added tests for resolveTemplateFactory to ensure correct behavior for both sync and async templates.
- Updated pnpm workspace configuration.
2025-09-19 13:02:16 +00:00
7148b12066 1.1.1
Some checks failed
Default (tags) / security (push) Failing after 25s
Default (tags) / test (push) Failing after 15s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-07-07 08:48:35 +00:00
309d708830 add fullscreen mode 2025-07-07 08:48:09 +00:00
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
15 changed files with 4848 additions and 1949 deletions

View File

@@ -1,5 +1,24 @@
# Changelog
## 2025-09-19 - 1.2.0 - feat(wcc-properties)
Add advanced property editors, recursive element detection, demo wrapper, UI refresh and test fixtures
- Advanced JSON property editor: multiple side-by-side editors with save/cancel, syntax validation and inline error display; editors affect frame layout (frame bottom increases when editors open).
- Improved properties panel element detection: recursive search through nested children and shadow DOM, initial delay and retry mechanism to handle async Lit rendering.
- Add dees-demowrapper component in ts_demotools to run post-render callbacks and support async demo setup and DOM access for demos.
- UI refresh with shadcn-like styles: CSS variables for theming, redesigned properties panel and sidebar, improved form controls, theme and viewport selectors.
- Viewport and frame improvements: responsive padding based on viewport type, theme-aware background rendering, and scroll position tracking with URL/state restoration for frame and sidebar.
- Add test fixtures and demo elements/pages under test/ to exercise properties, complex types, nested elements and scroll restoration; include node test for resolveTemplateFactory.
- Expose setupWccTools entry point and plugin wiring (wcctools.plugins exports for dees-domtools and smartdelay) for easier integration.
## 2025-06-27 - 1.1.0 - feat(wcctools)
Enhance component tools with an advanced property editor, improved element detection and modernized UI styling for a more responsive dashboard experience.
- Updated documentation and in-code hints with new shadcn-like design patterns for the dashboard UI.
- Introduced an advanced complex properties editor supporting JSON validation and multi-editor handling.
- Refined recursive element search in the properties panel to improve asynchronous rendering detection.
- Expanded test coverage with scenarios for edge cases, nested elements and wrapper components.
## 2025-06-26 - 1.0.101 - fix(wcc-dashboard)
Improve scroll listener management and add new test pages

View File

@@ -1,6 +1,6 @@
{
"name": "@design.estate/dees-wcctools",
"version": "1.0.101",
"version": "1.2.0",
"private": false,
"description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.",
"exports": {
@@ -17,18 +17,20 @@
"author": "Lossless GmbH",
"license": "UNLICENSED",
"dependencies": {
"@design.estate/dees-domtools": "^2.0.57",
"@design.estate/dees-element": "^2.0.34",
"@design.estate/dees-domtools": "^2.3.3",
"@design.estate/dees-element": "^2.1.2",
"@push.rocks/smartdelay": "^3.0.5",
"lit": "^3.1.3"
"lit": "^3.3.1"
},
"devDependencies": {
"@api.global/typedserver": "^3.0.29",
"@git.zone/tsbuild": "^2.1.72",
"@git.zone/tsbundle": "^2.0.15",
"@api.global/typedserver": "^3.0.79",
"@git.zone/tsbuild": "^2.6.8",
"@git.zone/tsbundle": "^2.5.1",
"@git.zone/tsrun": "^1.2.44",
"@git.zone/tswatch": "^2.0.23",
"@push.rocks/projectinfo": "^5.0.2"
"@git.zone/tstest": "^2.3.8",
"@git.zone/tswatch": "^2.2.1",
"@push.rocks/projectinfo": "^5.0.2",
"@types/node": "^22.18.6"
},
"files": [
"ts/**/*",

5624
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

4
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,4 @@
onlyBuiltDependencies:
- esbuild
- mongodb-memory-server
- puppeteer

View File

@@ -1,5 +1,107 @@
# 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)
### Problem

View File

@@ -211,6 +211,19 @@ export class MyComponent extends DeesElement {
}
```
### ⏳ Async Demos
If your catalogue needs additional setup before rendering, return a `Promise` from the `demo` function. The dashboard waits for the result before inserting it into the viewport:
```typescript
public static demo = async () => {
await Promise.resolve(); // e.g. fetch data, load fixtures, or await wrappers
return html`<my-component .value=${'Loaded asynchronously'}></my-component>`;
};
```
The same pattern works for page factories you pass into `setupWccTools`, enabling asynchronous data preparation across the entire demo surface.
### 🎭 Container Queries Support
Components can respond to their container size:

View File

@@ -98,3 +98,10 @@ Properties panel was overwriting values set by demo functions
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
# Async Demo Support (IN PROGRESS)
## Tasks
- [ ] Allow dashboard-selected items to return Promise-based TemplateResults
- [ ] Await async demos/pages before rendering them into the viewport
- [ ] Add regression test covering async demo usage
- [ ] Document async demo pattern in README and verify with pnpm scripts

View File

@@ -0,0 +1,22 @@
import { tap, expect } from '@git.zone/tstest/tapbundle';
import { resolveTemplateFactory } from '../ts_web/elements/wcctools.helpers.js';
import { html } from 'lit';
const waitFor = (durationMs: number) => new Promise(resolve => setTimeout(resolve, durationMs));
tap.test('resolveTemplateFactory returns sync TemplateResult', async () => {
const template = html`<p>sync demo</p>`;
const resolvedTemplate = await resolveTemplateFactory(() => template);
expect(resolvedTemplate).toEqual(template);
});
tap.test('resolveTemplateFactory awaits async TemplateResult', async () => {
const template = html`<p>async demo</p>`;
const resolvedTemplate = await resolveTemplateFactory(async () => {
await waitFor(5);
return template;
});
expect(resolvedTemplate).toEqual(template);
});
export default tap.start();

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@design.estate/dees-wcctools',
version: '1.0.101',
version: '1.2.0',
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
}

View File

@@ -1,4 +1,6 @@
import { DeesElement, property, html, customElement, type TemplateResult, queryAsync, render, domtools } from '@design.estate/dees-element';
import { resolveTemplateFactory } from './wcctools.helpers.js';
import type { TTemplateFactory } from './wcctools.helpers.js';
import * as plugins from '../wcctools.plugins.js';
@@ -21,7 +23,7 @@ export class WccDashboard extends DeesElement {
public selectedItemName: string;
@property()
public selectedItem: (() => TemplateResult) | DeesElement;
public selectedItem: TTemplateFactory | DeesElement;
@property()
public selectedViewport: plugins.deesDomtools.breakpoints.TViewport = 'desktop';
@@ -30,7 +32,10 @@ export class WccDashboard extends DeesElement {
public selectedTheme: TTheme = 'dark';
@property()
public pages: { [key: string]: () => TemplateResult } = {};
public isFullscreen: boolean = false;
@property()
public pages: Record<string, TTemplateFactory> = {};
@property()
public elements: { [key: string]: DeesElement } = {};
@@ -47,7 +52,7 @@ export class WccDashboard extends DeesElement {
constructor(
elementsArg?: { [key: string]: DeesElement },
pagesArg?: { [key: string]: () => TemplateResult }
pagesArg?: Record<string, TTemplateFactory>
) {
super();
if (elementsArg) {
@@ -76,6 +81,7 @@ export class WccDashboard extends DeesElement {
<wcc-sidebar
.dashboardRef=${this}
.selectedItem=${this.selectedItem}
.isFullscreen=${this.isFullscreen}
@selectedType=${(eventArg) => {
this.selectedType = eventArg.detail;
}}
@@ -92,6 +98,7 @@ export class WccDashboard extends DeesElement {
.selectedItem=${this.selectedItem}
.selectedViewport=${this.selectedViewport}
.selectedTheme=${this.selectedTheme}
.isFullscreen=${this.isFullscreen}
@selectedViewport=${(eventArg) => {
this.selectedViewport = eventArg.detail;
this.scheduleUpdate();
@@ -99,8 +106,18 @@ export class WccDashboard extends DeesElement {
@selectedTheme=${(eventArg) => {
this.selectedTheme = eventArg.detail;
}}
@editorStateChanged=${async (eventArg) => {
const frame = await this.wccFrame;
if (frame) {
frame.advancedEditorOpen = eventArg.detail.isOpen;
frame.requestUpdate();
}
}}
@toggleFullscreen=${() => {
this.toggleFullscreen();
}}
></wcc-properties>
<wcc-frame id="wccFrame" viewport=${this.selectedViewport}>
<wcc-frame id="wccFrame" viewport=${this.selectedViewport} .isFullscreen=${this.isFullscreen}>
</wcc-frame>
`;
}
@@ -115,9 +132,20 @@ export class WccDashboard extends DeesElement {
}
}
public toggleFullscreen() {
this.isFullscreen = !this.isFullscreen;
}
public async firstUpdated() {
this.domtools = await plugins.deesDomtools.DomTools.setupDomTools();
// Add ESC key handler for fullscreen mode
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape' && this.isFullscreen) {
this.isFullscreen = false;
}
});
// Set up scroll listeners after DOM is ready
setTimeout(() => {
this.setupScrollListeners();
@@ -175,7 +203,9 @@ export class WccDashboard extends DeesElement {
if (typeof this.selectedItem === 'function') {
console.log('slotting page.');
const viewport = await wccFrame.getViewportElement();
render(this.selectedItem(), viewport);
const pageFactory = this.selectedItem as TTemplateFactory;
const pageTemplate = await resolveTemplateFactory(pageFactory);
render(pageTemplate, viewport);
console.log('rendered page.');
} else {
console.error('The selected item looks strange:');
@@ -196,7 +226,8 @@ export class WccDashboard extends DeesElement {
}
this.setWarning(null);
const viewport = await wccFrame.getViewportElement();
render(anonItem.demo(), viewport);;
const demoTemplate = await resolveTemplateFactory(() => anonItem.demo());
render(demoTemplate, viewport);
}
}

View File

@@ -13,6 +13,12 @@ export class WccFrame extends DeesElement {
@property()
public viewport: string;
@property({ type: Boolean })
public advancedEditorOpen: boolean = false;
@property({ type: Boolean })
public isFullscreen: boolean = false;
public static styles = [
css`
:host {
@@ -22,7 +28,6 @@ export class WccFrame extends DeesElement {
left: 200px;
right: 0px;
top: 0px;
bottom: 100px;
overflow-y: auto;
overflow-x: auto;
overscroll-behavior: contain;
@@ -41,7 +46,19 @@ export class WccFrame extends DeesElement {
return html`
<style>
:host {
${(() => {
${this.isFullscreen ? `
border: none !important;
left: 0px !important;
right: 0px !important;
top: 0px !important;
bottom: 0px !important;
` : `
bottom: ${this.advancedEditorOpen ? '400px' : '100px'};
border: 10px solid #ffaeaf;
left: 200px;
`}
transition: all 0.3s ease;
${this.isFullscreen ? 'padding: 0px;' : (() => {
switch (this.viewport) {
case 'desktop':
return `
@@ -70,7 +87,7 @@ export class WccFrame extends DeesElement {
}
.viewport {
${this.viewport !== 'desktop'
${!this.isFullscreen && this.viewport !== 'desktop'
? html` border-right: 1px dotted #444; border-left: 1px dotted #444; `
: html``
}

View File

@@ -1,5 +1,6 @@
import { DeesElement, property, html, customElement, type TemplateResult, state } from '@design.estate/dees-element';
import { WccDashboard } from './wcc-dashboard.js';
import type { TTemplateFactory } from './wcctools.helpers.js';
export type TPropertyType = 'String' | 'Number' | 'Boolean' | 'Object' | 'Enum' | 'Array';
@@ -20,7 +21,7 @@ export class WccProperties extends DeesElement {
public dashboardRef: WccDashboard;
@property()
public selectedItem: (() => TemplateResult) | DeesElement;
public selectedItem: TTemplateFactory | DeesElement;
@property()
public selectedViewport: TEnvironment = 'native';
@@ -31,34 +32,72 @@ export class WccProperties extends DeesElement {
@property()
public warning: string = null;
@property()
public isFullscreen: boolean = false;
@state()
propertyContent: TemplateResult[] = [];
@state()
editingProperties: Array<{
id: string;
name: string;
value: any;
element: HTMLElement;
editorValue: string;
editorError: string;
}> = [];
public editorHeight: number = 300;
public render(): TemplateResult {
return html`
<style>
: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;
position: absolute;
left: 200px;
height: 100px;
height: ${this.editingProperties.length > 0 ? 100 + this.editorHeight : 100}px;
bottom: 0px;
right: 0px;
overflow: hidden;
background: #111;
color: #fff;
background: var(--background);
color: var(--foreground);
display: ${this.isFullscreen ? 'none' : 'block'};
}
.grid {
display: grid;
grid-template-columns: auto 150px 300px 70px;
grid-template-columns: 1fr 150px 300px 70px;
height: 100%;
}
.properties {
border-right: 1px solid #999;
height: 100px;
background: transparent;
overflow-y: auto;
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 {
@@ -77,86 +116,476 @@ export class WccProperties extends DeesElement {
}
.properties .property {
padding: 5px;
background: #444;
border: 1px solid #000;
padding: 0.4rem;
background: transparent;
border-right: 1px solid var(--border);
border-bottom: 1px solid var(--border);
transition: all 0.15s ease;
}
.properties input,
.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[type="text"],
.properties input[type="number"],
.properties select {
display: block;
width: 100%;
background: #333;
border: none;
color: #fff;
padding: 0.25rem 0.4rem;
background: var(--input);
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,
.themeSelector {
user-select: none;
border-right: 1px solid #999;
background: transparent;
display: flex;
flex-direction: column;
border-right: 1px solid var(--border);
}
.selectorButtons2 {
display: grid;
grid-template-columns: 50% 50%;
grid-template-columns: 1fr 1fr;
flex: 1;
}
.selectorButtons4 {
display: grid;
grid-template-columns: 25% 25% 25% 25%;
grid-template-columns: repeat(4, 1fr);
flex: 1;
}
.button {
padding: 10px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0.5rem 0.25rem;
text-align: center;
border: 1px solid #000;
transition: all 0.2s;
background: transparent;
border: 1px solid var(--border);
transition: all 0.15s ease;
cursor: pointer;
font-size: 0.65rem;
gap: 0.2rem;
color: #999;
}
.button:hover {
color: #333;
background: #fff;
background: rgba(59, 130, 246, 0.05);
color: #bbb;
}
.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 {
color: #ffffff;
background: #455a64;
background: rgba(59, 130, 246, 0.2);
}
.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 {
padding: 5px;
font-weight: bold;
background: #444;
border: 1px solid #000;
padding: 0.3rem 0.5rem;
font-weight: 500;
font-size: 0.65rem;
background: rgba(59, 130, 246, 0.03);
border-bottom: 1px solid var(--border);
color: #888;
text-transform: uppercase;
letter-spacing: 0.08em;
}
.docs {
text-align: center;
line-height: 100px;
display: flex;
align-items: center;
justify-content: center;
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 {
color: #333;
background: #fff;
background: rgba(59, 130, 246, 0.05);
color: #999;
}
.warning {
position: absolute;
background: #222;
color: #CCC;
top: 0px;
bottom: 0px;
left: 0px;
right: 520px;
text-align: center;
padding: 35px;
font-size: 25px;
background: rgba(20, 20, 20, 0.8);
color: #888;
top: 0.5rem;
bottom: 0.5rem;
left: 0.5rem;
right: calc(520px + 0.5rem);
display: flex;
align-items: center;
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>
${this.editingProperties.length > 0 ? html`
<div class="advanced-editor-container">
<div class="editor-header-bar">
<div class="editor-header-title">Property Editors</div>
<button class="editor-close-all" @click=${this.closeAllEditors}>
Close All
</button>
</div>
<div class="editors-container">
${this.editingProperties.length === 0 ? html`
<div style="
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: #666;
font-size: 0.875rem;
text-align: center;
padding: 2rem;
">
<div>
<div style="margin-bottom: 0.5rem; font-size: 1.5rem; opacity: 0.5;">{ }</div>
<div>No properties being edited</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>
` : null}
<div class="main-content">
<div class="grid">
<div class="properties">
<div class="panelheading">Properties</div>
${this.propertyContent}
</div>
<div class="themeSelector">
@@ -168,7 +597,7 @@ export class WccProperties extends DeesElement {
this.selectTheme('dark');
}}
>
Dark<br /><i class="material-symbols-outlined">brightness_3</i>
Dark<i class="material-symbols-outlined">brightness_3</i>
</div>
<div
class="button ${this.selectedTheme === 'bright' ? 'selected' : null}"
@@ -176,7 +605,7 @@ export class WccProperties extends DeesElement {
this.selectTheme('bright');
}}
>
Bright<br /><i class="material-symbols-outlined">flare</i>
Bright<i class="material-symbols-outlined">flare</i>
</div>
</div>
</div>
@@ -189,7 +618,7 @@ export class WccProperties extends DeesElement {
this.selectViewport('phone');
}}
>
Phone<br /><i class="material-symbols-outlined">smartphone</i>
Phone<i class="material-symbols-outlined">smartphone</i>
</div>
<div
class="button ${this.selectedViewport === 'phablet' ? 'selected' : null}"
@@ -197,7 +626,7 @@ export class WccProperties extends DeesElement {
this.selectViewport('phablet');
}}
>
Phablet<br /><i class="material-symbols-outlined">smartphone</i>
Phablet<i class="material-symbols-outlined">smartphone</i>
</div>
<div
class="button ${this.selectedViewport === 'tablet' ? 'selected' : null}"
@@ -205,7 +634,7 @@ export class WccProperties extends DeesElement {
this.selectViewport('tablet');
}}
>
Tablet<br /><i class="material-symbols-outlined">tablet</i>
Tablet<i class="material-symbols-outlined">tablet</i>
</div>
<div
class="button ${this.selectedViewport === 'desktop' ||
@@ -216,13 +645,18 @@ export class WccProperties extends DeesElement {
this.selectViewport('native');
}}
>
Desktop<br /><i class="material-symbols-outlined">desktop_windows</i>
Desktop<i class="material-symbols-outlined">desktop_windows</i>
</div>
</div>
</div>
<div class="docs">Docs</div>
<div class="docs" @click=${() => this.toggleFullscreen()}>
<i class="material-symbols-outlined" style="font-size: 20px;">
${this.isFullscreen ? 'fullscreen_exit' : 'fullscreen'}
</i>
</div>
</div>
${this.warning ? html`<div class="warning">${this.warning}</div>` : null}
</div>
`;
}
@@ -356,7 +790,7 @@ export class WccProperties extends DeesElement {
propertyArray.push(
html`
<div class="property">
${key} / ${propertyTypeString}<br />
<div class="property-label">${key} (${propertyTypeString})</div>
${(() => {
switch (propertyTypeString) {
case 'Boolean':
@@ -401,6 +835,17 @@ export class WccProperties extends DeesElement {
`;
})}
</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>
@@ -452,4 +897,101 @@ export class WccProperties extends DeesElement {
);
this.dashboardRef.buildUrl();
}
private openAdvancedEditor(propertyName: string, value: any, element: HTMLElement) {
// Check if this property is already being edited
const existingEditor = this.editingProperties.find(p => p.name === propertyName && p.element === element);
if (existingEditor) {
return; // Property is already open for editing
}
const newEditor = {
id: `${propertyName}-${Date.now()}`,
name: propertyName,
value: value,
element: element,
editorValue: JSON.stringify(value, null, 2),
editorError: ''
};
this.editingProperties = [...this.editingProperties, newEditor];
// Notify parent to resize frame if this is the first editor
if (this.editingProperties.length === 1) {
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: true },
bubbles: true
})
);
}
}
private handleEditorSave(editorId: string) {
const editor = this.editingProperties.find(p => p.id === editorId);
if (!editor) return;
try {
const parsedValue = JSON.parse(editor.editorValue);
editor.element[editor.name] = parsedValue;
// Remove this editor from the list
this.editingProperties = this.editingProperties.filter(p => p.id !== editorId);
// If no more editors, notify parent to resize frame
if (this.editingProperties.length === 0) {
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: false },
bubbles: true
})
);
}
// Refresh properties display
this.createProperties();
} catch (error) {
// Update error for this specific editor
const editorIndex = this.editingProperties.findIndex(p => p.id === editorId);
if (editorIndex !== -1) {
this.editingProperties[editorIndex].editorError = `Invalid JSON: ${error.message}`;
this.requestUpdate();
}
}
}
private handleEditorCancel(editorId: string) {
// Remove this editor from the list
this.editingProperties = this.editingProperties.filter(p => p.id !== editorId);
// If no more editors, notify parent to resize frame
if (this.editingProperties.length === 0) {
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: false },
bubbles: true
})
);
}
}
private closeAllEditors() {
this.editingProperties = [];
// Notify parent to resize frame back
this.dispatchEvent(
new CustomEvent('editorStateChanged', {
detail: { isOpen: false },
bubbles: true
})
);
}
private toggleFullscreen() {
this.dispatchEvent(
new CustomEvent('toggleFullscreen', {
bubbles: true
})
);
}
}

View File

@@ -1,13 +1,14 @@
import * as plugins from '../wcctools.plugins.js';
import { DeesElement, property, html, customElement, type TemplateResult } from '@design.estate/dees-element';
import { WccDashboard } from './wcc-dashboard.js';
import type { TTemplateFactory } from './wcctools.helpers.js';
export type TElementType = 'element' | 'page';
@customElement('wcc-sidebar')
export class WccSidebar extends DeesElement {
@property({ attribute: false })
public selectedItem: DeesElement | (() => TemplateResult);
public selectedItem: DeesElement | TTemplateFactory;
@property({ attribute: false })
public selectedType: TElementType;
@@ -15,33 +16,73 @@ export class WccSidebar extends DeesElement {
@property()
public dashboardRef: WccDashboard;
@property()
public isFullscreen: boolean = false;
public render(): TemplateResult {
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>
:host {
display: block;
border-right: 1px solid #999;
font-family: 'Roboto', 'Inter', sans-serif;
font-size: 12px;
/* 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: ${this.isFullscreen ? 'none' : 'block'};
border-right: 1px solid rgba(255, 255, 255, 0.08);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
font-size: 14px;
box-sizing: border-box;
position: absolute;
left: 0px;
width: 200px;
top: 0px;
bottom: 0px;
overflow-y: scroll;
overflow-y: auto;
overflow-x: hidden;
background: #222;
color: #fff;
padding: 5px;
background: var(--background);
color: var(--foreground);
}
.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 {
font-family: 'Material Symbols Outlined';
font-weight: normal;
font-style: normal;
font-size: 24px; /* Preferred icon size */
font-size: 16px;
display: inline-block;
line-height: 1;
text-transform: none;
@@ -49,51 +90,75 @@ export class WccSidebar extends DeesElement {
word-wrap: normal;
white-space: nowrap;
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 {
user-select: none;
position: relative;
line-height: 24px;
padding: 5px;
transition: all 0.2s;
margin: 0.125rem 0.5rem;
padding: 0.5rem 0.75rem;
transition: all 0.15s ease;
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 {
padding: 5px;
color: #333;
background: #fff;
background: rgba(59, 130, 246, 0.05);
color: #bbb;
}
.selectOption:hover .material-symbols-outlined {
opacity: 0.7;
}
.selectOption.selected {
background: #455A64;;
}
.selectOption.selected:hover {
color: #ffffff;
background: #455A64;
}
.selectOption .material-symbols-outlined {
color: #666;
display: block;
transition: all 0.2s;
background: rgba(59, 130, 246, 0.15);
color: var(--primary);
}
.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 {
display: block;
word-wrap: break-word;
word-break: break-all;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
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>
<div class="menu">
<h3>Pages</h3>
@@ -138,7 +203,7 @@ export class WccSidebar extends DeesElement {
`;
}
public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: (() => TemplateResult) | DeesElement) {
public selectItem(typeArg: TElementType, itemNameArg: string, itemArg: TTemplateFactory | DeesElement) {
console.log('selected item');
console.log(itemNameArg);
console.log(itemArg);

View File

@@ -0,0 +1,9 @@
import type { TemplateResult } from 'lit';
export type TTemplateFactory = () => TemplateResult | Promise<TemplateResult>;
export const resolveTemplateFactory = async (
factoryArg: TTemplateFactory
): Promise<TemplateResult> => {
return await Promise.resolve(factoryArg());
};

View File

@@ -1,7 +1,11 @@
import { WccDashboard } from './elements/wcc-dashboard.js';
import { LitElement, type TemplateResult } from 'lit';
import { LitElement } from 'lit';
import type { TTemplateFactory } from './elements/wcctools.helpers.js';
const setupWccTools = (elementsArg?: { [key: string]: LitElement }, pagesArg?: { [key: string]: () => TemplateResult }) => {
const setupWccTools = (
elementsArg?: { [key: string]: LitElement },
pagesArg?: Record<string, TTemplateFactory>
) => {
let hasRun = false;
const runWccToolsSetup = async () => {
if (document.readyState === 'complete' && !hasRun) {