Compare commits

..

17 Commits

Author SHA1 Message Date
a778ad6855 v3.7.1
Some checks failed
Default (tags) / security (push) Failing after 13s
Default (tags) / test (push) Failing after 15s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-04 17:07:41 +00:00
24a1f064ba fix(sidebar): increase scrolled sidebar header box-shadow intensity and size to improve visual separation 2026-01-04 17:07:41 +00:00
203a53a45d v3.7.0
Some checks failed
Default (tags) / security (push) Failing after 13s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-04 17:02:15 +00:00
349d4ba320 feat(wcc-sidebar): add header shadow and scrolled state for sidebar menu to show elevation when content is scrolled 2026-01-04 17:02:15 +00:00
399ef3d508 v3.6.2
Some checks failed
Default (tags) / security (push) Failing after 14s
Default (tags) / test (push) Failing after 15s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-04 16:59:18 +00:00
e0f176b221 v3.6.1
Some checks failed
Default (tags) / security (push) Failing after 14s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-04 16:51:52 +00:00
e625fe9ba6 fix(wcc-sidebar): sort sidebar items alphabetically and unify grouped and ungrouped items for consistent ordering 2026-01-04 16:51:52 +00:00
fe62278d74 v3.6.0
Some checks failed
Default (tags) / security (push) Failing after 14s
Default (tags) / test (push) Failing after 15s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-04 16:41:24 +00:00
3ee8afcdae feat(sidebar): restructure sidebar layout, add search clear button, and improve scrolling behavior 2026-01-04 16:41:24 +00:00
ab517b6ba8 v3.5.3
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 11s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-04 15:59:51 +00:00
2e4cbd911c fix(deps): bump dependency versions: @design.estate/dees-domtools to ^2.3.7, @design.estate/dees-element to ^2.1.5, lit to ^3.3.2; update devDependencies @api.global/typedserver to ^8.1.0 and @git.zone/tstest to ^3.1.4 2026-01-04 15:59:51 +00:00
6e14ebde03 v3.5.2
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 11s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-04 11:43:54 +00:00
28d1227d30 fix(elements): delay hiding sidebar and properties panels during native-mode transition and use transparent rgba border for frame to avoid layout jumps 2026-01-04 11:43:54 +00:00
8c60d3bea3 v3.5.1
Some checks failed
Default (tags) / security (push) Failing after 14s
Default (tags) / test (push) Failing after 15s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2026-01-04 11:31:02 +00:00
9ed614994f fix(sidebar): disable frame CSS transition while user is resizing the sidebar to prevent janky animations 2026-01-04 11:31:02 +00:00
61b79aa4dc update 2026-01-04 11:29:19 +00:00
1134cba575 update 2026-01-04 10:57:45 +00:00
10 changed files with 1227 additions and 191 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -1,5 +1,67 @@
# Changelog # Changelog
## 2026-01-04 - 3.7.1 - fix(sidebar)
increase scrolled sidebar header box-shadow intensity and size to improve visual separation
- Changed .sidebar-header.scrolled box-shadow from `0 4px 12px -2px rgba(0, 0, 0, 0.4)` to `0 8px 24px -2px rgba(0, 0, 0, 1)`
- File modified: ts_web/elements/wcc-sidebar.ts — stronger, larger, and fully opaque shadow for better contrast when scrolled
## 2026-01-04 - 3.7.0 - feat(wcc-sidebar)
add header shadow and scrolled state for sidebar menu to show elevation when content is scrolled
- Introduce isMenuScrolled state to track whether the menu has been scrolled
- Add handleMenuScroll handler and bind it to the menu scroll event
- Apply a 'scrolled' class to .sidebar-header to add box-shadow and border-bottom color with transitions
- Update template to conditionally add scrolled class and attach scroll listener
## 2026-01-04 - 3.6.2 - fix(wcc-sidebar)
use sidebar's internal .menu element for scroll management and expose scrollableContainer getter
- Add public scrollableContainer getter to wcc-sidebar that returns the .menu element for external scroll control
- Update wcc-dashboard to query wcc-sidebar as WccSidebar and attach scroll listeners to sidebar.scrollableContainer instead of the host element
- Restore sidebar scroll position by setting scrollTop on the scrollableContainer when applying saved positions
- TypeScript casting added to avoid nullable/implicit any issues when querying the sidebar element
## 2026-01-04 - 3.6.1 - fix(wcc-sidebar)
sort sidebar items alphabetically and unify grouped and ungrouped items for consistent ordering
- Unifies ungrouped elements and groups into a single render list using a RenderItem type with a sortKey.
- Sorts all top-level items alphabetically by element name (case-insensitive via toLowerCase) so sidebar order is deterministic.
- Groups are ordered by the first element's name; individual items inside a group preserve their original order.
- Replaces previous separate rendering paths with a single sorted render pass that returns TemplateResult array.
## 2026-01-04 - 3.6.0 - feat(sidebar)
restructure sidebar layout, add search clear button, and improve scrolling behavior
- Change sidebar root to a flex column layout and add a .sidebar-header to separate header content from the scrollable menu
- Move pinned section into the header and make .menu flex: 1 with min-height: 0 so the menu becomes the scrollable area
- Replace overflow-y on the root with overflow:hidden to avoid double scrolling and constrain scrolling to .menu
- Add a clear button for the search input (.search-clear) with positioning, hover styles, and a clearSearch() method to reset the query and emit searchChanged
- Adjust search input padding and make .search-container position: relative to correctly position the clear button
## 2026-01-04 - 3.5.3 - fix(deps)
bump dependency versions: @design.estate/dees-domtools to ^2.3.7, @design.estate/dees-element to ^2.1.5, lit to ^3.3.2; update devDependencies @api.global/typedserver to ^8.1.0 and @git.zone/tstest to ^3.1.4
- Updated runtime dependencies: @design.estate/dees-domtools ^2.3.7, @design.estate/dees-element ^2.1.5, lit ^3.3.2.
- Updated devDependencies: @api.global/typedserver ^8.1.0 (major bump), @git.zone/tstest ^3.1.4.
- @api.global/typedserver major bump affects development tooling only (devDependency), not runtime API.
- Current package version is 3.5.2; recommend a patch release to 3.5.3.
## 2026-01-04 - 3.5.2 - fix(elements)
delay hiding sidebar and properties panels during native-mode transition and use transparent rgba border for frame to avoid layout jumps
- Add isHidden state to wcc-sidebar and wcc-properties and switch display bindings to use isHidden instead of directly using isNative
- Introduce a 300ms delayed hide when entering native mode so UI hides after frame animation completes; show immediately when exiting native mode
- Replace hardcoded hex border values in wcc-frame with rgba and set native border to a transparent 0px to prevent abrupt visual jumps
## 2026-01-04 - 3.5.1 - fix(sidebar)
disable frame CSS transition while user is resizing the sidebar to prevent janky animations
- Added isResizing boolean property to wcc-frame to toggle transitions during resize
- Set frame.isResizing = true at resize start and false on mouseup to re-enable transitions
- Updated CSS to skip transition while isResizing is true
- Files changed: ts_web/elements/wcc-frame.ts, ts_web/elements/wcc-sidebar.ts
## 2026-01-04 - 3.5.0 - feat(wcctools) ## 2026-01-04 - 3.5.0 - feat(wcctools)
add context menu and pinning support, persist pinned state in URL, and add grouped demo test elements add context menu and pinning support, persist pinned state in URL, and add grouped demo test elements

