diff --git a/src/packages/core/sorter/sorter.controller.test.ts b/src/packages/core/sorter/sorter.controller.test.ts new file mode 100644 index 0000000000..3595ec1376 --- /dev/null +++ b/src/packages/core/sorter/sorter.controller.test.ts @@ -0,0 +1,205 @@ +import { UmbSorterController } from './sorter.controller.js'; +import { aTimeout, expect, fixture, html } from '@open-wc/testing'; +import { customElement } from '@umbraco-cms/backoffice/external/lit'; +import { UmbLitElement } from '../lit-element/lit-element.element.js'; + +@customElement('test-my-sorter') +class UmbSorterTestElement extends UmbLitElement { + sorter = new UmbSorterController(this, { + getUniqueOfElement: (element) => { + return element.id; + }, + getUniqueOfModel: (modelEntry) => { + return modelEntry; + }, + identifier: 'Umb.SorterIdentifier.Test', + itemSelector: '.item', + containerSelector: '#container', + disabledItemSelector: '.disabled', + onChange: ({ model }) => { + this.model = model; + }, + }); + + getAllItems() { + return Array.from(this.shadowRoot!.querySelectorAll('.item')) as HTMLElement[]; + } + + getSortableItems() { + return Array.from(this.shadowRoot!.querySelectorAll('.item:not(.disabled')) as HTMLElement[]; + } + + getDisabledItems() { + return Array.from(this.shadowRoot!.querySelectorAll('.item.disabled')) as HTMLElement[]; + } + + override render() { + return html`
+
Item 1
+
Item 2
+
Item 3
+
Item 4
+
`; + } +} + +describe('UmbSorterController', () => { + let element: UmbSorterTestElement; + + beforeEach(async () => { + element = await fixture(html``); + await aTimeout(10); + }); + + it('is defined with its own instance', () => { + expect(element).to.be.instanceOf(UmbSorterTestElement); + expect(element.sorter).to.be.instanceOf(UmbSorterController); + }); + + describe('Public API', () => { + describe('methods', () => { + it('has a enable method', () => { + expect(element.sorter).to.have.property('enable').that.is.a('function'); + }); + + it('has a disable method', () => { + expect(element.sorter).to.have.property('disable').that.is.a('function'); + }); + + it('has a setModel method', () => { + expect(element.sorter).to.have.property('setModel').that.is.a('function'); + }); + + it('has a hasItem method', () => { + expect(element.sorter).to.have.property('hasItem').that.is.a('function'); + }); + + it('has a getItem method', () => { + expect(element.sorter).to.have.property('getItem').that.is.a('function'); + }); + + it('has a setupItem method', () => { + expect(element.sorter).to.have.property('setupItem').that.is.a('function'); + }); + + it('has a destroyItem method', () => { + expect(element.sorter).to.have.property('destroyItem').that.is.a('function'); + }); + + it('has a hasOtherItemsThan method', () => { + expect(element.sorter).to.have.property('hasOtherItemsThan').that.is.a('function'); + }); + + it('has a moveItemInModel method', () => { + expect(element.sorter).to.have.property('moveItemInModel').that.is.a('function'); + }); + + it('has a updateAllowIndication method', () => { + expect(element.sorter).to.have.property('updateAllowIndication').that.is.a('function'); + }); + + it('has a removeAllowIndication method', () => { + expect(element.sorter).to.have.property('removeAllowIndication').that.is.a('function'); + }); + + it('has a notifyDisallowed method', () => { + expect(element.sorter).to.have.property('notifyDisallowed').that.is.a('function'); + }); + + it('has a notifyRequestDrop method', () => { + expect(element.sorter).to.have.property('notifyRequestDrop').that.is.a('function'); + }); + + it('has a destroy method', () => { + expect(element.sorter).to.have.property('destroy').that.is.a('function'); + }); + }); + }); + + describe('Init', () => { + it('should find all items', () => { + const items = element.getAllItems(); + expect(items.length).to.equal(4); + }); + + it('sets all allowed draggable items to draggable', () => { + const items = element.getSortableItems(); + expect(items.length).to.equal(3); + items.forEach((item) => { + expect(item.draggable).to.be.true; + }); + }); + + it('sets all disabled items non draggable', () => { + const items = element.getDisabledItems(); + expect(items.length).to.equal(1); + items.forEach((item) => { + expect(item.draggable).to.be.false; + }); + }); + }); + + describe('disable', () => { + it('sets all items to non draggable', () => { + element.sorter.disable(); + const items = element.getAllItems(); + items.forEach((item) => { + expect(item.draggable).to.be.false; + }); + }); + }); + + describe('enable', () => { + it('sets all allowed items to draggable', () => { + const items = element.getSortableItems(); + expect(items.length).to.equal(3); + items.forEach((item) => { + expect(item.draggable).to.be.true; + }); + }); + + it('sets all disabled items non draggable', () => { + const items = element.getDisabledItems(); + expect(items.length).to.equal(1); + items.forEach((item) => { + expect(item.draggable).to.be.false; + }); + }); + }); + + describe('setModel & getModel', () => { + it('it sets the model', () => { + const model = ['1', '2', '3', '4']; + element.sorter.setModel(model); + expect(element.sorter.getModel()).to.deep.equal(model); + }); + }); + + describe('hasItem', () => { + beforeEach(() => { + element.sorter.setModel(['1', '2', '3', '4']); + }); + + it('returns true if item exists', () => { + expect(element.sorter.hasItem('1')).to.be.true; + }); + + it('returns false if item does not exist', () => { + expect(element.sorter.hasItem('5')).to.be.false; + }); + }); + + describe('getItem', () => { + beforeEach(() => { + element.sorter.setModel(['1', '2', '3', '4']); + }); + + it('returns the item if it exists', () => { + expect(element.sorter.getItem('1')).to.equal('1'); + }); + + it('returns undefined if item does not exist', () => { + expect(element.sorter.getItem('5')).to.be.undefined; + }); + }); +}); diff --git a/src/packages/core/sorter/sorter.controller.ts b/src/packages/core/sorter/sorter.controller.ts index 7afe497382..3fb39166e1 100644 --- a/src/packages/core/sorter/sorter.controller.ts +++ b/src/packages/core/sorter/sorter.controller.ts @@ -260,6 +260,8 @@ export class UmbSorterController(); + public get identifier() { return this.#config.identifier; } @@ -294,6 +296,11 @@ export class UmbSorterController} + * @memberof UmbSorterController + */ + getModel(): Array { + return this.#model; + } + hasItem(unique: UniqueType) { return this.#model.find((x) => this.#config.getUniqueOfModel(x) === unique) !== undefined; } @@ -330,12 +352,14 @@ export class UmbSorterController { const containerEl = (this.#config.containerSelector @@ -365,6 +389,7 @@ export class UmbSorterController this.destroyItem(item)); } _itemDraggedOver = (e: DragEvent) => { @@ -442,6 +469,9 @@ export class UmbSorterController x !== element); } #setupPlaceholderStyle() { diff --git a/src/packages/documents/documents/components/input-document/input-document.element.ts b/src/packages/documents/documents/components/input-document/input-document.element.ts index 6c07e8834a..4c24fa30a3 100644 --- a/src/packages/documents/documents/components/input-document/input-document.element.ts +++ b/src/packages/documents/documents/components/input-document/input-document.element.ts @@ -1,5 +1,14 @@ import { UmbDocumentPickerContext } from './input-document.context.js'; -import { classMap, css, customElement, html, property, repeat, state } from '@umbraco-cms/backoffice/external/lit'; +import { + classMap, + css, + customElement, + html, + nothing, + property, + repeat, + state, +} from '@umbraco-cms/backoffice/external/lit'; import { splitStringToArray } from '@umbraco-cms/backoffice/utils'; import { UmbChangeEvent } from '@umbraco-cms/backoffice/event'; import { UmbFormControlMixin } from '@umbraco-cms/backoffice/validation'; @@ -103,6 +112,27 @@ export class UmbInputDocumentElement extends UmbFormControlMixin 0 ? this.selection.join(',') : undefined; } + /** + * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + public get readonly() { + return this.#readonly; + } + public set readonly(value) { + this.#readonly = value; + + if (this.#readonly) { + this.#sorter.disable(); + } else { + this.#sorter.enable(); + } + } + #readonly = false; + @state() private _editDocumentPath = ''; @@ -173,7 +203,8 @@ export class UmbInputDocumentElement extends UmbFormControlMixin + label=${this.localize.term('general_choose')} + ?disabled=${this.readonly}> `; } @@ -193,11 +224,14 @@ export class UmbInputDocumentElement extends UmbFormControlMixin + ${this.#renderIcon(item)} ${this.#renderIsTrashed(item)} - ${this.#renderOpenButton(item)} - this.#onRemove(item)} label=${this.localize.term('general_remove')}> + ${this.#renderOpenButton(item)} ${this.#renderRemoveButton(item)} `; @@ -213,8 +247,16 @@ export class UmbInputDocumentElement extends UmbFormControlMixinTrashed`; } + #renderRemoveButton(item: UmbDocumentItemModel) { + if (this.readonly) return nothing; + return html` + this.#onRemove(item)} label=${this.localize.term('general_remove')}> + `; + } + #renderOpenButton(item: UmbDocumentItemModel) { - if (!this.showOpenButton) return; + if (this.readonly) return nothing; + if (!this.showOpenButton) return nothing; return html` = [ propertyEditorSchemaAlias: 'Umbraco.ContentPicker', icon: 'icon-document', group: 'pickers', + supportsReadOnly: true, settings: { properties: [ { diff --git a/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts b/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts index 2d7dfbd6f4..5b85243363 100644 --- a/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts +++ b/src/packages/documents/documents/property-editors/document-picker/property-editor-ui-document-picker.element.ts @@ -26,6 +26,15 @@ export class UmbPropertyEditorUIDocumentPickerElement extends UmbLitElement impl this._showOpenButton = config.getValueByAlias('showOpenButton') ?? false; } + /** + * Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content. + * @type {boolean} + * @attr + * @default false + */ + @property({ type: Boolean, reflect: true }) + readonly = false; + @state() private _min = 0; @@ -57,7 +66,8 @@ export class UmbPropertyEditorUIDocumentPickerElement extends UmbLitElement impl .startNode=${startNode} .value=${this.value} ?showOpenButton=${this._showOpenButton} - @change=${this.#onChange}> + @change=${this.#onChange} + ?readonly=${this.readonly}> `; }