Skip to content

Section Sidebar Menu Expansion #19810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 120 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
120 commits
Select commit Hold shift + click to select a range
7912e61
wip section menu expansion
madsrasmussen Jun 27, 2025
286587e
make section context local to each section
madsrasmussen Jun 28, 2025
652afb1
split kind manifest from element file
madsrasmussen Jun 28, 2025
335b94a
Merge branch 'main' into v16/feature/menu-expansion
madsrasmussen Jul 1, 2025
c1f10b9
make generic entity expansion manager
madsrasmussen Jul 1, 2025
fc2525e
wip menu context
madsrasmussen Jul 2, 2025
8a146e8
add collapsed and expanded events
madsrasmussen Jul 2, 2025
2c8e185
Export new expansion entity event modules
madsrasmussen Jul 2, 2025
c2f3619
rename events
madsrasmussen Jul 2, 2025
90a6db2
dispatch events
madsrasmussen Jul 2, 2025
8316f66
Set tree expansion changes in the menu context
madsrasmussen Jul 2, 2025
6990157
expand menu from workspace
madsrasmussen Jul 2, 2025
f0719d6
do not allow undefined
madsrasmussen Jul 2, 2025
e7b50fc
make menu item feature folder
madsrasmussen Jul 2, 2025
6cf35c1
Update menu-variant-tree-structure-workspace-context-base.ts
madsrasmussen Jul 2, 2025
2e1a1c5
Merge branch 'main' into v16/feature/menu-expansion
madsrasmussen Jul 29, 2025
b887808
menu: pass expansion as prop to prevent dependency on the section sid…
madsrasmussen Jul 29, 2025
9a57dc7
use correct event
madsrasmussen Jul 29, 2025
b307d8c
Add event listener support to extension slot element
madsrasmussen Jul 29, 2025
aaa59af
Add entity expansion event handling to sidebar menu
madsrasmussen Jul 29, 2025
34922d1
Optimize expansion state updates in menu components
madsrasmussen Jul 29, 2025
f21224b
only check if we have a local state already
madsrasmussen Jul 29, 2025
63f04ae
add bulk expand method
madsrasmussen Jul 29, 2025
e192f67
use bulk expand method
madsrasmussen Jul 29, 2025
a7f4e9e
align naming
madsrasmussen Jul 30, 2025
7957891
mute updates
madsrasmussen Jul 30, 2025
f566b0f
lower threshold
madsrasmussen Jul 30, 2025
4cf39f0
add expansion model with target
madsrasmussen Jul 30, 2025
3f5c665
add function to link entries
madsrasmussen Jul 30, 2025
438d6ae
fix self import
madsrasmussen Jul 30, 2025
09a76f1
export constants
madsrasmussen Jul 30, 2025
8a408d0
update js docs for entity expansion manager
madsrasmussen Jul 30, 2025
654c558
link entries
madsrasmussen Jul 30, 2025
56c21ad
fix import
madsrasmussen Jul 30, 2025
eba4138
do not export from menu here
madsrasmussen Jul 30, 2025
ecdc84a
fix import
madsrasmussen Jul 30, 2025
d7c1d04
fix import
madsrasmussen Jul 30, 2025
f9973ac
Merge branch 'main' into v16/feature/menu-expansion
madsrasmussen Jul 30, 2025
b637fcc
align how we register manifests
madsrasmussen Jul 30, 2025
fabcad7
add specific managers for section sidebar menu
madsrasmussen Jul 30, 2025
a2939eb
use structure items
madsrasmussen Jul 30, 2025
d6469cc
dot not expand current item
madsrasmussen Jul 30, 2025
f68cefc
Refactor section sidebar menu to use programmatic extension slot
madsrasmussen Jul 30, 2025
af2d4e6
add section context extension
madsrasmussen Jul 31, 2025
8ce93ab
register menu as section context instead of hardcoding
madsrasmussen Jul 31, 2025
8d02980
rename folder
madsrasmussen Jul 31, 2025
ac11898
align naming
madsrasmussen Jul 31, 2025
12723d7
export extension slot elements
madsrasmussen Jul 31, 2025
b549957
fix typings
madsrasmussen Jul 31, 2025
2266eeb
destroy extension slot element when host is disconnected
madsrasmussen Jul 31, 2025
9204675
use entry model
madsrasmussen Jul 31, 2025
020b752
move and rename
madsrasmussen Jul 31, 2025
44c55fd
register global context to hold menu state across sections
madsrasmussen Jul 31, 2025
4b55108
temp observe section specific expansions
madsrasmussen Jul 31, 2025
489dd2d
temp observe section specific expansions
madsrasmussen Jul 31, 2025
6a361ff
add method to collapse multiple items
madsrasmussen Jul 31, 2025
ce59d2e
bind expansion to section
madsrasmussen Jul 31, 2025
aa463fa
make entity expansion manager generic
madsrasmussen Jul 31, 2025
5d7660e
add helper method
madsrasmussen Jul 31, 2025
d2739b9
remove temp test data
madsrasmussen Jul 31, 2025
da56702
include last item in target
madsrasmussen Aug 4, 2025
cb0f063
Merge branch 'main' into v16/feature/menu-expansion
madsrasmussen Aug 6, 2025
2f38dee
Merge branch 'main' into v16/feature/menu-expansion
madsrasmussen Aug 6, 2025
3a9b99b
remove unused
madsrasmussen Aug 6, 2025
f412ee6
pass full entry to event
madsrasmussen Aug 6, 2025
cebd848
add type for menu item expansion
madsrasmussen Aug 6, 2025
dc8c298
export types
madsrasmussen Aug 6, 2025
0a7ec5b
add menuItem alias
madsrasmussen Aug 6, 2025
168bd20
Update types.ts
madsrasmussen Aug 6, 2025
14882b0
support menu item expansion entry
madsrasmussen Aug 6, 2025
b839174
add const for menu item alias + use for breadcrumb and menu item
madsrasmussen Aug 6, 2025
77685a5
add data type menu item alias const + apply to breadcrumb
madsrasmussen Aug 6, 2025
bde787d
move to correct manifest
madsrasmussen Aug 6, 2025
d624e26
add menu item alias to expand entries
madsrasmussen Aug 6, 2025
b318537
Update manifests.ts
madsrasmussen Aug 6, 2025
2d046ad
add menu structure kind types
madsrasmussen Aug 6, 2025
732d4e7
add kind to manifests
madsrasmussen Aug 6, 2025
99bb2b3
add menu item context
madsrasmussen Aug 6, 2025
4798686
filter menu items
madsrasmussen Aug 6, 2025
ea37741
handle menu item expansion
madsrasmussen Aug 6, 2025
1a1b9b5
clean up
madsrasmussen Aug 6, 2025
d11994b
fix order
madsrasmussen Aug 6, 2025
381cf0d
add example dashboard and entity action
madsrasmussen Aug 7, 2025
1fd858f
import types
madsrasmussen Aug 7, 2025
b4c83df
align model type names
madsrasmussen Aug 7, 2025
bb3e7ea
align naming
madsrasmussen Aug 7, 2025
90315af
use ui component
madsrasmussen Aug 7, 2025
dd49974
add guard for menu item entry
madsrasmussen Aug 7, 2025
42f40e4
use correct type
madsrasmussen Aug 7, 2025
83cf8a0
Update section-sidebar-menu.element.ts
madsrasmussen Aug 7, 2025
b849da2
Update entity-expansion.manager.ts
madsrasmussen Aug 7, 2025
7fa076a
Merge branch 'main' into v16/feature/menu-expansion
madsrasmussen Aug 7, 2025
4ffb904
export constants
madsrasmussen Aug 7, 2025
82861e8
add menuItemAlias to manifest
madsrasmussen Aug 7, 2025
0414dac
add menuItemAlias to manifest
madsrasmussen Aug 7, 2025
14d0bfd
add menuItemAlias to manifest
madsrasmussen Aug 7, 2025
08bebca
add menuItemAlias to manifest
madsrasmussen Aug 7, 2025
078d799
add menu item alias
madsrasmussen Aug 7, 2025
55fd940
add menuItemAlias to manifest
madsrasmussen Aug 7, 2025
93c1a97
add menuItemAlias to manifest
madsrasmussen Aug 7, 2025
9267ec7
add menuItemAlias to manifest
madsrasmussen Aug 7, 2025
96babd8
add alias
madsrasmussen Aug 7, 2025
54c1ecf
add kind
madsrasmussen Aug 7, 2025
5222389
fix import path
madsrasmussen Aug 7, 2025
a52c0dc
do not expand menu from modal
madsrasmussen Aug 7, 2025
d30183b
collect all menu-item files in one folder
madsrasmussen Aug 8, 2025
f34b4d4
Merge branch 'main' into v16/feature/menu-expansion
madsrasmussen Aug 8, 2025
4190933
fix lint errors
madsrasmussen Aug 8, 2025
4b64646
Merge branch 'v16/feature/menu-expansion' of https://github.com/umbra…
madsrasmussen Aug 8, 2025
6299e56
Update content-detail-workspace-base.ts
madsrasmussen Aug 8, 2025
592c246
Merge branch 'main' into v16/feature/menu-expansion
madsrasmussen Aug 8, 2025
ab2d1f1
clean up
madsrasmussen Aug 8, 2025
7086067
rename to example
madsrasmussen Aug 8, 2025
95cb2a7
add button to collapse everything within a section
madsrasmussen Aug 8, 2025
f46796f
fix breadcrumb for non-variant structure
madsrasmussen Aug 10, 2025
70dd7f3
reload entity
madsrasmussen Aug 10, 2025
98956ad
destroy
madsrasmussen Aug 10, 2025
ad35d49
remove self
madsrasmussen Aug 10, 2025
7a95310
Updated acceptance tests to check if a caret button is open before cl…
andr317c Aug 12, 2025
64617db
Bumped version of test helpers
andr317c Aug 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { createImportMap } from '../importmap/index.js';

