Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0e816379a5 | |||
| aa2c065918 | |||
| a778ad6855 | |||
| 24a1f064ba | |||
| 203a53a45d | |||
| 349d4ba320 | |||
| 399ef3d508 |
45
changelog.md
45
changelog.md
@@ -1,5 +1,50 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-01-27 - 3.8.0 - feat(sidebar)
|
||||||
|
rename demoGroup to demoGroups, add multi-group support, search by group name, and context menu group navigation
|
||||||
|
|
||||||
|
- Static property demoGroup renamed to demoGroups; accepts string | string[] for multi-group membership
|
||||||
|
- Elements with multiple demoGroups appear in each group's sidebar section simultaneously and show the library_books icon instead of featured_video
|
||||||
|
- Sidebar search now matches group names in addition to element names; groups are sorted alphabetically by group name
|
||||||
|
- Context menu for elements includes a "Show in Group:" section with navigable group entries that scroll to and briefly highlight the target group; group headers also have a context menu entry to filter by that group
|
||||||
|
- Added data-group attribute on .item-group for DOM querying and visual classes for group highlight and filter match
|
||||||
|
- Updated test elements to use demoGroups and updated docs/changelog/readme.hints to document the new behavior
|
||||||
|
- Bumped several devDependencies (@api.global/typedserver, @git.zone/tsbuild, @git.zone/tsbundle, @git.zone/tstest, @git.zone/tswatch, @types/node) and adjusted npm script `watch` to use tswatch
|
||||||
|
|
||||||
|
## 2026-01-27 - 3.8.0 - feat(sidebar)
|
||||||
|
rename demoGroup to demoGroups, add multi-group support, search by group name, and context menu group navigation
|
||||||
|
|
||||||
|
- Rename static property `demoGroup` to `demoGroups` on element classes; accepts `string | string[]` for multi-group membership
|
||||||
|
- Elements with an array of `demoGroups` appear in each group's sidebar section simultaneously
|
||||||
|
- Search now matches group names in addition to element names (e.g. searching "Buttons" shows all elements in the Buttons group)
|
||||||
|
- Groups are sorted alphabetically by group name instead of by first element name
|
||||||
|
- Elements belonging to multiple groups display a `library_books` icon instead of `featured_video`
|
||||||
|
- Right-click context menu on elements with groups shows "Show in Group:" section with navigable group entries
|
||||||
|
- Clicking a group name in the context menu scrolls to and briefly highlights that group in the sidebar
|
||||||
|
- Updated test elements (test-button-primary, test-button-secondary, test-button-danger, test-input-text, test-input-checkbox) to use `demoGroups`
|
||||||
|
|
||||||
|
## 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)
|
## 2026-01-04 - 3.6.1 - fix(wcc-sidebar)
|
||||||
sort sidebar items alphabetically and unify grouped and ungrouped items for consistent ordering
|
sort sidebar items alphabetically and unify grouped and ungrouped items for consistent ordering
|
||||||
|
|
||||||
|
|||||||
@@ -35,5 +35,8 @@
|
|||||||
},
|
},
|
||||||
"@ship.zone/szci": {
|
"@ship.zone/szci": {
|
||||||
"npmGlobalTools": []
|
"npmGlobalTools": []
|
||||||
|
},
|
||||||
|
"@git.zone/tswatch": {
|
||||||
|
"preset": "element"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@design.estate/dees-wcctools",
|
"name": "@design.estate/dees-wcctools",
|
||||||
"version": "3.6.1",
|
"version": "3.8.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.",
|
"description": "A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.",
|
||||||
"exports": {
|
"exports": {
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(npm run build)",
|
"test": "(npm run build)",
|
||||||
"build": "(tsbuild tsfolders --allowimplicitany && tsbundle element)",
|
"build": "(tsbuild tsfolders --allowimplicitany && tsbundle element)",
|
||||||
"watch": "tswatch element",
|
"watch": "tswatch",
|
||||||
"buildDocs": "tsdoc"
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
"author": "Lossless GmbH",
|
"author": "Lossless GmbH",
|
||||||
@@ -23,14 +23,14 @@
|
|||||||
"lit": "^3.3.2"
|
"lit": "^3.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@api.global/typedserver": "^8.1.0",
|
"@api.global/typedserver": "^8.3.0",
|
||||||
"@git.zone/tsbuild": "^4.0.2",
|
"@git.zone/tsbuild": "^4.1.2",
|
||||||
"@git.zone/tsbundle": "^2.6.3",
|
"@git.zone/tsbundle": "^2.8.3",
|
||||||
"@git.zone/tsrun": "^2.0.1",
|
"@git.zone/tsrun": "^2.0.1",
|
||||||
"@git.zone/tstest": "^3.1.4",
|
"@git.zone/tstest": "^3.1.8",
|
||||||
"@git.zone/tswatch": "^2.3.13",
|
"@git.zone/tswatch": "^3.0.1",
|
||||||
"@push.rocks/projectinfo": "^5.0.2",
|
"@push.rocks/projectinfo": "^5.0.2",
|
||||||
"@types/node": "^25.0.3"
|
"@types/node": "^25.0.10"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
|
|||||||
3241
pnpm-lock.yaml
generated
3241
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -62,6 +62,33 @@ Section names are URL-encoded. Legacy routes (`element`/`page` as section name)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Element Demo Groups (2026-01-27)
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
Elements can declare `demoGroups` (renamed from `demoGroup`) as a static property to appear grouped in the sidebar. Supports `string | string[]` — elements with an array appear in multiple groups simultaneously.
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
```typescript
|
||||||
|
// Single group
|
||||||
|
public static demoGroups = 'Buttons';
|
||||||
|
|
||||||
|
// Multiple groups — element appears in both
|
||||||
|
public static demoGroups = ['Buttons', 'Form Controls'];
|
||||||
|
```
|
||||||
|
|
||||||
|
### Features
|
||||||
|
- Search matches group names (searching "Buttons" shows all elements in that group)
|
||||||
|
- Groups sorted alphabetically by group name
|
||||||
|
- Multi-group elements show `library_books` icon instead of `featured_video`
|
||||||
|
- Context menu shows "Show in Group:" with clickable group entries that scroll to and highlight the group
|
||||||
|
- `data-group` attribute on `.item-group` containers for DOM querying
|
||||||
|
|
||||||
|
### Files Changed
|
||||||
|
- `ts_web/elements/wcc-sidebar.ts` — grouping logic, search filter, sort key, icon, context menu, scrollToGroup
|
||||||
|
- `test/elements/test-button-*.ts`, `test/elements/test-input-*.ts` — renamed `demoGroup` → `demoGroups`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## UI Redesign with Shadcn-like Styles (2025-06-27)
|
## UI Redesign with Shadcn-like Styles (2025-06-27)
|
||||||
|
|
||||||
### Changes Made
|
### Changes Made
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export * from './test-withwrapper.js';
|
|||||||
export * from './test-edgecases.js';
|
export * from './test-edgecases.js';
|
||||||
export * from './test-nested.js';
|
export * from './test-nested.js';
|
||||||
|
|
||||||
// Grouped elements to demo the demoGroup feature
|
// Grouped elements to demo the demoGroups feature
|
||||||
export * from './test-button-primary.js';
|
export * from './test-button-primary.js';
|
||||||
export * from './test-button-secondary.js';
|
export * from './test-button-secondary.js';
|
||||||
export * from './test-button-danger.js';
|
export * from './test-button-danger.js';
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
@customElement('test-button-danger')
|
@customElement('test-button-danger')
|
||||||
export class TestButtonDanger extends DeesElement {
|
export class TestButtonDanger extends DeesElement {
|
||||||
// Same group as other buttons
|
// Same group as other buttons
|
||||||
public static demoGroup = 'Buttons';
|
public static demoGroups = 'Buttons';
|
||||||
|
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<test-button-danger>Delete</test-button-danger>
|
<test-button-danger>Delete</test-button-danger>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
@customElement('test-button-primary')
|
@customElement('test-button-primary')
|
||||||
export class TestButtonPrimary extends DeesElement {
|
export class TestButtonPrimary extends DeesElement {
|
||||||
// This groups the element with other "Buttons" in the sidebar
|
// This groups the element with other "Buttons" in the sidebar
|
||||||
public static demoGroup = 'Buttons';
|
public static demoGroups = 'Buttons';
|
||||||
|
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<test-button-primary>Click Me</test-button-primary>
|
<test-button-primary>Click Me</test-button-primary>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
@customElement('test-button-secondary')
|
@customElement('test-button-secondary')
|
||||||
export class TestButtonSecondary extends DeesElement {
|
export class TestButtonSecondary extends DeesElement {
|
||||||
// Same group as test-button-primary - they'll appear together
|
// Same group as test-button-primary - they'll appear together
|
||||||
public static demoGroup = 'Buttons';
|
public static demoGroups = 'Buttons';
|
||||||
|
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<test-button-secondary>Secondary Action</test-button-secondary>
|
<test-button-secondary>Secondary Action</test-button-secondary>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
@customElement('test-input-checkbox')
|
@customElement('test-input-checkbox')
|
||||||
export class TestInputCheckbox extends DeesElement {
|
export class TestInputCheckbox extends DeesElement {
|
||||||
// Same group as test-input-text
|
// Same group as test-input-text
|
||||||
public static demoGroup = 'Inputs';
|
public static demoGroups = ['Inputs', 'A Second Group'];
|
||||||
|
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<test-input-checkbox label="Accept terms and conditions"></test-input-checkbox>
|
<test-input-checkbox label="Accept terms and conditions"></test-input-checkbox>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import {
|
|||||||
@customElement('test-input-text')
|
@customElement('test-input-text')
|
||||||
export class TestInputText extends DeesElement {
|
export class TestInputText extends DeesElement {
|
||||||
// Different group - "Inputs"
|
// Different group - "Inputs"
|
||||||
public static demoGroup = 'Inputs';
|
public static demoGroups = 'Inputs';
|
||||||
|
|
||||||
public static demo = () => html`
|
public static demo = () => html`
|
||||||
<test-input-text placeholder="Enter text..."></test-input-text>
|
<test-input-text placeholder="Enter text..."></test-input-text>
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@design.estate/dees-wcctools',
|
name: '@design.estate/dees-wcctools',
|
||||||
version: '3.6.1',
|
version: '3.8.0',
|
||||||
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
|
description: 'A set of web component tools for creating element catalogues, enabling the structured development and documentation of custom elements and pages.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -493,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
|
||||||
@@ -505,11 +506,14 @@ 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) {
|
||||||
this.debouncedScrollUpdate();
|
scrollContainer.addEventListener('scroll', () => {
|
||||||
});
|
this.sidebarScrollY = scrollContainer.scrollTop;
|
||||||
|
this.debouncedScrollUpdate();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,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
|
||||||
@@ -565,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;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { WccDashboard, getSectionItems } from './wcc-dashboard.js';
|
|||||||
import type { TTemplateFactory } from './wcctools.helpers.js';
|
import type { TTemplateFactory } from './wcctools.helpers.js';
|
||||||
import { getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
|
import { getDemoCount, hasMultipleDemos } from './wcctools.helpers.js';
|
||||||
import type { IWccSection, TElementType } from '../wcctools.interfaces.js';
|
import type { IWccSection, TElementType } from '../wcctools.interfaces.js';
|
||||||
import { WccContextmenu } from './wcc-contextmenu.js';
|
import { WccContextmenu, type IContextMenuItem } from './wcc-contextmenu.js';
|
||||||
|
|
||||||
@customElement('wcc-sidebar')
|
@customElement('wcc-sidebar')
|
||||||
export class WccSidebar extends DeesElement {
|
export class WccSidebar extends DeesElement {
|
||||||
@@ -48,8 +48,19 @@ export class WccSidebar extends DeesElement {
|
|||||||
@state()
|
@state()
|
||||||
accessor isHidden: boolean = false;
|
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" />
|
||||||
@@ -89,6 +100,15 @@ export class WccSidebar extends DeesElement {
|
|||||||
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
flex-shrink: 0;
|
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 {
|
||||||
@@ -402,6 +422,7 @@ export class WccSidebar extends DeesElement {
|
|||||||
color: #555;
|
color: #555;
|
||||||
padding: 0.125rem 0.625rem 0.25rem;
|
padding: 0.125rem 0.625rem 0.25rem;
|
||||||
display: block;
|
display: block;
|
||||||
|
cursor: context-menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item-group .selectOption {
|
.item-group .selectOption {
|
||||||
@@ -409,6 +430,15 @@ export class WccSidebar extends DeesElement {
|
|||||||
margin-right: 0.25rem;
|
margin-right: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.item-group.group-highlight {
|
||||||
|
background: rgba(59, 130, 246, 0.15);
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-group.group-filter-match {
|
||||||
|
border-color: rgba(245, 158, 11, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
/* Resize handle */
|
/* Resize handle */
|
||||||
.resize-handle {
|
.resize-handle {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -430,7 +460,7 @@ export class WccSidebar extends DeesElement {
|
|||||||
background: var(--primary);
|
background: var(--primary);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header ${this.isMenuScrolled ? 'scrolled' : ''}">
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -447,7 +477,7 @@ export class WccSidebar extends DeesElement {
|
|||||||
</div>
|
</div>
|
||||||
${this.renderPinnedSection()}
|
${this.renderPinnedSection()}
|
||||||
</div>
|
</div>
|
||||||
<div class="menu">
|
<div class="menu" @scroll=${this.handleMenuScroll}>
|
||||||
${this.renderSections()}
|
${this.renderSections()}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -497,12 +527,49 @@ export class WccSidebar extends DeesElement {
|
|||||||
|
|
||||||
private showContextMenu(e: MouseEvent, sectionName: string, itemName: string) {
|
private showContextMenu(e: MouseEvent, sectionName: string, itemName: string) {
|
||||||
const isPinned = this.isPinned(sectionName, itemName);
|
const isPinned = this.isPinned(sectionName, itemName);
|
||||||
WccContextmenu.show(e, [
|
const section = this.dashboardRef?.sections?.find(s => s.name === sectionName);
|
||||||
|
const sectionEntries = section ? getSectionItems(section) : [];
|
||||||
|
const foundEntry = sectionEntries.find(([name]) => name === itemName);
|
||||||
|
const item = foundEntry?.[1];
|
||||||
|
const groups = item ? this.getElementGroups(item) : [];
|
||||||
|
|
||||||
|
const menuItems: IContextMenuItem[] = [
|
||||||
{
|
{
|
||||||
name: isPinned ? 'Unpin' : 'Pin',
|
name: isPinned ? 'Unpin' : 'Pin',
|
||||||
iconName: isPinned ? 'push_pin' : 'push_pin',
|
iconName: isPinned ? 'push_pin' : 'push_pin',
|
||||||
action: () => this.togglePin(sectionName, itemName),
|
action: () => this.togglePin(sectionName, itemName),
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
if (groups.length > 0) {
|
||||||
|
menuItems.push({
|
||||||
|
name: 'Show in Group:',
|
||||||
|
iconName: 'folder',
|
||||||
|
action: () => {},
|
||||||
|
disabled: true,
|
||||||
|
});
|
||||||
|
for (const groupName of groups) {
|
||||||
|
menuItems.push({
|
||||||
|
name: groupName,
|
||||||
|
iconName: 'label',
|
||||||
|
action: () => this.scrollToGroup(sectionName, groupName),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WccContextmenu.show(e, menuItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
private showGroupContextMenu(e: MouseEvent, groupName: string) {
|
||||||
|
WccContextmenu.show(e, [
|
||||||
|
{
|
||||||
|
name: `Show "${groupName}"`,
|
||||||
|
iconName: 'filter_alt',
|
||||||
|
action: () => {
|
||||||
|
this.searchQuery = groupName;
|
||||||
|
this.dispatchEvent(new CustomEvent('searchChanged', { detail: this.searchQuery }));
|
||||||
|
},
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -549,7 +616,9 @@ export class WccSidebar extends DeesElement {
|
|||||||
${pinnedEntries.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'
|
||||||
|
? (this.getElementGroups(item).length > 1 ? 'library_books' : 'featured_video')
|
||||||
|
: 'insert_drive_file';
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
@@ -583,7 +652,13 @@ export class WccSidebar extends DeesElement {
|
|||||||
return this.dashboardRef.sections.map((section) => {
|
return this.dashboardRef.sections.map((section) => {
|
||||||
// Check if section has any matching items
|
// Check if section has any matching items
|
||||||
const entries = getSectionItems(section);
|
const entries = getSectionItems(section);
|
||||||
const filteredEntries = entries.filter(([name]) => this.matchesSearch(name));
|
const filteredEntries = entries.filter(([name, item]) => {
|
||||||
|
if (this.matchesSearch(name)) return true;
|
||||||
|
const rawGroups = (item as any).demoGroups;
|
||||||
|
if (!rawGroups) return false;
|
||||||
|
const groups: string[] = Array.isArray(rawGroups) ? rawGroups : [rawGroups];
|
||||||
|
return groups.some(g => this.matchesSearch(g));
|
||||||
|
});
|
||||||
|
|
||||||
// Hide section if no items match the search
|
// Hide section if no items match the search
|
||||||
if (filteredEntries.length === 0 && this.searchQuery) {
|
if (filteredEntries.length === 0 && this.searchQuery) {
|
||||||
@@ -615,7 +690,13 @@ export class WccSidebar extends DeesElement {
|
|||||||
private renderSectionItems(section: IWccSection) {
|
private renderSectionItems(section: IWccSection) {
|
||||||
const entries = getSectionItems(section);
|
const entries = getSectionItems(section);
|
||||||
// Filter entries by search query
|
// Filter entries by search query
|
||||||
const filteredEntries = entries.filter(([name]) => this.matchesSearch(name));
|
const filteredEntries = entries.filter(([name, item]) => {
|
||||||
|
if (this.matchesSearch(name)) return true;
|
||||||
|
const rawGroups = (item as any).demoGroups;
|
||||||
|
if (!rawGroups) return false;
|
||||||
|
const groups: string[] = Array.isArray(rawGroups) ? rawGroups : [rawGroups];
|
||||||
|
return groups.some(g => this.matchesSearch(g));
|
||||||
|
});
|
||||||
|
|
||||||
if (section.type === 'pages') {
|
if (section.type === 'pages') {
|
||||||
return filteredEntries.map(([pageName, item]) => {
|
return filteredEntries.map(([pageName, item]) => {
|
||||||
@@ -635,16 +716,21 @@ export class WccSidebar extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// type === 'elements' - group by demoGroup
|
// type === 'elements' - group by demoGroups (supports string | string[])
|
||||||
const groupedItems = new Map<string | null, Array<[string, any]>>();
|
const groupedItems = new Map<string | null, Array<[string, any]>>();
|
||||||
|
|
||||||
for (const entry of filteredEntries) {
|
for (const entry of filteredEntries) {
|
||||||
const [, item] = entry;
|
const [, item] = entry;
|
||||||
const group = (item as any).demoGroup || null;
|
const rawGroups = (item as any).demoGroups;
|
||||||
if (!groupedItems.has(group)) {
|
const groups: Array<string | null> = rawGroups
|
||||||
groupedItems.set(group, []);
|
? (Array.isArray(rawGroups) ? rawGroups : [rawGroups])
|
||||||
|
: [null];
|
||||||
|
for (const group of groups) {
|
||||||
|
if (!groupedItems.has(group)) {
|
||||||
|
groupedItems.set(group, []);
|
||||||
|
}
|
||||||
|
groupedItems.get(group)!.push(entry);
|
||||||
}
|
}
|
||||||
groupedItems.get(group)!.push(entry);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build a unified list of render items (ungrouped elements and groups)
|
// Build a unified list of render items (ungrouped elements and groups)
|
||||||
@@ -661,11 +747,10 @@ export class WccSidebar extends DeesElement {
|
|||||||
renderItems.push({ type: 'element', entry, sortKey: entry[0].toLowerCase() });
|
renderItems.push({ type: 'element', entry, sortKey: entry[0].toLowerCase() });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add groups (sorted by their first element's name)
|
// Add groups (sorted by group 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] || '';
|
renderItems.push({ type: 'group', groupName, items, sortKey: groupName.toLowerCase() });
|
||||||
renderItems.push({ type: 'group', groupName, items, sortKey: firstElementName.toLowerCase() });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort all items alphabetically by sortKey
|
// Sort all items alphabetically by sortKey
|
||||||
@@ -677,8 +762,11 @@ export class WccSidebar extends DeesElement {
|
|||||||
return this.renderElementItem(item.entry, section);
|
return this.renderElementItem(item.entry, section);
|
||||||
} else {
|
} else {
|
||||||
return html`
|
return html`
|
||||||
<div class="item-group">
|
<div class="item-group ${this.isGroupFilterMatch(item.groupName) ? 'group-filter-match' : ''}" data-group="${item.groupName}">
|
||||||
<span class="item-group-legend">${item.groupName}</span>
|
<span
|
||||||
|
class="item-group-legend"
|
||||||
|
@contextmenu=${(e: MouseEvent) => this.showGroupContextMenu(e, item.groupName)}
|
||||||
|
>${item.groupName}</span>
|
||||||
${item.items.map((entry) => this.renderElementItem(entry, section))}
|
${item.items.map((entry) => this.renderElementItem(entry, section))}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -733,6 +821,7 @@ export class WccSidebar extends DeesElement {
|
|||||||
`;
|
`;
|
||||||
} else {
|
} else {
|
||||||
// Single demo element
|
// Single demo element
|
||||||
|
const icon = this.getElementGroups(item).length > 1 ? 'library_books' : 'featured_video';
|
||||||
return html`
|
return html`
|
||||||
<div
|
<div
|
||||||
class="selectOption ${isSelected ? 'selected' : ''} ${isPinned ? 'pinned' : ''}"
|
class="selectOption ${isSelected ? 'selected' : ''} ${isPinned ? 'pinned' : ''}"
|
||||||
@@ -742,7 +831,7 @@ export class WccSidebar extends DeesElement {
|
|||||||
}}
|
}}
|
||||||
@contextmenu=${(e: MouseEvent) => this.showContextMenu(e, section.name, elementName)}
|
@contextmenu=${(e: MouseEvent) => this.showContextMenu(e, section.name, elementName)}
|
||||||
>
|
>
|
||||||
<i class="material-symbols-outlined">featured_video</i>
|
<i class="material-symbols-outlined">${icon}</i>
|
||||||
<div class="text">${this.highlightMatch(elementName)}</div>
|
<div class="text">${this.highlightMatch(elementName)}</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -780,11 +869,47 @@ export class WccSidebar extends DeesElement {
|
|||||||
this.dispatchEvent(new CustomEvent('searchChanged', { detail: 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());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isGroupFilterMatch(groupName: string): boolean {
|
||||||
|
return !!this.searchQuery && groupName.toLowerCase() === this.searchQuery.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
private getElementGroups(item: any): string[] {
|
||||||
|
const raw = item?.demoGroups;
|
||||||
|
if (!raw) return [];
|
||||||
|
return Array.isArray(raw) ? raw : [raw];
|
||||||
|
}
|
||||||
|
|
||||||
|
private scrollToGroup(sectionName: string, groupName: string) {
|
||||||
|
// Ensure the section is not collapsed
|
||||||
|
this.collapsedSections.delete(sectionName);
|
||||||
|
// Clear any active search so all groups are visible
|
||||||
|
this.searchQuery = '';
|
||||||
|
this.requestUpdate();
|
||||||
|
|
||||||
|
// After render, scroll to the group element
|
||||||
|
this.updateComplete.then(() => {
|
||||||
|
const groupEl = this.shadowRoot?.querySelector(
|
||||||
|
`.item-group[data-group="${groupName}"]`
|
||||||
|
);
|
||||||
|
if (groupEl) {
|
||||||
|
groupEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
// Brief highlight flash
|
||||||
|
groupEl.classList.add('group-highlight');
|
||||||
|
setTimeout(() => groupEl.classList.remove('group-highlight'), 1500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private highlightMatch(text: string): TemplateResult {
|
private highlightMatch(text: string): TemplateResult {
|
||||||
if (!this.searchQuery) return html`${text}`;
|
if (!this.searchQuery) return html`${text}`;
|
||||||
const lowerText = text.toLowerCase();
|
const lowerText = text.toLowerCase();
|
||||||
|
|||||||
Reference in New Issue
Block a user