From f75567a863c37fd2391471cae79c5d00e9c9216a Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 18 Sep 2023 16:09:43 -0600 Subject: [PATCH] Select improvements - Add _styleTemplate for easier extension - re-organize file - Fix weird tag style - some cleanup --- api/js/etemplate/Et2Select/Et2Select.ts | 241 ++++++++++-------- api/js/etemplate/Et2Select/SearchMixin.ts | 4 +- .../Et2Select/Select/Et2SelectAccount.ts | 8 - .../Et2Select/Select/Et2SelectCategory.ts | 25 +- .../Et2Select/Select/Et2SelectEmail.ts | 10 - api/js/etemplate/Et2Select/Tag/Et2Tag.ts | 3 + 6 files changed, 148 insertions(+), 143 deletions(-) diff --git a/api/js/etemplate/Et2Select/Et2Select.ts b/api/js/etemplate/Et2Select/Et2Select.ts index a7ac9e3b42..88b60207e3 100644 --- a/api/js/etemplate/Et2Select/Et2Select.ts +++ b/api/js/etemplate/Et2Select/Et2Select.ts @@ -17,7 +17,7 @@ import {RowLimitedMixin} from "../Layout/RowLimitedMixin"; import {Et2Tag} from "./Tag/Et2Tag"; import {Et2WithSearchMixin} from "./SearchMixin"; import {property} from "lit/decorators/property.js"; -import {SlChangeEvent, SlSelect} from "@shoelace-style/shoelace"; +import {SlChangeEvent, SlOption, SlSelect} from "@shoelace-style/shoelace"; import {repeat} from "lit/directives/repeat.js"; // export Et2WidgetWithSelect which is used as type in other modules @@ -556,59 +556,6 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect) } */ - _emptyLabelTemplate() : TemplateResult - { - if(!this.emptyLabel || this.multiple) - { - return html``; - } - return html` - v == "")} - > - ${this.emptyLabel} - `; - } - - protected _optionsTemplate() : TemplateResult - { - return html`${repeat(this.select_options - // Filter out empty values if we have empty label to avoid duplicates - .filter(o => this.emptyLabel ? o.value !== '' : o), this._groupTemplate.bind(this)) - }`; - } - - /** - * Used to render each option into the select - * - * @param {SelectOption} option - * @returns {TemplateResult} - */ - protected _optionTemplate(option : SelectOption) : TemplateResult - { - // Exclude non-matches when searching - if(typeof option.isMatch == "boolean" && !option.isMatch) - { - return html``; - } - - // Tag used must match this.optionTag, but you can't use the variable directly. - // Pass option along so SearchMixin can grab it if needed - const value = (option.value).replaceAll(" ", "___"); - return html` - v == value)} - ?disabled=${option.disabled} - > - ${this._iconTemplate(option)} - ${this.noLang ? option.label : this.egw().lang(option.label)} - `; - } - /** * Tag used for rendering tags when multiple=true * Used for creating, finding & filtering options. @@ -620,54 +567,6 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect) return literal`et2-tag`; } - /** - * Custom tag - * @param {Et2Option} option - * @param {number} index - * @returns {TemplateResult} - * @protected - */ - protected _tagTemplate(option : Et2Option, index : number) : TemplateResult - { - const readonly = (this.readonly || option && typeof (option.disabled) != "undefined" && option.disabled); - const isEditable = this.editModeEnabled && !readonly; - const image = this._createImage(option); - const tagName = this.tagTag; - return html` - <${tagName} - part="tag" - exportparts=" - base:tag__base, - content:tag__content, - remove-button:tag__remove-button, - remove-button__base:tag__remove-button__base, - icon:icon - " - class=${"search_tag " + option.classList.value} - ?pill=${this.pill} - size=${this.size} - ?removable=${!readonly} - ?readonly=${readonly} - ?editable=${isEditable} - .value=${option.value.replaceAll("___", " ")} - @dblclick=${this._handleDoubleClick} - @click=${typeof this.onTagClick == "function" ? (e) => this.onTagClick(e, e.target) : nothing} - > - ${image ?? nothing} - ${option.getTextLabel().trim()} - - `; - } - - /** - * Additional customisation template - * @returns {*} - * @protected - */ - protected _extraTemplate() - { - return typeof super._extraTemplate == "function" ? super._extraTemplate() : nothing; - } /** * Customise how tags are rendered. This overrides what SlSelect @@ -867,6 +766,141 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect) return this.shadowRoot?.querySelector("sl-select"); } + /** + * Custom, dynamic styling + * + * Put as much as you can in static styles for performance reasons + * Override this for custom dynamic styles + * + * @returns {TemplateResult} + * @protected + */ + protected _styleTemplate() : TemplateResult + { + return null; + } + + /** + * Used for the "no value" option for single select + * Placeholder is used for multi-select with no value + * + * @returns {TemplateResult} + */ + _emptyLabelTemplate() : TemplateResult + { + if(!this.emptyLabel || this.multiple) + { + return html``; + } + return html` + v == "")} + > + ${this.emptyLabel} + `; + } + + /** + * Iterate over all the options + * @returns {TemplateResult} + * @protected + */ + protected _optionsTemplate() : TemplateResult + { + return html`${repeat(this.select_options + // Filter out empty values if we have empty label to avoid duplicates + .filter(o => this.emptyLabel ? o.value !== '' : o), this._groupTemplate.bind(this)) + }`; + } + + /** + * Used to render each option into the select + * Override for custom select options. Note that spaces are not allowed in option values, + * and sl-select _requires_ options to be + * + * @param {SelectOption} option + * @returns {TemplateResult} + */ + protected _optionTemplate(option : SelectOption) : TemplateResult + { + // Exclude non-matches when searching + if(typeof option.isMatch == "boolean" && !option.isMatch) + { + return html``; + } + + // Tag used must match this.optionTag, but you can't use the variable directly. + // Pass option along so SearchMixin can grab it if needed + const value = (option.value).replaceAll(" ", "___"); + return html` + v == value)} + ?disabled=${option.disabled} + > + ${this._iconTemplate(option)} + ${this.noLang ? option.label : this.egw().lang(option.label)} + `; + } + + + /** + * Custom tag + * + * Override this to customise display when multiple=true. + * There is no restriction on the tag used, unlike _optionTemplate() + * + * @param {Et2Option} option + * @param {number} index + * @returns {TemplateResult} + * @protected + */ + protected _tagTemplate(option : SlOption, index : number) : TemplateResult + { + const readonly = (this.readonly || option && typeof (option.disabled) != "undefined" && option.disabled); + const isEditable = this.editModeEnabled && !readonly; + const image = this._createImage(option); + const tagName = this.tagTag; + return html` + <${tagName} + part="tag" + exportparts=" + base:tag__base, + content:tag__content, + remove-button:tag__remove-button, + remove-button__base:tag__remove-button__base, + icon:icon + " + class=${"search_tag " + option.classList.value} + ?pill=${this.pill} + size=${this.size || "medium"} + ?removable=${!readonly} + ?readonly=${readonly} + ?editable=${isEditable} + .value=${option.value.replaceAll("___", " ")} + @dblclick=${this._handleDoubleClick} + @click=${typeof this.onTagClick == "function" ? (e) => this.onTagClick(e, e.target) : nothing} + > + ${image ?? nothing} + ${option.getTextLabel().trim()} + + `; + } + + /** + * Additional customisation template + * Override if needed. Added after select options. + * + * @protected + */ + protected _extraTemplate() : TemplateResult | typeof nothing + { + return typeof super._extraTemplate == "function" ? super._extraTemplate() : nothing; + } + public render() { const value = Array.isArray(this.value) ? @@ -883,12 +917,13 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect) } } return html` + ${this._styleTemplate()} } } - protected _extraTemplate() + protected _extraTemplate() : TemplateResult | typeof nothing { if(!this.searchEnabled && !this.editModeEnabled && !this.allowFreeEntries || this.readonly) { diff --git a/api/js/etemplate/Et2Select/Select/Et2SelectAccount.ts b/api/js/etemplate/Et2Select/Select/Et2SelectAccount.ts index 537d0cb213..6a77e3d55f 100644 --- a/api/js/etemplate/Et2Select/Select/Et2SelectAccount.ts +++ b/api/js/etemplate/Et2Select/Select/Et2SelectAccount.ts @@ -70,14 +70,6 @@ export class Et2SelectAccount extends SelectAccountMixin(Et2StaticSelectMixin(Et this.fetchComplete = Promise.all(fetch); } - - firstUpdated(changedProperties?) - { - super.firstUpdated(changedProperties); - // Due to the different way Et2SelectAccount handles options, we call this explicitly - this._renderOptions(); - } - set accountType(type : AccountType) { this.__accountType = type; diff --git a/api/js/etemplate/Et2Select/Select/Et2SelectCategory.ts b/api/js/etemplate/Et2Select/Select/Et2SelectCategory.ts index c462612b1e..084627bcc5 100644 --- a/api/js/etemplate/Et2Select/Select/Et2SelectCategory.ts +++ b/api/js/etemplate/Et2Select/Select/Et2SelectCategory.ts @@ -105,15 +105,15 @@ export class Et2SelectCategory extends Et2StaticSelectMixin(Et2Select) } /** - * Used to render each option into the select - * Overridden for colors + * Custom, dynamic styling + * + * CSS variables are not making it through to options, re-declaring them here works * - * @param {SelectOption} option * @returns {TemplateResult} + * @protected */ - public render() : TemplateResult + protected _styleTemplate() : TemplateResult { - /** CSS variables are not making it through to options, re-declaring them here works */ return html` - ${super.render()} `; } @@ -141,20 +140,6 @@ export class Et2SelectCategory extends Et2StaticSelectMixin(Et2Select) { return literal`et2-category-tag`; } - - /** - * Customise how tags are rendered. - * This overrides parent to set application - * - * @param item - * @protected - */ - protected _createTagNode(item) - { - let tag = super._createTagNode(item); - tag.application = this.application; - return tag; - } } customElements.define("et2-select-cat", Et2SelectCategory); \ No newline at end of file diff --git a/api/js/etemplate/Et2Select/Select/Et2SelectEmail.ts b/api/js/etemplate/Et2Select/Select/Et2SelectEmail.ts index 98f4abe300..32fec1bd8e 100644 --- a/api/js/etemplate/Et2Select/Select/Et2SelectEmail.ts +++ b/api/js/etemplate/Et2Select/Select/Et2SelectEmail.ts @@ -243,16 +243,6 @@ export class Et2SelectEmail extends Et2Select `; } - /** - * Override image to skip it, we add images in Et2EmailTag using CSS - * @param item - * @protected - */ - protected _createImage(item) - { - return this.multiple ? "" : super._createImage(item); - } - /** * Overwritten to NOT split RFC822 addresses containing a comma in quoted name part * diff --git a/api/js/etemplate/Et2Select/Tag/Et2Tag.ts b/api/js/etemplate/Et2Select/Tag/Et2Tag.ts index 24fbbe26d0..1eab4fca40 100644 --- a/api/js/etemplate/Et2Select/Tag/Et2Tag.ts +++ b/api/js/etemplate/Et2Select/Tag/Et2Tag.ts @@ -35,6 +35,9 @@ export class Et2Tag extends Et2Widget(SlTag) width: 20px; } + .tag__prefix { + line-height: normal; + } .tag__content { padding: 0px 0.2rem; flex: 1 2 auto;