Feature: Tree expansion state (#18227)
* implement tree expansion logic * wip test example * support complex expansion * extend entity * extend with model * Update tree-item-context.interface.ts * use expansion model to observe open state * clean up * fall back to tree context * Update default-tree.context.ts * Update default-tree.context.ts * Update default-tree.context.ts * clean up * simplify model and state * refactor to manager * remove test data * Update default-tree.context.ts * rename * add get method * rename to collapse * all collapse all method * fix collapse logic * add js docs * add tests for expansion manager * do not load children if the item is already open * Update tree-item-element-base.ts * config to expand tree root in pickers * expand tree root for duplicate to * Update tree-expansion-manager.test.ts * make methods async * use array state * add isExpanded helper * refactor to use isExpanded helper * fix type issues --------- Co-authored-by: Niels Lyngsø <niels.lyngso@gmail.com> Co-authored-by: Niels Lyngsø <nsl@umbraco.dk>
This commit is contained in:
@@ -3,6 +3,8 @@ import type { UmbTreeRepository } from '../data/tree-repository.interface.js';
|
||||
import type { UmbTreeContext } from '../tree-context.interface.js';
|
||||
import type { UmbTreeRootItemsRequestArgs } from '../data/types.js';
|
||||
import type { ManifestTree } from '../extensions/types.js';
|
||||
import { UmbTreeExpansionManager } from '../expansion-manager/index.js';
|
||||
import type { UmbTreeExpansionModel } from '../expansion-manager/types.js';
|
||||
import { UMB_TREE_CONTEXT } from './default-tree.context-token.js';
|
||||
import { type UmbActionEventContext, UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
|
||||
import { type ManifestRepository, umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
@@ -38,10 +40,14 @@ export class UmbDefaultTreeContext<
|
||||
public filter?: (item: TreeItemType) => boolean = () => true;
|
||||
public readonly selection = new UmbSelectionManager(this._host);
|
||||
public readonly pagination = new UmbPaginationManager();
|
||||
public readonly expansion = new UmbTreeExpansionManager(this._host);
|
||||
|
||||
#hideTreeRoot = new UmbBooleanState(false);
|
||||
hideTreeRoot = this.#hideTreeRoot.asObservable();
|
||||
|
||||
#expandTreeRoot = new UmbBooleanState(undefined);
|
||||
expandTreeRoot = this.#expandTreeRoot.asObservable();
|
||||
|
||||
#startNode = new UmbObjectState<UmbTreeStartNode | undefined>(undefined);
|
||||
startNode = this.#startNode.asObservable();
|
||||
|
||||
@@ -156,6 +162,10 @@ export class UmbDefaultTreeContext<
|
||||
if (data) {
|
||||
this.#treeRoot.setValue(data);
|
||||
this.pagination.setTotalItems(1);
|
||||
|
||||
if (this.getExpandTreeRoot()) {
|
||||
this.#toggleTreeRootExpansion(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -277,6 +287,56 @@ export class UmbDefaultTreeContext<
|
||||
return this.#additionalRequestArgs.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expansion state
|
||||
* @param {UmbTreeExpansionModel} data - The expansion state
|
||||
* @returns {void}
|
||||
* @memberof UmbDefaultTreeContext
|
||||
*/
|
||||
setExpansion(data: UmbTreeExpansionModel): void {
|
||||
this.expansion.setExpansion(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expansion state
|
||||
* @returns {UmbTreeExpansionModel} - The expansion state
|
||||
* @memberof UmbDefaultTreeContext
|
||||
*/
|
||||
getExpansion(): UmbTreeExpansionModel {
|
||||
return this.expansion.getExpansion();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expandTreeRoot config
|
||||
* @param {boolean} expandTreeRoot - Whether to expand the tree root
|
||||
* @memberof UmbDefaultTreeContext
|
||||
*/
|
||||
setExpandTreeRoot(expandTreeRoot: boolean) {
|
||||
this.#expandTreeRoot.setValue(expandTreeRoot);
|
||||
this.#toggleTreeRootExpansion(expandTreeRoot);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expandTreeRoot config
|
||||
* @returns {boolean | undefined} - Whether to expand the tree root
|
||||
* @memberof UmbDefaultTreeContext
|
||||
*/
|
||||
getExpandTreeRoot(): boolean | undefined {
|
||||
return this.#expandTreeRoot.getValue();
|
||||
}
|
||||
|
||||
#toggleTreeRootExpansion(expand: boolean) {
|
||||
const treeRoot = this.#treeRoot.getValue();
|
||||
if (!treeRoot) return;
|
||||
const treeRootEntity = { entityType: treeRoot.entityType, unique: treeRoot.unique };
|
||||
|
||||
if (expand) {
|
||||
this.expansion.expandItem(treeRootEntity);
|
||||
} else {
|
||||
this.expansion.collapseItem(treeRootEntity);
|
||||
}
|
||||
}
|
||||
|
||||
#resetTree() {
|
||||
this.#treeRoot.setValue(undefined);
|
||||
this.#rootItems.setValue([]);
|
||||
|
||||
@@ -5,6 +5,7 @@ import type {
|
||||
UmbTreeSelectionConfiguration,
|
||||
UmbTreeStartNode,
|
||||
} from '../types.js';
|
||||
import type { UmbTreeExpansionModel } from '../expansion-manager/types.js';
|
||||
import type { UmbDefaultTreeContext } from './default-tree.context.js';
|
||||
import { UMB_TREE_CONTEXT } from './default-tree.context-token.js';
|
||||
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
|
||||
@@ -28,6 +29,9 @@ export class UmbDefaultTreeElement extends UmbLitElement {
|
||||
@property({ type: Boolean, attribute: false })
|
||||
hideTreeRoot: boolean = false;
|
||||
|
||||
@property({ type: Boolean, attribute: false })
|
||||
expandTreeRoot: boolean = false;
|
||||
|
||||
@property({ type: Object, attribute: false })
|
||||
startNode?: UmbTreeStartNode;
|
||||
|
||||
@@ -40,6 +44,9 @@ export class UmbDefaultTreeElement extends UmbLitElement {
|
||||
@property({ attribute: false })
|
||||
filter: (item: UmbTreeItemModelBase) => boolean = () => true;
|
||||
|
||||
@property({ attribute: false })
|
||||
expansion: UmbTreeExpansionModel = [];
|
||||
|
||||
@state()
|
||||
private _rootItems: UmbTreeItemModel[] = [];
|
||||
|
||||
@@ -92,6 +99,10 @@ export class UmbDefaultTreeElement extends UmbLitElement {
|
||||
this.#treeContext!.setHideTreeRoot(this.hideTreeRoot);
|
||||
}
|
||||
|
||||
if (_changedProperties.has('expandTreeRoot')) {
|
||||
this.#treeContext!.setExpandTreeRoot(this.expandTreeRoot);
|
||||
}
|
||||
|
||||
if (_changedProperties.has('foldersOnly')) {
|
||||
this.#treeContext!.setFoldersOnly(this.foldersOnly ?? false);
|
||||
}
|
||||
@@ -103,12 +114,20 @@ export class UmbDefaultTreeElement extends UmbLitElement {
|
||||
if (_changedProperties.has('filter')) {
|
||||
this.#treeContext!.filter = this.filter;
|
||||
}
|
||||
|
||||
if (_changedProperties.has('expansion')) {
|
||||
this.#treeContext!.setExpansion(this.expansion);
|
||||
}
|
||||
}
|
||||
|
||||
getSelection() {
|
||||
return this.#treeContext?.selection.getSelection();
|
||||
}
|
||||
|
||||
getExpansion() {
|
||||
return this.#treeContext?.expansion.getExpansion();
|
||||
}
|
||||
|
||||
override render() {
|
||||
return html` ${this.#renderTreeRoot()} ${this.#renderRootItems()}`;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ export class UmbDuplicateToModalElement extends UmbModalBaseElement<UmbDuplicate
|
||||
alias=${this.data.treeAlias}
|
||||
.props=${{
|
||||
foldersOnly: this.data?.foldersOnly,
|
||||
expandTreeRoot: true,
|
||||
}}
|
||||
@selection-change=${this.#onTreeSelectionChange}></umb-tree>
|
||||
</uui-box>
|
||||
|
||||
@@ -16,6 +16,7 @@ export class UmbMoveToEntityAction extends UmbEntityActionBase<MetaEntityActionM
|
||||
data: {
|
||||
treeAlias: this.args.meta.treeAlias,
|
||||
foldersOnly: this.args.meta.foldersOnly,
|
||||
expandTreeRoot: true,
|
||||
pickableFilter: (treeItem) => treeItem.unique !== this.args.unique,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export * from './tree-expansion-manager.js';
|
||||
@@ -0,0 +1,107 @@
|
||||
import { UmbTreeExpansionManager } from './tree-expansion-manager.js';
|
||||
import { expect } from '@open-wc/testing';
|
||||
import { Observable } from '@umbraco-cms/backoffice/external/rxjs';
|
||||
import { customElement } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api';
|
||||
|
||||
@customElement('test-my-controller-host')
|
||||
class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {}
|
||||
|
||||
describe('UmbTreeExpansionManager', () => {
|
||||
let manager: UmbTreeExpansionManager;
|
||||
const item = { entityType: 'test', unique: '123' };
|
||||
const item2 = { entityType: 'test', unique: '456' };
|
||||
|
||||
beforeEach(() => {
|
||||
const hostElement = new UmbTestControllerHostElement();
|
||||
manager = new UmbTreeExpansionManager(hostElement);
|
||||
});
|
||||
|
||||
describe('Public API', () => {
|
||||
describe('properties', () => {
|
||||
it('has an expansion property', () => {
|
||||
expect(manager).to.have.property('expansion').to.be.an.instanceOf(Observable);
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
it('has an isExpanded method', () => {
|
||||
expect(manager).to.have.property('isExpanded').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a setExpansion method', () => {
|
||||
expect(manager).to.have.property('setExpansion').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a getExpansion method', () => {
|
||||
expect(manager).to.have.property('getExpansion').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a expandItem method', () => {
|
||||
expect(manager).to.have.property('expandItem').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a collapseItem method', () => {
|
||||
expect(manager).to.have.property('collapseItem').that.is.a('function');
|
||||
});
|
||||
|
||||
it('has a collapseAll method', () => {
|
||||
expect(manager).to.have.property('collapseAll').that.is.a('function');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isExpanded', () => {
|
||||
it('checks if an item is expanded', (done) => {
|
||||
manager.setExpansion([item]);
|
||||
const isExpanded = manager.isExpanded(item);
|
||||
expect(isExpanded).to.be.an.instanceOf(Observable);
|
||||
manager.isExpanded(item).subscribe((value) => {
|
||||
console.log('VALUE', value);
|
||||
expect(value).to.be.true;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setExpansion', () => {
|
||||
it('sets the expansion state', () => {
|
||||
const expansion = [item];
|
||||
manager.setExpansion(expansion);
|
||||
expect(manager.getExpansion()).to.deep.equal(expansion);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExpansion', () => {
|
||||
it('gets the expansion state', () => {
|
||||
const expansion = [item];
|
||||
manager.setExpansion(expansion);
|
||||
expect(manager.getExpansion()).to.deep.equal(expansion);
|
||||
});
|
||||
});
|
||||
|
||||
describe('expandItem', () => {
|
||||
it('expands an item', async () => {
|
||||
await manager.expandItem(item);
|
||||
expect(manager.getExpansion()).to.deep.equal([item]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collapseItem', () => {
|
||||
it('collapses an item', async () => {
|
||||
await manager.expandItem(item);
|
||||
expect(manager.getExpansion()).to.deep.equal([item]);
|
||||
manager.collapseItem(item);
|
||||
expect(manager.getExpansion()).to.deep.equal([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('collapseAll', () => {
|
||||
it('collapses all items', () => {
|
||||
manager.setExpansion([item, item2]);
|
||||
expect(manager.getExpansion()).to.deep.equal([item, item2]);
|
||||
manager.collapseAll();
|
||||
expect(manager.getExpansion()).to.deep.equal([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,81 @@
|
||||
import type { UmbTreeExpansionModel } from './types.js';
|
||||
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
|
||||
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
|
||||
import { UmbArrayState, type Observable } from '@umbraco-cms/backoffice/observable-api';
|
||||
|
||||
/**
|
||||
* Manages the expansion state of a tree
|
||||
* @exports
|
||||
* @class UmbTreeExpansionManager
|
||||
* @augments {UmbControllerBase}
|
||||
*/
|
||||
export class UmbTreeExpansionManager extends UmbControllerBase {
|
||||
#expansion = new UmbArrayState<UmbEntityModel>([], (x) => x.unique);
|
||||
expansion = this.#expansion.asObservable();
|
||||
|
||||
/**
|
||||
* Checks if an entity is expanded
|
||||
* @param {UmbEntityModel} entity The entity to check
|
||||
* @param {string} entity.entityType The entity type
|
||||
* @param {string} entity.unique The unique key
|
||||
* @returns {Observable<boolean>} True if the entity is expanded
|
||||
* @memberof UmbTreeExpansionManager
|
||||
*/
|
||||
isExpanded(entity: UmbEntityModel): Observable<boolean> {
|
||||
return this.#expansion.asObservablePart((entries) =>
|
||||
entries?.some((entry) => entry.entityType === entity.entityType && entry.unique === entity.unique),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expansion state
|
||||
* @param {UmbTreeExpansionModel | undefined} expansion The expansion state
|
||||
* @memberof UmbTreeExpansionManager
|
||||
* @returns {void}
|
||||
*/
|
||||
setExpansion(expansion: UmbTreeExpansionModel): void {
|
||||
this.#expansion.setValue(expansion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the expansion state
|
||||
* @memberof UmbTreeExpansionManager
|
||||
* @returns {UmbTreeExpansionModel} The expansion state
|
||||
*/
|
||||
getExpansion(): UmbTreeExpansionModel {
|
||||
return this.#expansion.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a child tree item
|
||||
* @param {UmbEntityModel} entity The entity to open
|
||||
* @param {string} entity.entityType The entity type
|
||||
* @param {string} entity.unique The unique key
|
||||
* @memberof UmbTreeExpansionManager
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async expandItem(entity: UmbEntityModel): Promise<void> {
|
||||
this.#expansion.appendOne(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a child tree item
|
||||
* @param {UmbEntityModel} entity The entity to close
|
||||
* @param {string} entity.entityType The entity type
|
||||
* @param {string} entity.unique The unique key
|
||||
* @memberof UmbTreeExpansionManager
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async collapseItem(entity: UmbEntityModel): Promise<void> {
|
||||
this.#expansion.filter((x) => x.entityType !== entity.entityType || x.unique !== entity.unique);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes all child tree items
|
||||
* @memberof UmbTreeExpansionManager
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public async collapseAll(): Promise<void> {
|
||||
this.#expansion.setValue([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
import type { UmbEntityModel } from '@umbraco-cms/backoffice/entity';
|
||||
|
||||
export type UmbTreeExpansionModel = Array<UmbEntityModel>;
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
import type { UmbEntityActionEvent } from '@umbraco-cms/backoffice/entity-action';
|
||||
import { UmbPaginationManager, debounce } from '@umbraco-cms/backoffice/utils';
|
||||
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
|
||||
import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity';
|
||||
|
||||
export abstract class UmbTreeItemContextBase<
|
||||
TreeItemType extends UmbTreeItemModel,
|
||||
@@ -28,7 +29,7 @@ export abstract class UmbTreeItemContextBase<
|
||||
extends UmbContextBase<UmbTreeItemContext<TreeItemType>>
|
||||
implements UmbTreeItemContext<TreeItemType>
|
||||
{
|
||||
public unique?: string | null;
|
||||
public unique?: UmbEntityUnique;
|
||||
public entityType?: string;
|
||||
public readonly pagination = new UmbPaginationManager();
|
||||
|
||||
@@ -66,6 +67,9 @@ export abstract class UmbTreeItemContextBase<
|
||||
#path = new UmbStringState('');
|
||||
readonly path = this.#path.asObservable();
|
||||
|
||||
#isOpen = new UmbBooleanState(false);
|
||||
isOpen = this.#isOpen.asObservable();
|
||||
|
||||
#foldersOnly = new UmbBooleanState(false);
|
||||
readonly foldersOnly = this.#foldersOnly.asObservable();
|
||||
|
||||
@@ -212,16 +216,57 @@ export abstract class UmbTreeItemContextBase<
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the tree item
|
||||
* @memberof UmbTreeItemContextBase
|
||||
* @returns {void}
|
||||
*/
|
||||
public select() {
|
||||
if (this.unique === undefined) throw new Error('Could not select. Unique is missing');
|
||||
this.treeContext?.selection.select(this.unique);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deselects the tree item
|
||||
* @memberof UmbTreeItemContextBase
|
||||
* @returns {void}
|
||||
*/
|
||||
public deselect() {
|
||||
if (this.unique === undefined) throw new Error('Could not deselect. Unique is missing');
|
||||
this.treeContext?.selection.deselect(this.unique);
|
||||
}
|
||||
|
||||
public showChildren() {
|
||||
const entityType = this.entityType;
|
||||
const unique = this.unique;
|
||||
|
||||
if (!entityType) {
|
||||
throw new Error('Could not show children, entity type is missing');
|
||||
}
|
||||
|
||||
if (unique === undefined) {
|
||||
throw new Error('Could not show children, unique is missing');
|
||||
}
|
||||
|
||||
// It is the tree that keeps track of the open children. We tell the tree to open this child
|
||||
this.treeContext?.expansion.expandItem({ entityType, unique });
|
||||
}
|
||||
|
||||
public hideChildren() {
|
||||
const entityType = this.entityType;
|
||||
const unique = this.unique;
|
||||
|
||||
if (!entityType) {
|
||||
throw new Error('Could not show children, entity type is missing');
|
||||
}
|
||||
|
||||
if (unique === undefined) {
|
||||
throw new Error('Could not show children, unique is missing');
|
||||
}
|
||||
|
||||
this.treeContext?.expansion.collapseItem({ entityType, unique });
|
||||
}
|
||||
|
||||
async #consumeContexts() {
|
||||
this.consumeContext(UMB_SECTION_CONTEXT, (instance) => {
|
||||
this.#sectionContext = instance;
|
||||
@@ -239,6 +284,7 @@ export abstract class UmbTreeItemContextBase<
|
||||
this.#observeIsSelectable();
|
||||
this.#observeIsSelected();
|
||||
this.#observeFoldersOnly();
|
||||
this.#observeExpansion();
|
||||
});
|
||||
|
||||
this.consumeContext(UMB_TREE_ITEM_CONTEXT, (instance) => {
|
||||
@@ -339,6 +385,25 @@ export abstract class UmbTreeItemContextBase<
|
||||
);
|
||||
}
|
||||
|
||||
#observeExpansion() {
|
||||
if (this.unique === undefined) return;
|
||||
if (!this.entityType) return;
|
||||
if (!this.treeContext) return;
|
||||
|
||||
this.observe(
|
||||
this.treeContext.expansion.isExpanded({ entityType: this.entityType, unique: this.unique }),
|
||||
(isExpanded) => {
|
||||
// If this item has children, load them
|
||||
if (isExpanded && this.#hasChildren.getValue() && this.#isOpen.getValue() === false) {
|
||||
this.loadChildren();
|
||||
}
|
||||
|
||||
this.#isOpen.setValue(isExpanded);
|
||||
},
|
||||
'observeExpansion',
|
||||
);
|
||||
}
|
||||
|
||||
#onReloadRequest = (event: UmbEntityActionEvent) => {
|
||||
if (event.getUnique() !== this.unique) return;
|
||||
if (event.getEntityType() !== this.entityType) return;
|
||||
|
||||
@@ -2,6 +2,7 @@ import type { UmbTreeItemContext } from '../index.js';
|
||||
import type { UmbTreeItemModel } from '../../types.js';
|
||||
import { html, nothing, state, ifDefined, repeat, property } from '@umbraco-cms/backoffice/external/lit';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { UUIMenuItemEvent } from '@umbraco-cms/backoffice/external/uui';
|
||||
|
||||
export abstract class UmbTreeItemElementBase<
|
||||
TreeItemModelType extends UmbTreeItemModel,
|
||||
@@ -32,6 +33,7 @@ export abstract class UmbTreeItemElementBase<
|
||||
this.observe(this.#api.childItems, (value) => (this._childItems = value));
|
||||
this.observe(this.#api.hasChildren, (value) => (this._hasChildren = value));
|
||||
this.observe(this.#api.isActive, (value) => (this._isActive = value));
|
||||
this.observe(this.#api.isOpen, (value) => (this._isOpen = value));
|
||||
this.observe(this.#api.isLoading, (value) => (this._isLoading = value));
|
||||
this.observe(this.#api.isSelectableContext, (value) => (this._isSelectableContext = value));
|
||||
this.observe(this.#api.isSelectable, (value) => (this._isSelectable = value));
|
||||
@@ -70,6 +72,9 @@ export abstract class UmbTreeItemElementBase<
|
||||
@state()
|
||||
private _hasChildren = false;
|
||||
|
||||
@state()
|
||||
private _isOpen = false;
|
||||
|
||||
@state()
|
||||
private _iconSlotHasChildren = false;
|
||||
|
||||
@@ -95,9 +100,14 @@ export abstract class UmbTreeItemElementBase<
|
||||
this.#api?.deselect();
|
||||
}
|
||||
|
||||
// TODO: do we want to catch and emit a backoffice event here?
|
||||
private _onShowChildren() {
|
||||
this.#api?.loadChildren();
|
||||
private _onShowChildren(event: UUIMenuItemEvent) {
|
||||
event.stopPropagation();
|
||||
this.#api?.showChildren();
|
||||
}
|
||||
|
||||
private _onHideChildren(event: UUIMenuItemEvent) {
|
||||
event.stopPropagation();
|
||||
this.#api?.hideChildren();
|
||||
}
|
||||
|
||||
#onLoadMoreClick = (event: any) => {
|
||||
@@ -113,6 +123,7 @@ export abstract class UmbTreeItemElementBase<
|
||||
return html`
|
||||
<uui-menu-item
|
||||
@show-children=${this._onShowChildren}
|
||||
@hide-children=${this._onHideChildren}
|
||||
@selected=${this._handleSelectedItem}
|
||||
@deselected=${this._handleDeselectedItem}
|
||||
?active=${this._isActive}
|
||||
@@ -121,6 +132,7 @@ export abstract class UmbTreeItemElementBase<
|
||||
?selected=${this._isSelected}
|
||||
.loading=${this._isLoading}
|
||||
.hasChildren=${this._hasChildren}
|
||||
.showChildren=${this._isOpen}
|
||||
.caretLabel=${this.localize.term('visuallyHiddenTexts_expandChildItems') + ' ' + label}
|
||||
label=${label}
|
||||
href="${ifDefined(this._isSelectableContext ? undefined : this._href)}">
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface UmbTreeItemContext<TreeItemType extends UmbTreeItemModel> exten
|
||||
isSelectable: Observable<boolean>;
|
||||
isSelected: Observable<boolean>;
|
||||
isActive: Observable<boolean>;
|
||||
isOpen: Observable<boolean>;
|
||||
hasActions: Observable<boolean>;
|
||||
path: Observable<string>;
|
||||
pagination: UmbPaginationManager;
|
||||
@@ -24,4 +25,7 @@ export interface UmbTreeItemContext<TreeItemType extends UmbTreeItemModel> exten
|
||||
select(): void;
|
||||
deselect(): void;
|
||||
constructPath(pathname: string, entityType: string, unique: string): string;
|
||||
loadChildren(): void;
|
||||
showChildren(): void;
|
||||
hideChildren(): void;
|
||||
}
|
||||
|
||||
@@ -182,6 +182,7 @@ export class UmbTreePickerModalElement<TreeItemType extends UmbTreeItemModelBase
|
||||
.props=${{
|
||||
hideTreeItemActions: true,
|
||||
hideTreeRoot: this.data?.hideTreeRoot,
|
||||
expandTreeRoot: this.data?.expandTreeRoot,
|
||||
selectionConfiguration: this._selectionConfiguration,
|
||||
filter: this.data?.filter,
|
||||
selectableFilter: this.data?.pickableFilter,
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface UmbTreePickerModalData<
|
||||
PathPatternParamsType extends UmbPathPatternParamsType = UmbPathPatternParamsType,
|
||||
> extends UmbPickerModalData<TreeItemType> {
|
||||
hideTreeRoot?: boolean;
|
||||
expandTreeRoot?: boolean;
|
||||
treeAlias?: string;
|
||||
// Consider if it makes sense to move this into the UmbPickerModalData interface, but for now this is a TreePicker feature. [NL]
|
||||
createAction?: UmbTreePickerModalCreateActionData<PathPatternParamsType>;
|
||||
|
||||
@@ -46,7 +46,10 @@ export class UmbDocumentDuplicateToModalElement extends UmbModalBaseElement<
|
||||
return html`
|
||||
<umb-body-layout headline="Duplicate">
|
||||
<uui-box id="tree-box" headline="Duplicate to">
|
||||
<umb-tree alias=${UMB_DOCUMENT_TREE_ALIAS} @selection-change=${this.#onTreeSelectionChange}></umb-tree>
|
||||
<umb-tree
|
||||
alias=${UMB_DOCUMENT_TREE_ALIAS}
|
||||
.props=${{ expandTreeRoot: true }}
|
||||
@selection-change=${this.#onTreeSelectionChange}></umb-tree>
|
||||
</uui-box>
|
||||
<uui-box headline="Options">
|
||||
<umb-property-layout label="Relate to original" orientation="vertical"
|
||||
|
||||
Reference in New Issue
Block a user