const ILLEGAL_CORE_IMPORTS_THRESHOLD = 5;
const SELF_IMPORTS_THRESHOLD = 0;
const BIDIRECTIONAL_IMPORTS_THRESHOLD = 18;
const BIDIRECTIONAL_IMPORTS_THRESHOLD = 16;

const clientProjectRoot = path.resolve(import.meta.dirname, '../../');
const modulePrefix = '@umbraco-cms/backoffice/';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UMB_MENU_ITEM_CONTEXT } from '@umbraco-cms/backoffice/menu';

export class ExampleCollapseMenuItemEntityAction extends UmbEntityActionBase<never> {
override async execute() {
const context = await this.getContext(UMB_MENU_ITEM_CONTEXT);
context?.expansion.collapseAll();
}
}

export { ExampleCollapseMenuItemEntityAction as api };
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'entityAction',
kind: 'default',
alias: 'Umb.EntityAction.Document.MenuItem.Collapse',
name: 'Collapse Document Menu Item',
api: () => import('./collapse-menu-item.entity-action.js'),
forEntityTypes: ['document-type-root', 'media-type-root', 'member-type-root', 'data-type-root'],
weight: -10,
meta: {
label: 'Collapse Menu Item',
icon: 'icon-wand',
},
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { manifests as playgroundDashboardManifests } from './playground-dashboard/manifests.js';
import { manifests as collapseMenuItemManifests } from './collapse-menu-item-entity-action/manifests.js';

