diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts index cc8a844b48..fb5004f572 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/dropdown/dropdown.element.ts @@ -5,15 +5,28 @@ import type { UUIPopoverContainerElement, } from '@umbraco-cms/backoffice/external/uui'; import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; import { css, html, customElement, property, query, when } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UmbClosedEvent, UmbOpenedEvent } from '@umbraco-cms/backoffice/event'; // TODO: maybe move this to UI Library. @customElement('umb-dropdown') export class UmbDropdownElement extends UmbLitElement { + #open = false; + @property({ type: Boolean, reflect: true }) - open = false; + public get open() { + return this.#open; + } + public set open(value) { + this.#open = value; + + if (value === true && this.popoverContainerElement) { + this.openDropdown(); + } else { + this.closeDropdown(); + } + } @property() label?: string; @@ -36,29 +49,12 @@ export class UmbDropdownElement extends UmbLitElement { @query('#dropdown-popover') popoverContainerElement?: UUIPopoverContainerElement; - protected override updated(_changedProperties: PropertyValueMap | Map): void { - super.updated(_changedProperties); - if (_changedProperties.has('open') && this.popoverContainerElement) { - if (this.open) { - this.openDropdown(); - } else { - this.closeDropdown(); - } - } - } - - #onToggle(event: ToggleEvent) { - // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this.open = event.newState === 'open'; - } - openDropdown() { // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.popoverContainerElement?.showPopover(); + this.#open = true; } closeDropdown() { @@ -66,6 +62,20 @@ export class UmbDropdownElement extends UmbLitElement { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore this.popoverContainerElement?.hidePopover(); + this.#open = false; + } + + #onToggle(event: ToggleEvent) { + // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.open = event.newState === 'open'; + + if (this.open) { + this.dispatchEvent(new UmbOpenedEvent()); + } else { + this.dispatchEvent(new UmbClosedEvent()); + } } override render() { @@ -81,7 +91,7 @@ export class UmbDropdownElement extends UmbLitElement { ${when( !this.hideExpand, - () => html``, + () => html``, )} @@ -97,6 +107,7 @@ export class UmbDropdownElement extends UmbLitElement { css` #dropdown-button { min-width: max-content; + height: 100%; } :host(:not([hide-expand]):not([compact])) #dropdown-button { --uui-button-padding-right-factor: 2; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts index a8d70cbac6..682ac057f9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/components/entity-actions-bundle/entity-actions-bundle.element.ts @@ -1,17 +1,7 @@ import { UmbEntityContext } from '../../entity/entity.context.js'; -import type { UmbDropdownElement } from '../dropdown/index.js'; import type { UmbEntityAction, ManifestEntityActionDefaultKind } from '@umbraco-cms/backoffice/entity-action'; import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit'; -import { - html, - nothing, - customElement, - property, - state, - ifDefined, - css, - query, -} from '@umbraco-cms/backoffice/external/lit'; +import { html, nothing, customElement, property, state, ifDefined, css } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry'; import { UmbExtensionsManifestInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api'; @@ -39,11 +29,32 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { @state() private _firstActionHref?: string; - @query('#action-modal') - private _dropdownElement?: UmbDropdownElement; - // TODO: provide the entity context on a higher level, like the root element of this entity, tree-item/workspace/... [NL] #entityContext = new UmbEntityContext(this); + #inViewport = false; + #observingEntityActions = false; + + constructor() { + super(); + + // Only observe entity actions when the element is in the viewport + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + this.#inViewport = true; + this.#observeEntityActions(); + } + }); + }, + { + root: null, // Use the viewport as the root + threshold: 0.1, // Trigger when at least 10% of the element is visible + }, + ); + + observer.observe(this); + } protected override updated(_changedProperties: PropertyValueMap | Map): void { if (_changedProperties.has('entityType') && _changedProperties.has('unique')) { @@ -54,6 +65,11 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { } #observeEntityActions() { + if (!this.entityType) return; + if (this.unique === undefined) return; + if (!this.#inViewport) return; // Only observe if the element is in the viewport + if (this.#observingEntityActions) return; + new UmbExtensionsManifestInitializer( this, umbExtensionsRegistry, @@ -67,6 +83,8 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { }, 'umbEntityActionsObserver', ); + + this.#observingEntityActions = true; } async #createFirstActionApi() { @@ -90,14 +108,6 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { await this._firstActionApi?.execute().catch(() => {}); } - #onActionExecuted() { - this._dropdownElement?.closeDropdown(); - } - - #onDropdownClick(event: Event) { - event.stopPropagation(); - } - override render() { if (this._numberOfActions === 0) return nothing; return html`${this.#renderMore()} ${this.#renderFirstAction()} `; @@ -107,16 +117,9 @@ export class UmbEntityActionsBundleElement extends UmbLitElement { if (this._numberOfActions === 1) return nothing; return html` - - - - - - + + + `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-dropdown/entity-actions-dropdown.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-dropdown/entity-actions-dropdown.element.ts new file mode 100644 index 0000000000..df3074634e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/entity-actions-dropdown/entity-actions-dropdown.element.ts @@ -0,0 +1,93 @@ +import type { UmbDropdownElement } from '../../../components/dropdown/index.js'; +import { UmbEntityActionListElement } from '../../entity-action-list.element.js'; +import { html, customElement, property, css, query } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; +import { UUIScrollContainerElement } from '@umbraco-cms/backoffice/external/uui'; +import { UMB_ENTITY_CONTEXT, type UmbEntityModel } from '@umbraco-cms/backoffice/entity'; +import { observeMultiple } from '@umbraco-cms/backoffice/observable-api'; + +@customElement('umb-entity-actions-dropdown') +export class UmbEntityActionsDropdownElement extends UmbLitElement { + @property({ type: Boolean }) + compact = false; + + @property({ type: String }) + public label?: string; + + @query('#action-modal') + private _dropdownElement?: UmbDropdownElement; + + #scrollContainerElement?: UUIScrollContainerElement; + #entityActionListElement?: UmbEntityActionListElement; + #entityType?: UmbEntityModel['entityType']; + #unique?: UmbEntityModel['unique']; + + constructor() { + super(); + this.consumeContext(UMB_ENTITY_CONTEXT, (context) => { + if (!context) return; + + this.observe(observeMultiple([context.entityType, context.unique]), ([entityType, unique]) => { + this.#entityType = entityType; + this.#unique = unique; + + if (this.#entityActionListElement) { + this.#entityActionListElement.entityType = entityType; + this.#entityActionListElement.unique = unique; + } + }); + }); + } + + #onActionExecuted() { + this._dropdownElement?.closeDropdown(); + } + + #onDropdownClick(event: Event) { + event.stopPropagation(); + } + + #onDropdownOpened() { + if (this.#scrollContainerElement) { + return; // Already created + } + + // First create dropdown content when the dropdown is opened. + // Programmatically create the elements so they are cached if the dropdown is opened again + this.#scrollContainerElement = new UUIScrollContainerElement(); + this.#entityActionListElement = new UmbEntityActionListElement(); + this.#entityActionListElement.addEventListener('action-executed', this.#onActionExecuted); + this.#entityActionListElement.entityType = this.#entityType; + this.#entityActionListElement.unique = this.#unique; + this.#entityActionListElement.setAttribute('label', this.label ?? ''); + this.#scrollContainerElement.appendChild(this.#entityActionListElement); + this._dropdownElement?.appendChild(this.#scrollContainerElement); + } + + override render() { + return html` + + + `; + } + + static override styles = [ + css` + uui-scroll-container { + max-height: 700px; + } + `, + ]; +} + +declare global { + interface HTMLElementTagNameMap { + 'umb-entity-actions-dropdown': UmbEntityActionsDropdownElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/index.ts index 841ca85af1..493801d33f 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/entity-action/global-components/index.ts @@ -1 +1,2 @@ +import './entity-actions-dropdown/entity-actions-dropdown.element.js'; import './entity-actions-table-column-view/entity-actions-table-column-view.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/event/closed.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/closed.event.ts new file mode 100644 index 0000000000..4b6fe30e33 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/event/closed.event.ts @@ -0,0 +1,8 @@ +export class UmbClosedEvent extends Event { + public static readonly TYPE = 'closed'; + + public constructor() { + // mimics the native toggle event + super(UmbClosedEvent.TYPE, { bubbles: false, composed: false, cancelable: false }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts index 7e986dc488..6d9a131fba 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/event/index.ts @@ -1,8 +1,10 @@ export * from './action-executed.event.js'; export * from './change.event.js'; +export * from './closed.event.js'; export * from './delete.event.js'; export * from './deselected.event.js'; export * from './input.event.js'; +export * from './opened.event.js'; export * from './progress.event.js'; export * from './selected.event.js'; export * from './selection-change.event.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/event/opened.event.ts b/src/Umbraco.Web.UI.Client/src/packages/core/event/opened.event.ts new file mode 100644 index 0000000000..88e6935bc5 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/core/event/opened.event.ts @@ -0,0 +1,8 @@ +export class UmbOpenedEvent extends Event { + public static readonly TYPE = 'opened'; + + public constructor() { + // mimics the native toggle event + super(UmbOpenedEvent.TYPE, { bubbles: false, composed: false, cancelable: false }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts index eb38a8fa53..06252f0472 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-entity-action-menu/workspace-entity-action-menu.element.ts @@ -1,10 +1,8 @@ import { UMB_ENTITY_WORKSPACE_CONTEXT } from '../../contexts/index.js'; -import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; -import { css, html, customElement, state, nothing, query } from '@umbraco-cms/backoffice/external/lit'; -import type { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event'; +import { html, customElement, state, nothing, css } from '@umbraco-cms/backoffice/external/lit'; import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'; -import type { UUIPopoverContainerElement } from '@umbraco-cms/backoffice/external/uui'; import type { UmbEntityUnique } from '@umbraco-cms/backoffice/entity'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; @customElement('umb-workspace-entity-action-menu') export class UmbWorkspaceEntityActionMenuElement extends UmbLitElement { private _workspaceContext?: typeof UMB_ENTITY_WORKSPACE_CONTEXT.TYPE; @@ -15,12 +13,6 @@ export class UmbWorkspaceEntityActionMenuElement extends UmbLitElement { @state() private _entityType?: string; - @state() - private _popoverOpen = false; - - @query('#workspace-entity-action-menu-popover') - private _popover?: UUIPopoverContainerElement; - constructor() { super(); @@ -35,48 +27,16 @@ export class UmbWorkspaceEntityActionMenuElement extends UmbLitElement { }); } - #onActionExecuted(event: UmbActionExecutedEvent) { - event.stopPropagation(); - - // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this._popover?.hidePopover(); - } - - #onPopoverToggle(event: ToggleEvent) { - // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this._popoverOpen = event.newState === 'open'; - } - override render() { - return this._unique !== undefined && this._entityType - ? html` - - - - - - - - - - - - ` - : nothing; + if (!this._entityType) return nothing; + if (this._unique === undefined) return nothing; + + return html` + + `; } static override styles = [ @@ -86,7 +46,8 @@ export class UmbWorkspaceEntityActionMenuElement extends UmbLitElement { height: 100%; margin-left: calc(var(--uui-size-layout-1) * -1); } - :host > uui-button { + + umb-entity-actions-dropdown { height: 100%; } `, diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts index 660c27db90..f8cc6fc297 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/collection/views/table/column-layouts/document-table-column-name.element.ts @@ -34,7 +34,9 @@ export class UmbDocumentTableColumnNameElement extends UmbLitElement implements override render() { if (!this.value) return nothing; - return html` `; + if (!this.value.editPath) return nothing; + if (!this._name) return nothing; + return html``; } static override styles = [ diff --git a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json index f2cabf43c3..9c0cd687ff 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package-lock.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package-lock.json @@ -8,7 +8,7 @@ "hasInstallScript": true, "dependencies": { "@umbraco/json-models-builders": "^2.0.36", - "@umbraco/playwright-testhelpers": "^16.0.25", + "@umbraco/playwright-testhelpers": "^16.0.27", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" @@ -66,9 +66,10 @@ } }, "node_modules/@umbraco/playwright-testhelpers": { - "version": "16.0.25", - "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.25.tgz", - "integrity": "sha512-IvRkkrTIxlXbg2dw0RhAUgkb7KSBJCyktK6zJynOORgZ5RXRae19hqKk7yEu2EwJpTstl6m9AzoVf1x4b94x5w==", + "version": "16.0.27", + "resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-16.0.27.tgz", + "integrity": "sha512-KxjIpfFsiK5b1Au8QrlWceK88eo53VxogLs0LMrxsRS3rt4rdmD1YRP6U+yIucdPKnhVgfIsh40J/taGAZyPFQ==", + "license": "MIT", "dependencies": { "@umbraco/json-models-builders": "2.0.36", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/package.json b/tests/Umbraco.Tests.AcceptanceTest/package.json index 78c09e9719..b2e9ac7ada 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/package.json +++ b/tests/Umbraco.Tests.AcceptanceTest/package.json @@ -21,7 +21,7 @@ }, "dependencies": { "@umbraco/json-models-builders": "^2.0.36", - "@umbraco/playwright-testhelpers": "^16.0.25", + "@umbraco/playwright-testhelpers": "^16.0.27", "camelize": "^1.0.0", "dotenv": "^16.3.1", "node-fetch": "^2.6.7" diff --git a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts index 72a5936b0c..c4c317d3e2 100644 --- a/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts +++ b/tests/Umbraco.Tests.AcceptanceTest/tests/DefaultConfig/Settings/DocumentType/DocumentTypeDesignTab.spec.ts @@ -213,6 +213,7 @@ test('can create a document type with a composition', {tag: '@smoke'}, async ({u // Act await umbracoUi.documentType.goToDocumentType(documentTypeName); + await umbracoUi.waitForTimeout(1000); await umbracoUi.documentType.clickCompositionsButton(); await umbracoUi.documentType.clickModalMenuItemWithName(compositionDocumentTypeName); await umbracoUi.documentType.clickSubmitButton();