Compare commits

..

4 Commits

Author SHA1 Message Date
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
9 changed files with 157 additions and 22 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,13 @@
# Changelog # Changelog
## 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.5.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": {

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.5.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

@@ -66,6 +66,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 +131,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 +150,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 +167,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 +186,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 +262,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 +285,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 +351,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 +374,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 +473,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;
@@ -507,6 +539,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;

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 #ffaeaf;
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;
@@ -55,9 +60,9 @@ export class WccFrame extends DeesElement {
` : ` ` : `
bottom: ${this.advancedEditorOpen ? '400px' : '100px'}; bottom: ${this.advancedEditorOpen ? '400px' : '100px'};
border: 10px solid #ffaeaf; border: 10px solid #ffaeaf;
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[] = [];
@@ -89,7 +92,7 @@ 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;

View File

@@ -36,6 +36,14 @@ 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;
private sectionsInitialized = false; private sectionsInitialized = false;
public render(): TemplateResult { public render(): TemplateResult {
@@ -66,7 +74,7 @@ export class WccSidebar extends DeesElement {
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-y: auto;
@@ -167,6 +175,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;
@@ -321,13 +333,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,6 +368,27 @@ 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="search-container"> <div class="search-container">
<input <input
@@ -366,6 +403,10 @@ export class WccSidebar extends DeesElement {
${this.renderPinnedSection()} ${this.renderPinnedSection()}
${this.renderSections()} ${this.renderSections()}
</div> </div>
<div
class="resize-handle ${this.isResizing ? 'active' : ''}"
@mousedown=${this.startResize}
></div>
`; `;
} }
@@ -429,6 +470,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 +485,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 +499,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';
@@ -717,6 +756,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,