View File

@@ -1,6 +1,6 @@
{ {
"name": "@design.estate/dees-wcctools", "name": "@design.estate/dees-wcctools",
"version": "3.5.0", "version": "3.7.1",
"private": false, "private": false,
"description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.", "description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.",
"exports": { "exports": {
@@ -17,17 +17,17 @@
"author": "Lossless GmbH", "author": "Lossless GmbH",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@design.estate/dees-domtools": "^2.3.6", "@design.estate/dees-domtools": "^2.3.7",
"@design.estate/dees-element": "^2.1.3", "@design.estate/dees-element": "^2.1.5",
"@push.rocks/smartdelay": "^3.0.5", "@push.rocks/smartdelay": "^3.0.5",
"lit": "^3.3.1" "lit": "^3.3.2"
}, },
"devDependencies": { "devDependencies": {
"@api.global/typedserver": "^7.11.1", "@api.global/typedserver": "^8.1.0",
"@git.zone/tsbuild": "^4.0.2", "@git.zone/tsbuild": "^4.0.2",
"@git.zone/tsbundle": "^2.6.3", "@git.zone/tsbundle": "^2.6.3",
"@git.zone/tsrun": "^2.0.1", "@git.zone/tsrun": "^2.0.1",
"@git.zone/tstest": "^3.1.3", "@git.zone/tstest": "^3.1.4",
"@git.zone/tswatch": "^2.3.13", "@git.zone/tswatch": "^2.3.13",
"@push.rocks/projectinfo": "^5.0.2", "@push.rocks/projectinfo": "^5.0.2",
"@types/node": "^25.0.3" "@types/node": "^25.0.3"

959
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@design.estate/dees-wcctools', name: '@design.estate/dees-wcctools',
version: '3.5.0', version: '3.7.1',
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

@@ -12,6 +12,7 @@ import './wcc-properties.js';
import { type TTheme } from './wcc-properties.js'; import { type TTheme } from './wcc-properties.js';
import { breakpoints } from '@design.estate/dees-domtools'; import { breakpoints } from '@design.estate/dees-domtools';
import { WccFrame } from './wcc-frame.js'; import { WccFrame } from './wcc-frame.js';
import { WccSidebar } from './wcc-sidebar.js';
/** /**
* Get filtered and sorted items from a section * Get filtered and sorted items from a section
@@ -66,6 +67,10 @@ export class WccDashboard extends DeesElement {
@property({ attribute: false }) @property({ attribute: false })
accessor pinnedItems: Set<string> = new Set(); accessor pinnedItems: Set<string> = new Set();
// Sidebar width (resizable)
@property({ type: Number })
accessor sidebarWidth: number = 200;
// Derived from selectedViewport - no need for separate property // Derived from selectedViewport - no need for separate property
public get isNative(): boolean { public get isNative(): boolean {
return this.selectedViewport === 'native'; return this.selectedViewport === 'native';
@@ -127,6 +132,7 @@ export class WccDashboard extends DeesElement {
.selectedItem=${this.selectedItem} .selectedItem=${this.selectedItem}
.searchQuery=${this.searchQuery} .searchQuery=${this.searchQuery}
.pinnedItems=${this.pinnedItems} .pinnedItems=${this.pinnedItems}
.sidebarWidth=${this.sidebarWidth}
.isNative=${this.isNative} .isNative=${this.isNative}
@selectedType=${(eventArg) => { @selectedType=${(eventArg) => {
this.selectedType = eventArg.detail; this.selectedType = eventArg.detail;
@@ -145,6 +151,15 @@ export class WccDashboard extends DeesElement {
this.pinnedItems = eventArg.detail; this.pinnedItems = eventArg.detail;
this.updateUrlWithScrollState(); this.updateUrlWithScrollState();
}} }}
@widthChanged=${async (eventArg: CustomEvent) => {
this.sidebarWidth = eventArg.detail;
this.updateUrlWithScrollState();
const frame = await this.wccFrame;
if (frame) {
frame.sidebarWidth = eventArg.detail;
frame.requestUpdate();
}
}}
></wcc-sidebar> ></wcc-sidebar>
<wcc-properties <wcc-properties
.dashboardRef=${this} .dashboardRef=${this}
@@ -153,6 +168,7 @@ export class WccDashboard extends DeesElement {
.selectedViewport=${this.selectedViewport} .selectedViewport=${this.selectedViewport}
.selectedTheme=${this.selectedTheme} .selectedTheme=${this.selectedTheme}
.isNative=${this.isNative} .isNative=${this.isNative}
.sidebarWidth=${this.sidebarWidth}
@selectedViewport=${(eventArg) => { @selectedViewport=${(eventArg) => {
this.selectedViewport = eventArg.detail; this.selectedViewport = eventArg.detail;
this.scheduleUpdate(); this.scheduleUpdate();
@@ -171,7 +187,7 @@ export class WccDashboard extends DeesElement {
this.toggleNative(); this.toggleNative();
}} }}
></wcc-properties> ></wcc-properties>
<wcc-frame id="wccFrame" viewport=${this.selectedViewport} .isNative=${this.isNative}> <wcc-frame id="wccFrame" viewport=${this.selectedViewport} .isNative=${this.isNative} .sidebarWidth=${this.sidebarWidth}>
</wcc-frame> </wcc-frame>
`; `;
} }
@@ -247,6 +263,7 @@ export class WccDashboard extends DeesElement {
const frameScrollY = routeInfo.queryParams.frameScrollY; const frameScrollY = routeInfo.queryParams.frameScrollY;
const sidebarScrollY = routeInfo.queryParams.sidebarScrollY; const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
const pinned = routeInfo.queryParams.pinned; const pinned = routeInfo.queryParams.pinned;
const sidebarWidth = routeInfo.queryParams.sidebarWidth;
if (search) { if (search) {
this.searchQuery = search; this.searchQuery = search;
@@ -269,10 +286,19 @@ export class WccDashboard extends DeesElement {
} else if (this.pinnedItems.size > 0) { } else if (this.pinnedItems.size > 0) {
this.pinnedItems = new Set(); this.pinnedItems = new Set();
} }
if (sidebarWidth) {
this.sidebarWidth = parseInt(sidebarWidth, 10);
}
// Apply scroll positions after a short delay to ensure DOM is ready // Apply scroll positions and update frame after a short delay to ensure DOM is ready
setTimeout(() => { setTimeout(async () => {
this.applyScrollPositions(); this.applyScrollPositions();
// Ensure frame gets the sidebarWidth
const frame = await this.wccFrame;
if (frame) {
frame.sidebarWidth = this.sidebarWidth;
frame.requestUpdate();
}
}, 100); }, 100);
} else { } else {
this.searchQuery = ''; this.searchQuery = '';
@@ -326,6 +352,7 @@ export class WccDashboard extends DeesElement {
const frameScrollY = routeInfo.queryParams.frameScrollY; const frameScrollY = routeInfo.queryParams.frameScrollY;
const sidebarScrollY = routeInfo.queryParams.sidebarScrollY; const sidebarScrollY = routeInfo.queryParams.sidebarScrollY;
const pinned = routeInfo.queryParams.pinned; const pinned = routeInfo.queryParams.pinned;
const sidebarWidth = routeInfo.queryParams.sidebarWidth;
if (search) { if (search) {
this.searchQuery = search; this.searchQuery = search;
@@ -348,6 +375,9 @@ export class WccDashboard extends DeesElement {
} else if (this.pinnedItems.size > 0) { } else if (this.pinnedItems.size > 0) {
this.pinnedItems = new Set(); this.pinnedItems = new Set();
} }
if (sidebarWidth) {
this.sidebarWidth = parseInt(sidebarWidth, 10);
}
// Apply scroll positions after a short delay to ensure DOM is ready // Apply scroll positions after a short delay to ensure DOM is ready
setTimeout(() => { setTimeout(() => {
@@ -444,6 +474,9 @@ export class WccDashboard extends DeesElement {
if (this.pinnedItems.size > 0) { if (this.pinnedItems.size > 0) {
queryParams.set('pinned', Array.from(this.pinnedItems).join(',')); queryParams.set('pinned', Array.from(this.pinnedItems).join(','));
} }
if (this.sidebarWidth !== 200) {
queryParams.set('sidebarWidth', this.sidebarWidth.toString());
}
const queryString = queryParams.toString(); const queryString = queryParams.toString();
const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl; const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
@@ -461,7 +494,7 @@ export class WccDashboard extends DeesElement {
} }
const wccFrame = await this.wccFrame; const wccFrame = await this.wccFrame;
const wccSidebar = this.shadowRoot.querySelector('wcc-sidebar'); const wccSidebar = this.shadowRoot.querySelector('wcc-sidebar') as WccSidebar | null;
if (wccFrame) { if (wccFrame) {
// The frame element itself is the scrollable container // The frame element itself is the scrollable container
@@ -473,13 +506,16 @@ export class WccDashboard extends DeesElement {
} }
if (wccSidebar) { if (wccSidebar) {
// The sidebar element itself is the scrollable container // Use the sidebar's scrollable container (.menu element)
wccSidebar.addEventListener('scroll', () => { const scrollContainer = wccSidebar.scrollableContainer;
this.sidebarScrollY = wccSidebar.scrollTop; if (scrollContainer) {
scrollContainer.addEventListener('scroll', () => {
this.sidebarScrollY = scrollContainer.scrollTop;
this.debouncedScrollUpdate(); this.debouncedScrollUpdate();
}); });
} }
} }
}
private debouncedScrollUpdate() { private debouncedScrollUpdate() {
clearTimeout(this.scrollUpdateTimeout); clearTimeout(this.scrollUpdateTimeout);
@@ -507,6 +543,9 @@ export class WccDashboard extends DeesElement {
if (this.pinnedItems.size > 0) { if (this.pinnedItems.size > 0) {
queryParams.set('pinned', Array.from(this.pinnedItems).join(',')); queryParams.set('pinned', Array.from(this.pinnedItems).join(','));
} }
if (this.sidebarWidth !== 200) {
queryParams.set('sidebarWidth', this.sidebarWidth.toString());
}
const queryString = queryParams.toString(); const queryString = queryParams.toString();
const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl; const fullUrl = queryString ? `${baseUrl}?${queryString}` : baseUrl;
@@ -522,7 +561,7 @@ export class WccDashboard extends DeesElement {
} }
const wccFrame = await this.wccFrame; const wccFrame = await this.wccFrame;
const wccSidebar = this.shadowRoot.querySelector('wcc-sidebar'); const wccSidebar = this.shadowRoot.querySelector('wcc-sidebar') as WccSidebar | null;
if (wccFrame && this.frameScrollY > 0) { if (wccFrame && this.frameScrollY > 0) {
// The frame element itself is the scrollable container // The frame element itself is the scrollable container
@@ -530,8 +569,11 @@ export class WccDashboard extends DeesElement {
} }
if (wccSidebar && this.sidebarScrollY > 0) { if (wccSidebar && this.sidebarScrollY > 0) {
// The sidebar element itself is the scrollable container // Use the sidebar's scrollable container (.menu element)
wccSidebar.scrollTop = this.sidebarScrollY; const scrollContainer = wccSidebar.scrollableContainer;
if (scrollContainer) {
scrollContainer.scrollTop = this.sidebarScrollY;
}
} }
this.scrollPositionsApplied = true; this.scrollPositionsApplied = true;

View File

@@ -19,13 +19,18 @@ export class WccFrame extends DeesElement {
@property({ type: Boolean }) @property({ type: Boolean })
accessor isNative: boolean = false; accessor isNative: boolean = false;
@property({ type: Number })
accessor sidebarWidth: number = 200;
@property({ type: Boolean })
accessor isResizing: boolean = false;
public static styles = [ public static styles = [
css` css`
:host { :host {
border: 10px solid #ffaeaf; border: 10px solid rgba(255, 174, 175, 1);
position: absolute; position: absolute;
background: ${cssManager.bdTheme('#333', '#000')}; background: ${cssManager.bdTheme('#333', '#000')};
left: 200px;
right: 0px; right: 0px;
top: 0px; top: 0px;
overflow-y: auto; overflow-y: auto;
@@ -47,17 +52,17 @@ export class WccFrame extends DeesElement {
<style> <style>
:host { :host {
${this.isNative ? ` ${this.isNative ? `
border: none !important; border: 0px solid rgba(255, 174, 175, 0) !important;
left: 0px !important; left: 0px !important;
right: 0px !important; right: 0px !important;
top: 0px !important; top: 0px !important;
bottom: 0px !important; bottom: 0px !important;
` : ` ` : `
bottom: ${this.advancedEditorOpen ? '400px' : '100px'}; bottom: ${this.advancedEditorOpen ? '400px' : '100px'};
border: 10px solid #ffaeaf; border: 10px solid rgba(255, 174, 175, 1);
left: 200px; left: ${this.sidebarWidth}px;
`} `}
transition: all 0.3s ease; transition: ${this.isResizing ? 'none' : 'all 0.3s ease'};
${this.isNative ? 'padding: 0px;' : (() => { ${this.isNative ? 'padding: 0px;' : (() => {
switch (this.viewport) { switch (this.viewport) {
case 'desktop': case 'desktop':
@@ -67,19 +72,19 @@ export class WccFrame extends DeesElement {
case 'tablet': case 'tablet':
return ` return `
padding: 0px ${ padding: 0px ${
(document.body.clientWidth - 200 - domtools.breakpoints.tablet) / 2 (document.body.clientWidth - this.sidebarWidth - domtools.breakpoints.tablet) / 2
}px; }px;
`; `;
case 'phablet': case 'phablet':
return ` return `
padding: 0px ${ padding: 0px ${
(document.body.clientWidth - 200 - domtools.breakpoints.phablet) / 2 (document.body.clientWidth - this.sidebarWidth - domtools.breakpoints.phablet) / 2
}px; }px;
`; `;
case 'phone': case 'phone':
return ` return `
padding: 0px ${ padding: 0px ${
(document.body.clientWidth - 200 - domtools.breakpoints.phone) / 2 (document.body.clientWidth - this.sidebarWidth - domtools.breakpoints.phone) / 2
}px; }px;
`; `;
} }

View File

@@ -37,6 +37,9 @@ export class WccProperties extends DeesElement {
@property() @property()
accessor isNative: boolean = false; accessor isNative: boolean = false;
@property({ type: Number })
accessor sidebarWidth: number = 200;
@state() @state()
accessor propertyContent: TemplateResult[] = []; accessor propertyContent: TemplateResult[] = [];
@@ -60,6 +63,10 @@ export class WccProperties extends DeesElement {
@state() @state()
accessor recordingDuration: number = 0; accessor recordingDuration: number = 0;
// Delayed hide for native mode transition
@state()
accessor isHidden: boolean = false;
public editorHeight: number = 300; public editorHeight: number = 300;
public render(): TemplateResult { public render(): TemplateResult {
@@ -89,14 +96,14 @@ export class WccProperties extends DeesElement {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; 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: ${this.sidebarWidth}px;
height: ${this.editingProperties.length > 0 ? 100 + this.editorHeight : 100}px; height: ${this.editingProperties.length > 0 ? 100 + this.editorHeight : 100}px;
bottom: 0px; bottom: 0px;
right: 0px; right: 0px;
overflow: hidden; overflow: hidden;
background: var(--background); background: var(--background);
color: var(--foreground); color: var(--foreground);
display: ${this.isNative ? 'none' : 'block'}; display: ${this.isHidden ? 'none' : 'block'};
} }
.grid { .grid {
display: grid; display: grid;
@@ -928,6 +935,19 @@ export class WccProperties extends DeesElement {
protected updated(changedProperties: Map<string, unknown>) { protected updated(changedProperties: Map<string, unknown>) {
super.updated(changedProperties); super.updated(changedProperties);
// Handle delayed hide for native mode transition
if (changedProperties.has('isNative')) {
if (this.isNative) {
// Delay hiding until frame animation completes
setTimeout(() => {
this.isHidden = true;
}, 300);
} else {
// Show immediately when exiting native mode
this.isHidden = false;
}
}
// Only recreate properties when selectedItem changes // Only recreate properties when selectedItem changes
if (changedProperties.has('selectedItem')) { if (changedProperties.has('selectedItem')) {
this.createProperties().catch(error => { this.createProperties().catch(error => {

View File

@@ -36,8 +36,31 @@ export class WccSidebar extends DeesElement {
@property({ attribute: false }) @property({ attribute: false })
accessor pinnedItems: Set<string> = new Set(); accessor pinnedItems: Set<string> = new Set();
// Sidebar width (resizable)
@property({ type: Number })
accessor sidebarWidth: number = 200;
// Track if currently resizing
@state()
accessor isResizing: boolean = false;
// Delayed hide for native mode transition
@state()
accessor isHidden: boolean = false;
// Track if menu is scrolled for header shadow
@state()
accessor isMenuScrolled: boolean = false;
private sectionsInitialized = false; private sectionsInitialized = false;
/**
* Returns the scrollable container element (.menu) for external scroll management
*/
public get scrollableContainer(): HTMLElement | null {
return this.shadowRoot?.querySelector('.menu') as HTMLElement | null;
}
public render(): TemplateResult { public render(): TemplateResult {
return html` return html`
<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" /> <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" />
@@ -59,23 +82,40 @@ export class WccSidebar extends DeesElement {
--ring: #3b82f6; --ring: #3b82f6;
--radius: 4px; --radius: 4px;
display: ${this.isNative ? 'none' : 'block'}; display: ${this.isHidden ? 'none' : 'flex'};
flex-direction: column;
border-right: 1px solid rgba(255, 255, 255, 0.08); border-right: 1px solid rgba(255, 255, 255, 0.08);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif; font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
font-size: 14px; font-size: 14px;
box-sizing: border-box; box-sizing: border-box;
position: absolute; position: absolute;
left: 0px; left: 0px;
width: 200px; width: ${this.sidebarWidth}px;
top: 0px; top: 0px;
bottom: 0px; bottom: 0px;
overflow-y: auto; overflow: hidden;
overflow-x: hidden;
background: var(--background); background: var(--background);
color: var(--foreground); color: var(--foreground);
} }
.sidebar-header {
flex-shrink: 0;
transition: box-shadow 0.2s ease, border-color 0.2s ease;
border-bottom: 1px solid transparent;
position: relative;
z-index: 1;
}
.sidebar-header.scrolled {
box-shadow: 0 8px 24px -2px rgba(0, 0, 0, 1);
border-bottom-color: var(--border);
}
.menu { .menu {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
padding: 0.5rem 0; padding: 0.5rem 0;
} }
@@ -167,6 +207,10 @@ export class WccSidebar extends DeesElement {
grid-template-columns: 16px 1fr; grid-template-columns: 16px 1fr;
} }
.selectOption.folder .text {
margin-left: 4px;
}
.selectOption .expand-icon { .selectOption .expand-icon {
font-size: 14px; font-size: 14px;
opacity: 0.5; opacity: 0.5;
@@ -265,6 +309,7 @@ export class WccSidebar extends DeesElement {
.search-container { .search-container {
padding: 0.5rem; padding: 0.5rem;
border-bottom: 1px solid var(--border); border-bottom: 1px solid var(--border);
position: relative;
} }
.search-input { .search-input {
@@ -273,7 +318,7 @@ export class WccSidebar extends DeesElement {
background: var(--input); background: var(--input);
border: 1px solid var(--border); border: 1px solid var(--border);
border-radius: var(--radius); border-radius: var(--radius);
padding: 0.5rem 0.75rem; padding: 0.5rem 1.75rem 0.5rem 0.75rem;
color: var(--foreground); color: var(--foreground);
font-size: 0.75rem; font-size: 0.75rem;
font-family: inherit; font-family: inherit;
@@ -289,6 +334,33 @@ export class WccSidebar extends DeesElement {
color: var(--muted-foreground); color: var(--muted-foreground);
} }
.search-clear {
position: absolute;
right: 0.75rem;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
padding: 0.25rem;
cursor: pointer;
color: var(--muted-foreground);
display: flex;
align-items: center;
justify-content: center;
border-radius: 2px;
transition: color 0.15s ease, background 0.15s ease;
}
.search-clear:hover {
color: var(--foreground);
background: rgba(255, 255, 255, 0.1);
}
.search-clear .material-symbols-outlined {
font-size: 14px;
opacity: 1;
}
.highlight { .highlight {
background: rgba(59, 130, 246, 0.3); background: rgba(59, 130, 246, 0.3);
border-radius: 2px; border-radius: 2px;
@@ -321,13 +393,17 @@ export class WccSidebar extends DeesElement {
opacity: 0.8; opacity: 0.8;
} }
/* Section tag for pinned items */ /* Section tag pill for pinned items */
.section-tag { .section-tag {
font-size: 0.55rem; font-size: 0.5rem;
color: #555; color: #888;
margin-left: auto; margin-left: auto;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.03em; letter-spacing: 0.02em;
background: rgba(255, 255, 255, 0.06);
padding: 0.15rem 0.4rem;
border-radius: 9999px;
white-space: nowrap;
} }
/* Group container */ /* Group container */
@@ -352,7 +428,29 @@ export class WccSidebar extends DeesElement {
margin-left: 0.25rem; margin-left: 0.25rem;
margin-right: 0.25rem; margin-right: 0.25rem;
} }
/* Resize handle */
.resize-handle {
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 4px;
cursor: col-resize;
background: transparent;
transition: background 0.15s ease;
z-index: 10;
}
.resize-handle:hover {
background: rgba(59, 130, 246, 0.3);
}
.resize-handle.active {
background: var(--primary);
}
</style> </style>
<div class="sidebar-header ${this.isMenuScrolled ? 'scrolled' : ''}">
<div class="search-container"> <div class="search-container">
<input <input
type="text" type="text"
@@ -361,11 +459,21 @@ export class WccSidebar extends DeesElement {
.value=${this.searchQuery} .value=${this.searchQuery}
@input=${this.handleSearchInput} @input=${this.handleSearchInput}
/> />
${this.searchQuery ? html`
<button class="search-clear" @click=${this.clearSearch}>
<i class="material-symbols-outlined">close</i>
</button>
` : null}
</div> </div>
<div class="menu">
${this.renderPinnedSection()} ${this.renderPinnedSection()}
</div>
<div class="menu" @scroll=${this.handleMenuScroll}>
${this.renderSections()} ${this.renderSections()}
</div> </div>
<div
class="resize-handle ${this.isResizing ? 'active' : ''}"
@mousedown=${this.startResize}
></div>
`; `;
} }
@@ -429,6 +537,7 @@ export class WccSidebar extends DeesElement {
const isCollapsed = this.collapsedSections.has('__pinned__'); const isCollapsed = this.collapsedSections.has('__pinned__');
// Collect pinned items with their original section info // Collect pinned items with their original section info
// Pinned items are NOT filtered by search - they always remain visible
const pinnedEntries: Array<{ sectionName: string; itemName: string; item: any; section: IWccSection }> = []; const pinnedEntries: Array<{ sectionName: string; itemName: string; item: any; section: IWccSection }> = [];
for (const key of this.pinnedItems) { for (const key of this.pinnedItems) {
@@ -443,10 +552,7 @@ export class WccSidebar extends DeesElement {
} }
} }
// Filter by search if (pinnedEntries.length === 0) {
const filteredEntries = pinnedEntries.filter(e => this.matchesSearch(e.itemName));
if (filteredEntries.length === 0 && this.searchQuery) {
return null; return null;
} }
@@ -460,7 +566,7 @@ export class WccSidebar extends DeesElement {
<span>Pinned</span> <span>Pinned</span>
</div> </div>
<div class="section-content ${isCollapsed ? 'collapsed' : ''}"> <div class="section-content ${isCollapsed ? 'collapsed' : ''}">
${filteredEntries.map(({ sectionName, itemName, item, section }) => { ${pinnedEntries.map(({ sectionName, itemName, item, section }) => {
const isSelected = this.selectedItem === item; const isSelected = this.selectedItem === item;
const type = section.type === 'elements' ? 'element' : 'page'; const type = section.type === 'elements' ? 'element' : 'page';
const icon = section.type === 'elements' ? 'featured_video' : 'insert_drive_file'; const icon = section.type === 'elements' ? 'featured_video' : 'insert_drive_file';
@@ -561,27 +667,43 @@ export class WccSidebar extends DeesElement {
groupedItems.get(group)!.push(entry); groupedItems.get(group)!.push(entry);
} }
const result: TemplateResult[] = []; // Build a unified list of render items (ungrouped elements and groups)
// Each item has a sortKey (element name or first element name of group)
type RenderItem =
| { type: 'element'; entry: [string, any]; sortKey: string }
| { type: 'group'; groupName: string; items: Array<[string, any]>; sortKey: string };
// Render ungrouped items first const renderItems: RenderItem[] = [];
// Add ungrouped items
const ungrouped = groupedItems.get(null) || []; const ungrouped = groupedItems.get(null) || [];
for (const entry of ungrouped) { for (const entry of ungrouped) {
result.push(this.renderElementItem(entry, section)); renderItems.push({ type: 'element', entry, sortKey: entry[0].toLowerCase() });
} }
// Render grouped items // Add groups (sorted by their first element's name)
for (const [groupName, items] of groupedItems) { for (const [groupName, items] of groupedItems) {
if (groupName === null) continue; if (groupName === null) continue;
const firstElementName = items[0]?.[0] || '';
result.push(html` renderItems.push({ type: 'group', groupName, items, sortKey: firstElementName.toLowerCase() });
<div class="item-group">
<span class="item-group-legend">${groupName}</span>
${items.map((entry) => this.renderElementItem(entry, section))}
</div>
`);
} }
return result; // Sort all items alphabetically by sortKey
renderItems.sort((a, b) => a.sortKey.localeCompare(b.sortKey));
// Render in sorted order
return renderItems.map((item) => {
if (item.type === 'element') {
return this.renderElementItem(item.entry, section);
} else {
return html`
<div class="item-group">
<span class="item-group-legend">${item.groupName}</span>
${item.items.map((entry) => this.renderElementItem(entry, section))}
</div>
`;
}
});
} }
} }
@@ -673,6 +795,16 @@ export class WccSidebar extends DeesElement {
this.dispatchEvent(new CustomEvent('searchChanged', { detail: this.searchQuery })); this.dispatchEvent(new CustomEvent('searchChanged', { detail: this.searchQuery }));
} }
private clearSearch() {
this.searchQuery = '';
this.dispatchEvent(new CustomEvent('searchChanged', { detail: this.searchQuery }));
}
private handleMenuScroll(e: Event) {
const target = e.target as HTMLElement;
this.isMenuScrolled = target.scrollTop > 0;
}
private matchesSearch(name: string): boolean { private matchesSearch(name: string): boolean {
if (!this.searchQuery) return true; if (!this.searchQuery) return true;
return name.toLowerCase().includes(this.searchQuery.toLowerCase()); return name.toLowerCase().includes(this.searchQuery.toLowerCase());
@@ -693,6 +825,19 @@ export class WccSidebar extends DeesElement {
protected updated(changedProperties: Map<string, unknown>) { protected updated(changedProperties: Map<string, unknown>) {
super.updated(changedProperties); super.updated(changedProperties);
// Handle delayed hide for native mode transition
if (changedProperties.has('isNative')) {
if (this.isNative) {
// Delay hiding until frame animation completes
setTimeout(() => {
this.isHidden = true;
}, 300);
} else {
// Show immediately when exiting native mode
this.isHidden = false;
}
}
// Auto-expand folder when a multi-demo element is selected // Auto-expand folder when a multi-demo element is selected
if (changedProperties.has('selectedItem') && this.selectedItem && this.dashboardRef?.sections) { if (changedProperties.has('selectedItem') && this.selectedItem && this.dashboardRef?.sections) {
// Find the element in any section // Find the element in any section
@@ -717,6 +862,51 @@ export class WccSidebar extends DeesElement {
} }
} }
// ============ Resize functionality ============
private startResize = (e: MouseEvent) => {
e.preventDefault();
this.isResizing = true;
const startX = e.clientX;
const startWidth = this.sidebarWidth;
// Cache references once at start
const frame = this.dashboardRef?.shadowRoot?.querySelector('wcc-frame') as any;
const properties = this.dashboardRef?.shadowRoot?.querySelector('wcc-properties') as any;
// Disable frame transition during resize
if (frame) {
frame.isResizing = true;
}
const onMouseMove = (e: MouseEvent) => {
const newWidth = Math.min(400, Math.max(150, startWidth + (e.clientX - startX)));
this.sidebarWidth = newWidth;
// Update frame and properties directly
if (frame) {
frame.sidebarWidth = newWidth;
}
if (properties) {
properties.sidebarWidth = newWidth;
}
};
const onMouseUp = () => {
this.isResizing = false;
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
// Re-enable frame transition
if (frame) {
frame.isResizing = false;
}
// Dispatch event on release for URL persistence
this.dispatchEvent(new CustomEvent('widthChanged', { detail: this.sidebarWidth }));
};
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
};
public selectItem( public selectItem(
typeArg: TElementType, typeArg: TElementType,
itemNameArg: string, itemNameArg: string,