diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.element.ts index f5c1642c2a91..c5f1fbb61a5e 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.element.ts @@ -20,6 +20,7 @@ export class UmbPropertyEditorUIColorPickerElement @property({ type: Object }) public override set value(value: UmbSwatchDetails | undefined) { super.value = value ? this.#ensureHashPrefix(value) : undefined; + this.#syncLabelFromSwatches(); } public override get value(): UmbSwatchDetails | undefined { return super.value; @@ -49,6 +50,19 @@ export class UmbPropertyEditorUIColorPickerElement const swatches = config?.getValueByAlias>('items') ?? []; this._swatches = swatches.map((swatch) => this.#ensureHashPrefix(swatch)); + this.#syncLabelFromSwatches(); + } + + #syncLabelFromSwatches() { + if (!this.value || this._swatches.length === 0) return; + // Compare case-insensitively to match legacy/inconsistently-cased stored values (e.g. "#FF0000" vs "#ff0000"). + const targetValue = this.value.value.toLowerCase(); + const match = this._swatches.find((swatch) => swatch.value.toLowerCase() === targetValue); + if (match && match.label !== this.value.label) { + super.value = match; + // Dispatch outside of user interaction so the refreshed label is persisted on the next save. + this.dispatchEvent(new UmbChangeEvent()); + } } #ensureHashPrefix(swatch: UmbSwatchDetails): UmbSwatchDetails { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.test.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.test.ts index fc1bb3f1b5b2..bcd32a368847 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.test.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/color-picker/property-editor-ui-color-picker.test.ts @@ -1,6 +1,8 @@ import { UmbPropertyEditorUIColorPickerElement } from './property-editor-ui-color-picker.element.js'; import { expect, fixture, html } from '@open-wc/testing'; import { type UmbTestRunnerWindow, defaultA11yConfig } from '@umbraco-cms/internal/test-utils'; +import { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor'; +import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; describe('UmbPropertyEditorUIColorPickerElement', () => { let element: UmbPropertyEditorUIColorPickerElement; @@ -13,6 +15,65 @@ describe('UmbPropertyEditorUIColorPickerElement', () => { expect(element).to.be.instanceOf(UmbPropertyEditorUIColorPickerElement); }); + describe('label sync', () => { + const swatches = [ + { value: '#28802a', label: 'Green' }, + { value: '#ff0000', label: 'Red' }, + ]; + + const configWith = (items: Array<{ value: string; label: string }>) => + new UmbPropertyEditorConfigCollection([{ alias: 'items', value: items }]); + + it('updates value.label and emits a single change event when config label differs from stored label', async () => { + let changeCount = 0; + element.addEventListener(UmbChangeEvent.TYPE, () => changeCount++); + + element.config = configWith(swatches); + element.value = { value: '#28802a', label: 'Forest' }; + await element.updateComplete; + + expect(element.value?.label).to.equal('Green'); + expect(element.value?.value).to.equal('#28802a'); + expect(changeCount).to.equal(1); + }); + + it('refreshes a stale label even when the stored hex casing differs from the swatch', async () => { + let changeCount = 0; + element.addEventListener(UmbChangeEvent.TYPE, () => changeCount++); + + element.config = configWith(swatches); + element.value = { value: '#28802A', label: 'Forest' }; + await element.updateComplete; + + expect(element.value?.label).to.equal('Green'); + expect(changeCount).to.equal(1); + }); + + it('does not emit a change event when labels already match', async () => { + let changeCount = 0; + element.addEventListener(UmbChangeEvent.TYPE, () => changeCount++); + + element.config = configWith(swatches); + element.value = { value: '#28802a', label: 'Green' }; + await element.updateComplete; + + expect(element.value?.label).to.equal('Green'); + expect(changeCount).to.equal(0); + }); + + it('does not emit a change event when no swatch matches the stored value', async () => { + let changeCount = 0; + element.addEventListener(UmbChangeEvent.TYPE, () => changeCount++); + + element.config = configWith(swatches); + element.value = { value: '#000000', label: 'Black' }; + await element.updateComplete; + + expect(element.value?.label).to.equal('Black'); + expect(changeCount).to.equal(0); + }); + }); + if ((window as UmbTestRunnerWindow).__UMBRACO_TEST_RUN_A11Y_TEST) { it('passes the a11y audit', async () => { await expect(element).shadowDom.to.be.accessible(defaultA11yConfig);