From 0659fff00e7e418bc991978cb5eaf94f5c587de6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 28 Nov 2023 20:18:50 +0100 Subject: [PATCH 01/13] direct usage of selection manager instead of proxy methods --- .../collection/collection-default.context.ts | 2 +- .../section-picker-modal.element.ts | 5 +- .../tree-item-base/tree-item-base.context.ts | 8 +-- .../src/packages/core/tree/tree.context.ts | 57 +------------------ .../src/packages/core/tree/tree.element.ts | 12 ++-- .../language-picker-modal.element.ts | 2 +- .../user-group-picker-modal.element.ts | 2 +- .../user-picker/user-picker-modal.element.ts | 2 +- .../src/shared/utils/selection-manager.ts | 36 +++++++++++- 9 files changed, 55 insertions(+), 71 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-default.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-default.context.ts index 008922dd60..e6838f1f86 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-default.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/collection/collection-default.context.ts @@ -53,7 +53,7 @@ export class UmbDefaultCollectionContext = []; - #selectionManager = new UmbSelectionManager(); + #selectionManager = new UmbSelectionManager(this); #submit() { this.modalContext?.submit({ @@ -38,7 +38,8 @@ export class UmbSectionPickerModalElement extends UmbModalBaseElement< this.observe( umbExtensionsRegistry.extensionsOfType('section'), (sections: Array) => (this._sections = sections), - ), 'umbSectionsObserver'; + ), + 'umbSectionsObserver'; } render() { diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-base/tree-item-base.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-base/tree-item-base.context.ts index 7fb269d591..5bb3b483e1 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-base/tree-item-base.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-base/tree-item-base.context.ts @@ -105,12 +105,12 @@ export class UmbTreeItemContextBase public select() { if (this.unique === undefined) throw new Error('Could not select, unique key is missing'); - this.treeContext?.select(this.unique); + this.treeContext?.selection.select(this.unique); } public deselect() { if (this.unique === undefined) throw new Error('Could not deselect, unique key is missing'); - this.treeContext?.deselect(this.unique); + this.treeContext?.selection.deselect(this.unique); } #consumeContexts() { @@ -138,7 +138,7 @@ export class UmbTreeItemContextBase #observeIsSelectable() { if (!this.treeContext) return; this.observe( - this.treeContext.selectable, + this.treeContext.selection.selectable, (value) => { this.#isSelectableContext.next(value); @@ -156,7 +156,7 @@ export class UmbTreeItemContextBase if (!this.treeContext || !this.unique) return; this.observe( - this.treeContext.selection.pipe(map((selection) => selection.includes(this.unique!))), + this.treeContext.selection.selection.pipe(map((selection) => selection.includes(this.unique!))), (isSelected) => { this.#isSelected.next(isSelected); }, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts index ddd3c0af26..ddfc37f131 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts @@ -13,20 +13,10 @@ import { type UmbControllerHostElement } from '@umbraco-cms/backoffice/controlle import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; import { ProblemDetails } from '@umbraco-cms/backoffice/backend-api'; import { UmbSelectionManager } from '@umbraco-cms/backoffice/utils'; -import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; // TODO: update interface export interface UmbTreeContext extends UmbBaseController { - readonly selectable: Observable; - readonly selection: Observable>; - setSelectable(value: boolean): void; - getSelectable(): boolean; - setMultiple(value: boolean): void; - getMultiple(): boolean; - setSelection(value: Array): void; - getSelection(): Array; - select(unique: string | null): void; - deselect(unique: string | null): void; + selection: UmbSelectionManager; requestChildrenOf: (parentUnique: string | null) => Promise<{ data?: UmbPagedData; error?: ProblemDetails; @@ -38,17 +28,11 @@ export class UmbTreeContextBase extends UmbBaseController implements UmbTreeContext { - #selectionManager = new UmbSelectionManager(); - - #selectable = new UmbBooleanState(false); - public readonly selectable = this.#selectable.asObservable(); - - public readonly multiple = this.#selectionManager.multiple; - public readonly selection = this.#selectionManager.selection; - public repository?: UmbTreeRepository; public selectableFilter?: (item: TreeItemType) => boolean = () => true; + public readonly selection = new UmbSelectionManager(this._host); + #treeAlias?: string; #initResolver?: () => void; @@ -82,41 +66,6 @@ export class UmbTreeContextBase return this.#treeAlias; } - public setSelectable(value: boolean) { - this.#selectable.next(value); - } - - public getSelectable() { - return this.#selectable.getValue(); - } - - public setMultiple(value: boolean) { - this.#selectionManager.setMultiple(value); - } - - public getMultiple() { - return this.#selectionManager.getMultiple(); - } - - public setSelection(value: Array) { - this.#selectionManager.setSelection(value); - } - - public getSelection() { - return this.#selectionManager.getSelection(); - } - - public select(unique: string | null) { - if (!this.getSelectable()) return; - this.#selectionManager.select(unique); - this._host.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); - } - - public deselect(unique: string | null) { - this.#selectionManager.deselect(unique); - this._host.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); - } - public async requestTreeRoot() { await this.#init; return this.repository!.requestTreeRoot(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts index 72a2a8194e..8d78adf200 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.element.ts @@ -19,27 +19,27 @@ export class UmbTreeElement extends UmbLitElement { @property({ type: Boolean, reflect: true }) get selectable() { - return this.#treeContext.getSelectable(); + return this.#treeContext.selection.getSelectable(); } set selectable(newVal) { - this.#treeContext.setSelectable(newVal); + this.#treeContext.selection.setSelectable(newVal); } @property({ type: Array }) get selection() { - return this.#treeContext.getSelection(); + return this.#treeContext.selection.getSelection(); } set selection(newVal) { if (!Array.isArray(newVal)) return; - this.#treeContext?.setSelection(newVal); + this.#treeContext?.selection.setSelection(newVal); } @property({ type: Boolean, reflect: true }) get multiple() { - return this.#treeContext.getMultiple(); + return this.#treeContext.selection.getMultiple(); } set multiple(newVal) { - this.#treeContext.setMultiple(newVal); + this.#treeContext.selection.setMultiple(newVal); } // TODO: what is the best name for this functionality? diff --git a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/modals/language-picker/language-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/modals/language-picker/language-picker-modal.element.ts index a83e66e5eb..66aa223c50 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/settings/languages/modals/language-picker/language-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/settings/languages/modals/language-picker/language-picker-modal.element.ts @@ -18,7 +18,7 @@ export class UmbLanguagePickerModalElement extends UmbModalBaseElement< private _languages: Array = []; #languageRepository = new UmbLanguageRepository(this); - #selectionManager = new UmbSelectionManager(); + #selectionManager = new UmbSelectionManager(this); connectedCallback(): void { super.connectedCallback(); diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/modals/user-group-picker/user-group-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/modals/user-group-picker/user-group-picker-modal.element.ts index 88af248707..82bc18b6be 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user-group/modals/user-group-picker/user-group-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user-group/modals/user-group-picker/user-group-picker-modal.element.ts @@ -12,7 +12,7 @@ export class UmbUserGroupPickerModalElement extends UmbModalBaseElement = []; - #selectionManager = new UmbSelectionManager(); + #selectionManager = new UmbSelectionManager(this); #userGroupCollectionRepository = new UmbUserGroupCollectionRepository(this); connectedCallback(): void { diff --git a/src/Umbraco.Web.UI.Client/src/packages/user/user/modals/user-picker/user-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/user/user/modals/user-picker/user-picker-modal.element.ts index 134b025628..a945307a83 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/user/user/modals/user-picker/user-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/user/user/modals/user-picker/user-picker-modal.element.ts @@ -10,7 +10,7 @@ export class UmbUserPickerModalElement extends UmbModalBaseElement = []; - #selectionManager = new UmbSelectionManager(); + #selectionManager = new UmbSelectionManager(this); #userCollectionRepository = new UmbUserCollectionRepository(this); connectedCallback(): void { diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts index b8d73d63e9..740da02d2c 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts @@ -1,3 +1,6 @@ +import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; +import { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbSelectionChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; /** @@ -5,13 +8,38 @@ import { UmbArrayState, UmbBooleanState } from '@umbraco-cms/backoffice/observab * @export * @class UmbSelectionManager */ -export class UmbSelectionManager { +export class UmbSelectionManager extends UmbBaseController { + #selectable = new UmbBooleanState(false); + public readonly selectable = this.#selectable.asObservable(); + #selection = new UmbArrayState(>[], (x) => x); public readonly selection = this.#selection.asObservable(); #multiple = new UmbBooleanState(false); public readonly multiple = this.#multiple.asObservable(); + constructor(host: UmbControllerHost) { + super(host); + } + + /** + * Returns whether items can be selected. + * @return {*} + * @memberof UmbSelectionManager + */ + public getSelectable() { + return this.#selectable.getValue(); + } + + /** + * Sets whether items can be selected. + * @param {boolean} value + * @memberof UmbSelectionManager + */ + public setSelectable(value: boolean) { + this.#selectable.next(value); + } + /** * Returns the current selection. * @return {*} @@ -27,6 +55,7 @@ export class UmbSelectionManager { * @memberof UmbSelectionManager */ public setSelection(value: Array) { + if (this.getSelectable() === false) return; if (value === undefined) throw new Error('Value cannot be undefined'); this.#selection.next(value); } @@ -55,6 +84,7 @@ export class UmbSelectionManager { * @memberof UmbSelectionManager */ public toggleSelect(unique: string | null) { + if (this.getSelectable() === false) return; this.isSelected(unique) ? this.deselect(unique) : this.select(unique); } @@ -64,8 +94,10 @@ export class UmbSelectionManager { * @memberof UmbSelectionManager */ public select(unique: string | null) { + if (this.getSelectable() === false) return; const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique]; this.#selection.next(newSelection); + this._host.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); } /** @@ -74,8 +106,10 @@ export class UmbSelectionManager { * @memberof UmbSelectionManager */ public deselect(unique: string | null) { + if (this.getSelectable() === false) return; const newSelection = this.getSelection().filter((x) => x !== unique); this.#selection.next(newSelection); + this._host.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); } /** From cbf37c0ef5b4bdb972a5120b536e443471d291d6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 28 Nov 2023 20:59:43 +0100 Subject: [PATCH 02/13] add tests --- .../shared/utils/selection.manager.test.ts | 220 ++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts new file mode 100644 index 0000000000..64e931a9e2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts @@ -0,0 +1,220 @@ +import { expect } from '@open-wc/testing'; +import { UmbSelectionManager } from './selection-manager.js'; +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') +export class UmbTestControllerHostElement extends UmbControllerHostElementMixin(HTMLElement) {} + +describe('UmbSelectionManager', () => { + let manager: UmbSelectionManager; + + beforeEach(() => { + const hostElement = new UmbTestControllerHostElement(); + manager = new UmbSelectionManager(hostElement); + manager.setSelectable(true); + manager.setMultiple(true); + }); + + describe('Public API', () => { + describe('properties', () => { + it('has a selectable property', () => { + expect(manager).to.have.property('selectable').to.be.an.instanceOf(Observable); + }); + + it('has a selection property', () => { + expect(manager).to.have.property('selection').to.be.an.instanceOf(Observable); + }); + + it('has a multiple property', () => { + expect(manager).to.have.property('multiple').to.be.an.instanceOf(Observable); + }); + }); + + describe('methods', () => { + it('has a getSelectable method', () => { + expect(manager).to.have.property('getSelectable').that.is.a('function'); + }); + + it('has a setSelectable method', () => { + expect(manager).to.have.property('setSelectable').that.is.a('function'); + }); + + it('has a getSelection method', () => { + expect(manager).to.have.property('getSelection').that.is.a('function'); + }); + + it('has a setSelection method', () => { + expect(manager).to.have.property('setSelection').that.is.a('function'); + }); + + it('has a getMultiple method', () => { + expect(manager).to.have.property('getMultiple').that.is.a('function'); + }); + + it('has a setMultiple method', () => { + expect(manager).to.have.property('setMultiple').that.is.a('function'); + }); + + it('has a toggleSelect method', () => { + expect(manager).to.have.property('toggleSelect').that.is.a('function'); + }); + + it('has a select method', () => { + expect(manager).to.have.property('select').that.is.a('function'); + }); + + it('has a deselect method', () => { + expect(manager).to.have.property('deselect').that.is.a('function'); + }); + + it('has a isSelected method', () => { + expect(manager).to.have.property('isSelected').that.is.a('function'); + }); + + it('has a clearSelection method', () => { + expect(manager).to.have.property('clearSelection').that.is.a('function'); + }); + }); + }); + + describe('Selectable', () => { + it('sets and gets the selectable value', () => { + manager.setSelectable(false); + expect(manager.getSelectable()).to.equal(false); + }); + + it('updates the observable', (done) => { + manager.setSelectable(false); + + manager.selectable + .subscribe((value) => { + expect(value).to.equal(false); + done(); + }) + .unsubscribe(); + }); + }); + + describe('Selection', () => { + it('sets and gets the selection value', () => { + manager.setSelection(['1', '2']); + expect(manager.getSelection()).to.deep.equal(['1', '2']); + }); + + it('updates the observable', (done) => { + manager.setSelection(['1', '2']); + + manager.selection + .subscribe((value) => { + expect(value).to.deep.equal(['1', '2']); + done(); + }) + .unsubscribe(); + }); + }); + + describe('Multiple', () => { + it('sets and gets the multiple value', () => { + manager.setMultiple(true); + expect(manager.getMultiple()).to.equal(true); + }); + + it('updates the observable', (done) => { + manager.setMultiple(true); + + manager.multiple + .subscribe((value) => { + expect(value).to.equal(true); + done(); + }) + .unsubscribe(); + }); + }); + + describe('Select', () => { + it('selects an item', () => { + manager.select('3'); + expect(manager.getSelection()).to.deep.equal(['3']); + }); + + it('does nothing if the item is already selected', () => { + manager.setSelection(['1', '2']); + manager.select('2'); + expect(manager.getSelection()).to.deep.equal(['1', '2']); + }); + + it('does nothing if selection isnt supported', () => { + manager.setSelectable(false); + manager.select('3'); + expect(manager.getSelection()).to.deep.equal([]); + }); + }); + + describe('Deselect', () => { + it('deselects an item', () => { + manager.setSelection(['1', '2', '3']); + manager.deselect('2'); + expect(manager.getSelection()).to.deep.equal(['1', '3']); + }); + + it('does nothing if the item isnt selected', () => { + manager.setSelection(['1', '2']); + manager.deselect('3'); + expect(manager.getSelection()).to.deep.equal(['1', '2']); + }); + + it('does nothing if selection isnt supported', () => { + manager.setSelection(['1', '2']); + manager.setSelectable(false); + manager.deselect('2'); + expect(manager.getSelection()).to.deep.equal(['1', '2']); + }); + }); + + describe('Toggle select', () => { + it('toggle selects an item', () => { + manager.toggleSelect('1'); + manager.toggleSelect('2'); + expect(manager.getSelection()).to.deep.equal(['1', '2']); + manager.toggleSelect('1'); + expect(manager.getSelection()).to.deep.equal(['2']); + }); + + it('does nothing if selection isnt supported', () => { + manager.setSelectable(false); + manager.toggleSelect('1'); + manager.toggleSelect('2'); + expect(manager.getSelection()).to.deep.equal([]); + }); + }); + + describe('Is selected', () => { + it('returns true if the item is selected', () => { + manager.setSelection(['1', '2']); + expect(manager.isSelected('1')).to.equal(true); + }); + + it('returns false if the item isnt selected', () => { + manager.setSelection(['1', '2']); + expect(manager.isSelected('3')).to.equal(false); + }); + }); + + describe('Clear selection', () => { + it('clears the selection', () => { + manager.setSelection(['1', '2']); + expect(manager.getSelection()).to.deep.equal(['1', '2']); + manager.clearSelection(); + expect(manager.getSelection()).to.deep.equal([]); + }); + + it('does nothing if selection isnt supported', () => { + manager.setSelection(['1', '2']); + manager.setSelectable(false); + manager.clearSelection(); + expect(manager.getSelection()).to.deep.equal(['1', '2']); + }); + }); +}); From 9bb1fa7b0f16c8b2aae255cd780a2311392a68f6 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 28 Nov 2023 21:00:01 +0100 Subject: [PATCH 03/13] fix functionality based on tests --- .../src/shared/utils/selection-manager.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts index 740da02d2c..e8d88f857f 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts @@ -57,7 +57,8 @@ export class UmbSelectionManager extends UmbBaseController { public setSelection(value: Array) { if (this.getSelectable() === false) return; if (value === undefined) throw new Error('Value cannot be undefined'); - this.#selection.next(value); + const newSelection = this.getMultiple() ? value : [value[0]]; + this.#selection.next(newSelection); } /** @@ -95,6 +96,7 @@ export class UmbSelectionManager extends UmbBaseController { */ public select(unique: string | null) { if (this.getSelectable() === false) return; + if (this.isSelected(unique)) return; const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique]; this.#selection.next(newSelection); this._host.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); @@ -127,6 +129,7 @@ export class UmbSelectionManager extends UmbBaseController { * @memberof UmbSelectionManager */ public clearSelection() { + if (this.getSelectable() === false) return; this.#selection.next([]); } } From dbfdc24aab78b03047cf8a7444233ee8edb927a0 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 28 Nov 2023 21:02:29 +0100 Subject: [PATCH 04/13] add tests for single + multiple selection --- .../shared/utils/selection.manager.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts index 64e931a9e2..5c4df85b8a 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts @@ -217,4 +217,36 @@ describe('UmbSelectionManager', () => { expect(manager.getSelection()).to.deep.equal(['1', '2']); }); }); + + describe('Multi selection', () => { + it('selects multiple items', () => { + manager.select('1'); + manager.select('2'); + expect(manager.getSelection()).to.deep.equal(['1', '2']); + }); + + it('deselects multiple items', () => { + manager.setSelection(['1', '2', '3']); + manager.deselect('1'); + manager.deselect('2'); + expect(manager.getSelection()).to.deep.equal(['3']); + }); + + it('toggles multiple items', () => { + manager.toggleSelect('1'); + manager.toggleSelect('2'); + expect(manager.getSelection()).to.deep.equal(['1', '2']); + manager.toggleSelect('1'); + expect(manager.getSelection()).to.deep.equal(['2']); + }); + }); + + describe('Single selection', () => { + it('selects a single item', () => { + manager.setMultiple(false); + manager.select('1'); + manager.select('2'); + expect(manager.getSelection()).to.deep.equal(['2']); + }); + }); }); From 141147c1f79b27ba8b99af39add84022766d0ed2 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 28 Nov 2023 21:05:51 +0100 Subject: [PATCH 05/13] add functionaltity if multiple is changed mid selection --- .../src/shared/utils/selection-manager.ts | 6 ++++++ .../src/shared/utils/selection.manager.test.ts | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts index e8d88f857f..8908020648 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts @@ -77,6 +77,12 @@ export class UmbSelectionManager extends UmbBaseController { */ public setMultiple(value: boolean) { this.#multiple.next(value); + + /* If multiple is set to false, and the current selection is more than one, + then we need to set the selection to the first item. */ + if (value === false && this.getSelection().length > 1) { + this.setSelection([this.getSelection()[0]]); + } } /** diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts index 5c4df85b8a..43f27e02c0 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts @@ -248,5 +248,12 @@ describe('UmbSelectionManager', () => { manager.select('2'); expect(manager.getSelection()).to.deep.equal(['2']); }); + + it('keeps the first item if multiple is disabled mid selection', () => { + manager.select('1'); + manager.select('2'); + manager.setMultiple(false); + expect(manager.getSelection()).to.deep.equal(['1']); + }); }); }); From 5101ab70d7aba16b48f404f929ebd8123489084a Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 29 Nov 2023 08:50:22 +0100 Subject: [PATCH 06/13] move into folder --- src/Umbraco.Web.UI.Client/src/shared/utils/index.ts | 2 +- .../shared/utils/{ => selection-manager}/selection-manager.ts | 0 .../src/shared/utils/selection.manager.test.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/Umbraco.Web.UI.Client/src/shared/utils/{ => selection-manager}/selection-manager.ts (100%) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts index 7b605bb5de..0409826de7 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts @@ -8,7 +8,7 @@ export * from './pagination-manager/pagination.manager.js'; export * from './path-decode.function.js'; export * from './path-encode.function.js'; export * from './path-folder-name.function.js'; -export * from './selection-manager.js'; +export * from './selection-manager/selection-manager.js'; export * from './udi-service.js'; export * from './umbraco-path.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection-manager.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager.ts rename to src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection-manager.ts diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts index 43f27e02c0..507d2e496e 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts @@ -1,5 +1,5 @@ import { expect } from '@open-wc/testing'; -import { UmbSelectionManager } from './selection-manager.js'; +import { UmbSelectionManager } from './selection-manager/selection-manager.js'; import { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; From a32b8966826882b13160ea24097f7d9f36305fef Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 29 Nov 2023 08:50:50 +0100 Subject: [PATCH 07/13] move into folder --- .../utils/{ => selection-manager}/selection.manager.test.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Umbraco.Web.UI.Client/src/shared/utils/{ => selection-manager}/selection.manager.test.ts (100%) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.test.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/selection.manager.test.ts rename to src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.test.ts From 306f3dc074d1f4c559835a935edd3ea50fcb1a24 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 29 Nov 2023 08:51:36 +0100 Subject: [PATCH 08/13] align naming --- .../{selection-manager.ts => selection.manager.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/{selection-manager.ts => selection.manager.ts} (100%) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection-manager.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts similarity index 100% rename from src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection-manager.ts rename to src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts From ab5d97f938ea327848889f22b476e3cfa6be9301 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 29 Nov 2023 08:54:01 +0100 Subject: [PATCH 09/13] update imports --- src/Umbraco.Web.UI.Client/src/shared/utils/index.ts | 2 +- .../shared/utils/selection-manager/selection.manager.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts index 0409826de7..e6861cf114 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/index.ts @@ -8,7 +8,7 @@ export * from './pagination-manager/pagination.manager.js'; export * from './path-decode.function.js'; export * from './path-encode.function.js'; export * from './path-folder-name.function.js'; -export * from './selection-manager/selection-manager.js'; +export * from './selection-manager/selection.manager.js'; export * from './udi-service.js'; export * from './umbraco-path.function.js'; diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.test.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.test.ts index 507d2e496e..adf7318509 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.test.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.test.ts @@ -1,5 +1,5 @@ import { expect } from '@open-wc/testing'; -import { UmbSelectionManager } from './selection-manager/selection-manager.js'; +import { UmbSelectionManager } from './selection.manager.js'; import { Observable } from '@umbraco-cms/backoffice/external/rxjs'; import { customElement } from '@umbraco-cms/backoffice/external/lit'; import { UmbControllerHostElementMixin } from '@umbraco-cms/backoffice/controller-api'; From 6362551fa37a2d27e22c02a3ef874ef182eecf46 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Dec 2023 21:02:43 +0100 Subject: [PATCH 10/13] remove unused --- src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts index ddfc37f131..2c89cfe9c5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree.context.ts @@ -7,7 +7,6 @@ import { type ManifestTree, umbExtensionsRegistry, } from '@umbraco-cms/backoffice/extension-registry'; -import { UmbBooleanState } from '@umbraco-cms/backoffice/observable-api'; import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; import { type UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; import { UmbExtensionApiInitializer } from '@umbraco-cms/backoffice/extension-api'; From 5579488eea4fac876e019169178520893a14556e Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Mon, 11 Dec 2023 21:02:53 +0100 Subject: [PATCH 11/13] clean up error message --- .../core/tree/tree-item-base/tree-item-base.context.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-base/tree-item-base.context.ts b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-base/tree-item-base.context.ts index 5bb3b483e1..4017019394 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-base/tree-item-base.context.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/tree/tree-item-base/tree-item-base.context.ts @@ -104,12 +104,12 @@ export class UmbTreeItemContextBase } public select() { - if (this.unique === undefined) throw new Error('Could not select, unique key is missing'); + if (this.unique === undefined) throw new Error('Could not select. Unique is missing'); this.treeContext?.selection.select(this.unique); } public deselect() { - if (this.unique === undefined) throw new Error('Could not deselect, unique key is missing'); + if (this.unique === undefined) throw new Error('Could not deselect. Unique is missing'); this.treeContext?.selection.deselect(this.unique); } From b972b55f35427b5a6251a18c50b9d9c93dc2a8de Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 13 Dec 2023 11:05:34 +0100 Subject: [PATCH 12/13] remove redundant _host --- .../src/shared/utils/selection-manager/selection.manager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts index 8908020648..b71f1a66d6 100644 --- a/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts +++ b/src/Umbraco.Web.UI.Client/src/shared/utils/selection-manager/selection.manager.ts @@ -105,7 +105,7 @@ export class UmbSelectionManager extends UmbBaseController { if (this.isSelected(unique)) return; const newSelection = this.getMultiple() ? [...this.getSelection(), unique] : [unique]; this.#selection.next(newSelection); - this._host.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); + this.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); } /** @@ -117,7 +117,7 @@ export class UmbSelectionManager extends UmbBaseController { if (this.getSelectable() === false) return; const newSelection = this.getSelection().filter((x) => x !== unique); this.#selection.next(newSelection); - this._host.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); + this.getHostElement().dispatchEvent(new UmbSelectionChangeEvent()); } /** From 91856a8ef64d56d873b6b6eeb86863fee127dd8f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Wed, 13 Dec 2023 11:08:43 +0100 Subject: [PATCH 13/13] set value when modal is submitted --- .../common/section-picker/section-picker-modal.element.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/section-picker/section-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/section-picker/section-picker-modal.element.ts index 7658f20876..5f936a8af5 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/section-picker/section-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/modal/common/section-picker/section-picker-modal.element.ts @@ -32,6 +32,11 @@ export class UmbSectionPickerModalElement extends UmbModalBaseElement< 'umbSectionsObserver'; } + #submit() { + this.value = { selection: this.#selectionManager.getSelection() }; + this._submitModal(); + } + render() { return html` @@ -49,7 +54,7 @@ export class UmbSectionPickerModalElement extends UmbModalBaseElement<
- +
`;