export const manifests: Array<UmbExtensionManifest> = [...playgroundDashboardManifests, ...collapseMenuItemManifests];
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'dashboard',
kind: 'default',
name: 'Example Section Sidebar Menu Playground Dashboard',
alias: 'Example.Dashboard.SectionSidebarMenuPlayground',
element: () => import('./section-sidebar-menu-playground.element.js'),
weight: 3000,
meta: {
label: 'Section Sidebar Menu Playground',
pathname: 'section-sidebar-menu-playground',
},
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { html, customElement, LitElement, css, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import {
UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT,
UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT,
type UmbSectionMenuItemExpansionEntryModel,
} from '@umbraco-cms/backoffice/menu';

@customElement('example-section-sidebar-menu-playground-dashboard')
export class ExampleSectionSidebarMenuPlaygroundDashboard extends UmbElementMixin(LitElement) {
#globalContext?: typeof UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT.TYPE;
#sectionContext?: typeof UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT.TYPE;

@state()
private _globalExpansion: Array<UmbSectionMenuItemExpansionEntryModel> = [];

@state()
private _sectionExpansion: Array<UmbSectionMenuItemExpansionEntryModel> = [];

constructor() {
super();

this.consumeContext(UMB_SECTION_SIDEBAR_MENU_GLOBAL_CONTEXT, (section) => {
this.#globalContext = section;
this.#observeGlobalExpansion();
});

this.consumeContext(UMB_SECTION_SIDEBAR_MENU_SECTION_CONTEXT, (section) => {
this.#sectionContext = section;
this.#observeSectionExpansion();
});
}

#observeGlobalExpansion() {
this.observe(this.#globalContext?.expansion.expansion, (items) => {
this._globalExpansion = items || [];
});
}

