From aa2d4f12075e3de8056c7dfdae430d675222216c Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 18 Sep 2025 17:37:21 +0200 Subject: [PATCH] Variants: Implements validation hints to the variant selector (closes #19953) (#20179) * feat: gets hints and assigns to variants to enable the view to show a badge if there is a hint * feat: find the first hint on the non-active variant * feat: protect against non-variants * feat: ignore invariant variants * feat: adds a render method for hints * chore: removes comment * only add a new hint if the weight is higher --- ...ace-split-view-variant-selector.element.ts | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts index 3e2ff19967..7cb20c7228 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-split-view/workspace-split-view-variant-selector.element.ts @@ -10,6 +10,8 @@ import { UUIInputEvent } from '@umbraco-cms/backoffice/external/uui'; import type { UmbEntityVariantModel, UmbEntityVariantOptionModel } from '@umbraco-cms/backoffice/variant'; import type { UUIInputElement, UUIPopoverContainerElement } from '@umbraco-cms/backoffice/external/uui'; import type { DocumentVariantStateModel } from '@umbraco-cms/backoffice/external/backend-api'; +import { UMB_HINT_CONTEXT } from '@umbraco-cms/backoffice/hint'; +import type { UmbHint, UmbVariantHint } from '@umbraco-cms/backoffice/hint'; @customElement('umb-workspace-split-view-variant-selector') export class UmbWorkspaceSplitViewVariantSelectorElement< @@ -96,8 +98,35 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< this.#observeDatasetContext(); this.#observeCurrentVariant(); }); + + this.consumeContext(UMB_HINT_CONTEXT, (context) => { + this.observe( + context?.descendingHints(), + (hints) => { + this._hintMap.clear(); + hints?.forEach((hint) => { + if (this.#isVariantHint(hint) && hint.variantId) { + // Add the hint if there is no existing hint for this variantId or if the existing hint has a lower weight + const existingHint = this._hintMap.get(hint.variantId.toString()); + if (!existingHint || existingHint.weight < hint.weight) { + this._hintMap.set(hint.variantId.toString(), hint); + } + } + }); + this.requestUpdate('_hintMap'); + }, + 'umbObserveHints', + ); + }); } + #isVariantHint(hint: UmbHint): hint is UmbVariantHint { + return hint && 'variantId' in hint; + } + + @state() + private _hintMap = new Map(); + async #observeVariants(workspaceContext?: UmbVariantDatasetWorkspaceContext) { this.observe( workspaceContext?.variantOptions, @@ -302,6 +331,16 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< override render() { if (!this._variantId) return nothing; + let firstHintOnInactiveVariant: UmbVariantHint | undefined; + + if (this._activeVariant) { + const hintsOrderedByWeight = Array.from(this._hintMap.values()).sort((a, b) => (b.weight || 0) - (a.weight || 0)); + firstHintOnInactiveVariant = hintsOrderedByWeight.find((hint) => { + if (!hint.variantId) return false; + return !hint.variantId.isInvariant() && hint.variantId.compare(this._activeVariant!) === false; + }); + } + return html` + ${this.#renderHintBadge(firstHintOnInactiveVariant)} ${this._activeVariants.length > 1 ? html` @@ -360,8 +400,11 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< const variantId = UmbVariantId.Create(variantOption); const notCreated = this.#isCreateMode(variantOption, variantId); const subVariantOptions = this.#getSegmentVariantOptionsForCulture(variantOption, variantId); + const hint = this._hintMap.get(variantId.toString()); + const active = this.#isVariantActive(variantId); + return html` -
+
${this._variesBySegment && this.#isCreated(variantOption) && subVariantOptions.length > 0 ? html`
${this.#renderExpandToggle(variantId)}
` : nothing} @@ -381,6 +424,7 @@ export class UmbWorkspaceSplitViewVariantSelectorElement<
${this.#getVariantSpecInfo(variantOption)}
+ ${this.#renderHintBadge(!active ? hint : undefined)} ${this.#renderSplitViewButton(variantOption)} @@ -390,6 +434,13 @@ export class UmbWorkspaceSplitViewVariantSelectorElement< `; } + #renderHintBadge(hint?: UmbVariantHint) { + if (!hint) return nothing; + return html`
+ ${hint.text} +
`; + } + #isCreated(variantOption: VariantOptionModelType) { return ( variantOption.variant?.state &&