diff --git a/api/js/etemplate/Et2Link/Et2LinkEntry.ts b/api/js/etemplate/Et2Link/Et2LinkEntry.ts index 873bd01e28..6625b8ed19 100644 --- a/api/js/etemplate/Et2Link/Et2LinkEntry.ts +++ b/api/js/etemplate/Et2Link/Et2LinkEntry.ts @@ -163,7 +163,7 @@ export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitE } if(changedProperties.has("readonly")) { - this._appNode.readonly = this.readonly; + this._appNode.readonly = this._appNode.disabled = this.readonly; this._searchNode.readonly = this.readonly; } // Pass some properties on to app selection diff --git a/api/js/etemplate/Et2Link/Et2LinkTo.ts b/api/js/etemplate/Et2Link/Et2LinkTo.ts index 9064a3095b..d1b480ae8e 100644 --- a/api/js/etemplate/Et2Link/Et2LinkTo.ts +++ b/api/js/etemplate/Et2Link/Et2LinkTo.ts @@ -139,6 +139,7 @@ export class Et2LinkTo extends Et2InputWidget(ScopedElementsMixin(FormControlMix return html` diff --git a/api/js/etemplate/Et2Select/Et2Select.ts b/api/js/etemplate/Et2Select/Et2Select.ts index c0d63ec0db..08c43e2e9e 100644 --- a/api/js/etemplate/Et2Select/Et2Select.ts +++ b/api/js/etemplate/Et2Select/Et2Select.ts @@ -88,36 +88,51 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect) .select__label { display: block; text-overflow: ellipsis; - /* This is usually not used due to flex, but is the basis for ellipsis calculation */ - width: 10ex; + /* This is usually not used due to flex, but is the basis for ellipsis calculation */ + width: 10ex; } - /** multiple=true uses tags for each value **/ - /* styling for icon inside tag (not option) */ - .tag_image { + /** multiple=true uses tags for each value **/ + /* styling for icon inside tag (not option) */ + + .tag_image { margin-right: var(--sl-spacing-x-small); - } - /* Maximum height + scrollbar on tags (+ other styling) */ - .select__tags { + } + + /* Maximum height + scrollbar on tags (+ other styling) */ + + .select__tags { margin-left: 0px; max-height: initial; overflow-y: auto; gap: 0.1rem 0.5rem; - } - .select--medium .select__tags { + } + + .select--medium .select__tags { padding-top: 2px; padding-bottom: 2px; - } - :host([rows]) .select__tags { + } + + :host([rows]) .select__tags { max-height: calc(var(--rows, 5) * 1.35rem); - } - /* Keep overflow tag right-aligned. It's the only sl-tag. */ - .select__tags sl-tag { + } + + /* Keep overflow tag right-aligned. It's the only sl-tag. */ + + .select__tags sl-tag { margin-left: auto; - } - select:hover { + } + + select:hover { box-shadow: 1px 1px 1px rgb(0 0 0 / 60%); - }` + } + + /* Hide dropdown trigger when multiple & readonly */ + + :host([readonly][multiple]) .select__icon { + display: none; + } + ` ]; } diff --git a/api/js/etemplate/Et2Select/SearchMixin.ts b/api/js/etemplate/Et2Select/SearchMixin.ts index c56abbdee1..afc60a37ce 100644 --- a/api/js/etemplate/Et2Select/SearchMixin.ts +++ b/api/js/etemplate/Et2Select/SearchMixin.ts @@ -365,9 +365,14 @@ export const Et2WithSearchMixin = >(superclass this._addNodes(); } // Update any tags if edit mode changes - if(changedProperties.has("editModeEnabled")) + if(changedProperties.has("editModeEnabled") || changedProperties.has("readonly")) { - this.shadowRoot.querySelectorAll(".select__tags > *").forEach(tag => tag.editable = this.editModeEnabled); + // Required because we explicitly create tags instead of doing it in render() + this.shadowRoot.querySelectorAll(".select__tags > *").forEach((tag : Et2Tag) => + { + tag.editable = this.editModeEnabled && !this.readonly; + tag.removable = !this.readonly; + }); } } @@ -411,7 +416,7 @@ export const Et2WithSearchMixin = >(superclass protected _createTagNode(item) { let tag = document.createElement(this.tagTag); - tag.editable = this.editModeEnabled; + tag.editable = this.editModeEnabled && !this.readonly; return tag; } diff --git a/api/js/etemplate/Et2Select/test/EditableTag.test.ts b/api/js/etemplate/Et2Select/test/EditableTag.test.ts index 530340505d..5a3976d8e6 100644 --- a/api/js/etemplate/Et2Select/test/EditableTag.test.ts +++ b/api/js/etemplate/Et2Select/test/EditableTag.test.ts @@ -28,6 +28,9 @@ async function before(editable = true) sinon.stub(element, "egw").returns(window.egw); await element.updateComplete; + let tags = []; + element.shadowRoot.querySelectorAll(element.tagTag).forEach((t : Et2Tag) => tags.push(t.updateComplete)); + await Promise.all(tags); return element; } @@ -120,6 +123,21 @@ describe("Editable tag", () => assert.equal(element.value, "change select too", "Tag change did not cause value change in parent select (allowFreeEntries was on)"); }); + + it("Does not have edit button when readonly", async() => + { + element.readonly = true; + await element.updateComplete; + + let tag = element.shadowRoot.querySelectorAll(element.tagTag); + assert.isAbove(tag.length, 0, "No tags found"); + + let wait = []; + tag.forEach((t : Et2Tag) => wait.push(t.updateComplete)) + await Promise.all(wait); + + assert.isNull(tag[0].shadowRoot.querySelector("et2-button-icon[label='edit*']"), "Unexpected edit button"); + }); }); describe("Select is not editable", () => { @@ -130,6 +148,8 @@ describe("Select is not editable", () => { let tag = element.shadowRoot.querySelectorAll(element.tagTag); assert.isAbove(tag.length, 0, "No tags found"); + assert.isNull(tag[0].shadowRoot.querySelector("et2-button-icon[label='edit*']"), "Unexpected edit button"); }); + }); \ No newline at end of file