#observeSectionExpansion() {
this.observe(this.#sectionContext?.expansion.expansion, (items) => {
this._sectionExpansion = items || [];
});
}

#onCloseItem(event: PointerEvent, item: UmbSectionMenuItemExpansionEntryModel) {
event.stopPropagation();
this.#sectionContext?.expansion.collapseItem(item);
}

#onCollapseSection() {
this.#sectionContext?.expansion.collapseAll();
}

override render() {
return html` <umb-stack>
<uui-box headline="Open Items for this section">
<uui-button slot="header-actions" @click=${this.#onCollapseSection} compact>
<uui-icon name="icon-wand"></uui-icon>
Collapse All</uui-button
>
${repeat(
this._sectionExpansion,
(item) => item.entityType + item.unique,
(item) => this.#renderItem(item),
)}
</uui-box>
<uui-box headline="Open Items for all sections">
${repeat(
this._globalExpansion,
(item) => item.entityType + item.unique,
(item) => this.#renderItem(item),
)}
</uui-box>
</umb-stack>`;
}

#renderItem(item: UmbSectionMenuItemExpansionEntryModel) {
return html` <uui-ref-node
name=${item.entityType}
detail=${item.unique + ', ' + item.menuItemAlias + ', ' + item.sectionAlias}>
<uui-button slot="actions" @click=${(event: PointerEvent) => this.#onCloseItem(event, item)}>Close</uui-button>
</uui-ref-node>`;
}

static override styles = [
css`
:host {
display: block;
padding: var(--uui-size-layout-1);
}
`,
];
}

export { ExampleSectionSidebarMenuPlaygroundDashboard as element };

declare global {
interface HTMLElementTagNameMap {
'example-section-sidebar-menu-playground-dashboard': ExampleSectionSidebarMenuPlaygroundDashboard;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ export class UmbBackofficeMainElement extends UmbLitElement {
this._routes = newRoutes;
}

// TODO: v17. Remove this section context when we have api's as part of the section manifest.
private _provideSectionContext(sectionManifest: ManifestSection) {
if (!this._sectionContext) {
this._sectionContext = new UmbSectionContext(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -924,11 +924,20 @@ export abstract class UmbContentDetailWorkspaceContextBase<
if (!eventContext) {
throw new Error('Event context is missing');
}
const event = new UmbRequestReloadChildrenOfEntityEvent({

const reloadStructureEvent = new UmbRequestReloadStructureForEntityEvent({
entityType: parent.entityType,
unique: parent.unique,
});
eventContext.dispatchEvent(event);

eventContext.dispatchEvent(reloadStructureEvent);

const reloadChildrenEvent = new UmbRequestReloadChildrenOfEntityEvent({
entityType: parent.entityType,
unique: parent.unique,
});

eventContext.dispatchEvent(reloadChildrenEvent);
}

async #update(variantIds: Array<UmbVariantId>, saveData: DetailModelType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@
}
#props?: Record<string, unknown> = {};

@property({ type: Object, attribute: false })
set events(newVal: Record<string, (event: Event) => void> | undefined) {
this.#events = newVal;
if (this.#extensionsController) {
this.#addEventListenersToExtensionElement();
}
}
get events(): Record<string, (event: Event) => void> | undefined {
return this.#events;
}
#events?: Record<string, (event: Event) => void> = {};

@property({ type: String, attribute: 'default-element' })
public defaultElement?: string;

Expand All @@ -104,6 +116,7 @@
}
override disconnectedCallback(): void {
// _permitted is reset as the extensionsController fires a callback on destroy.
this.#removeEventListenersFromExtensionElement();
this.#attached = false;
this.#extensionsController?.destroy();
this.#extensionsController = undefined;
Expand All @@ -121,6 +134,7 @@
this.filter,
(extensionControllers) => {
this._permitted = extensionControllers;
this.#addEventListenersToExtensionElement();
},
undefined, // We can leave the alias undefined as we destroy this our selfs.
this.defaultElement,
Expand All @@ -144,6 +158,34 @@
return this.renderMethod ? this.renderMethod(ext, i) : ext.component;
};

#addEventListenersToExtensionElement() {
this._permitted?.forEach((initializer) => {
const component = initializer.component as HTMLElement;
if (!component) return;

const events = this.#events;
if (!events) return;

Object.entries(events).forEach(([eventName, handler]) => {
component.addEventListener(eventName, handler);
});
});
}

Check warning on line 173 in src/Umbraco.Web.UI.Client/src/packages/core/extension-registry/components/extension-slot/extension-slot.element.ts

View check run for this annotation

CodeScene Delta Analysis / CodeScene Cloud Delta Analysis (main)

❌ New issue: Code Duplication

The module contains 2 functions with similar structure: UmbExtensionSlotElement.addEventListenersToExtensionElement,UmbExtensionSlotElement.removeEventListenersFromExtensionElement. Avoid duplicated, aka copy-pasted, code inside the module. More duplication lowers the code health.

#removeEventListenersFromExtensionElement() {
this._permitted?.forEach((initializer) => {
const component = initializer.component as HTMLElement;
if (!component) return;

const events = this.#events;
if (!events) return;

Object.entries(events).forEach(([eventName, handler]) => {
component.removeEventListener(eventName, handler);
});
});
}

static override styles = css`
:host {
display: contents;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ export * from './conditions/index.js';
export * from './initializers/index.js';
export * from './registry.js';
export * from './utils/index.js';
export * from './components/index.js';

export type * from './models/types.js';
export type * from './extensions/types.js';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { MetaMenuItem } from '../../../menu-item.extension.js';
import type { MetaMenuItem } from '../menu-item.extension.js';
import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
import type { UmbAction } from '@umbraco-cms/backoffice/action';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './menu-item.context.token.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { UmbMenuItemExpansionEntryModel } from '../../types.js';

/**
* Checks if the provided object is a Menu Item Expansion Entry.
* @param {object } object - The object to check.
* @returns {boolean } True if the object is a Menu Item Expansion Entry, false otherwise.
*/
export function isMenuItemExpansionEntry(object: unknown): object is UmbMenuItemExpansionEntryModel {
return (
typeof object === 'object' &&
object !== null &&
'entityType' in object &&
'unique' in object &&
'menuItemAlias' in object
);
}
Loading
Loading