mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-06-26 12:51:52 +02:00
Select improvements
- Add _styleTemplate for easier extension - re-organize file - Fix weird tag style - some cleanup
This commit is contained in:
parent
01797f071d
commit
f75567a863
@ -17,7 +17,7 @@ import {RowLimitedMixin} from "../Layout/RowLimitedMixin";
|
|||||||
import {Et2Tag} from "./Tag/Et2Tag";
|
import {Et2Tag} from "./Tag/Et2Tag";
|
||||||
import {Et2WithSearchMixin} from "./SearchMixin";
|
import {Et2WithSearchMixin} from "./SearchMixin";
|
||||||
import {property} from "lit/decorators/property.js";
|
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";
|
import {repeat} from "lit/directives/repeat.js";
|
||||||
|
|
||||||
// export Et2WidgetWithSelect which is used as type in other modules
|
// 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`
|
|
||||||
<sl-option value=""
|
|
||||||
.selected=${this.getValueAsArray().some(v => v == "")}
|
|
||||||
>
|
|
||||||
${this.emptyLabel}
|
|
||||||
</sl-option>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = (<string>option.value).replaceAll(" ", "___");
|
|
||||||
return html`
|
|
||||||
<sl-option
|
|
||||||
part="option"
|
|
||||||
value="${value}"
|
|
||||||
title="${!option.title || this.noLang ? option.title : this.egw().lang(option.title)}"
|
|
||||||
class="${option.class}" .option=${option}
|
|
||||||
.selected=${this.getValueAsArray().some(v => v == value)}
|
|
||||||
?disabled=${option.disabled}
|
|
||||||
>
|
|
||||||
${this._iconTemplate(option)}
|
|
||||||
${this.noLang ? option.label : this.egw().lang(option.label)}
|
|
||||||
</sl-option>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tag used for rendering tags when multiple=true
|
* Tag used for rendering tags when multiple=true
|
||||||
* Used for creating, finding & filtering options.
|
* Used for creating, finding & filtering options.
|
||||||
@ -620,54 +567,6 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
|
|||||||
return literal`et2-tag`;
|
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()}
|
|
||||||
</${tagName}>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Additional customisation template
|
|
||||||
* @returns {*}
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
protected _extraTemplate()
|
|
||||||
{
|
|
||||||
return typeof super._extraTemplate == "function" ? super._extraTemplate() : nothing;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Customise how tags are rendered. This overrides what SlSelect
|
* 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");
|
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`
|
||||||
|
<sl-option value=""
|
||||||
|
.selected=${this.getValueAsArray().some(v => v == "")}
|
||||||
|
>
|
||||||
|
${this.emptyLabel}
|
||||||
|
</sl-option>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 <sl-option>
|
||||||
|
*
|
||||||
|
* @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 = (<string>option.value).replaceAll(" ", "___");
|
||||||
|
return html`
|
||||||
|
<sl-option
|
||||||
|
part="option"
|
||||||
|
value="${value}"
|
||||||
|
title="${!option.title || this.noLang ? option.title : this.egw().lang(option.title)}"
|
||||||
|
class="${option.class}" .option=${option}
|
||||||
|
.selected=${this.getValueAsArray().some(v => v == value)}
|
||||||
|
?disabled=${option.disabled}
|
||||||
|
>
|
||||||
|
${this._iconTemplate(option)}
|
||||||
|
${this.noLang ? option.label : this.egw().lang(option.label)}
|
||||||
|
</sl-option>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()}
|
||||||
|
</${tagName}>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
public render()
|
||||||
{
|
{
|
||||||
const value = Array.isArray(this.value) ?
|
const value = Array.isArray(this.value) ?
|
||||||
@ -883,12 +917,13 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return html`
|
return html`
|
||||||
|
${this._styleTemplate()}
|
||||||
<sl-select
|
<sl-select
|
||||||
exportparts="prefix, tags, display-input, expand-icon, combobox, listbox, option"
|
exportparts="prefix, tags, display-input, expand-icon, combobox, listbox, option"
|
||||||
label=${this.label}
|
label=${this.label}
|
||||||
placeholder=${this.placeholder}
|
placeholder=${this.placeholder}
|
||||||
?multiple=${this.multiple}
|
?multiple=${this.multiple}
|
||||||
?disabled=${this.disabled}
|
?disabled=${this.disabled || this.readonly}
|
||||||
?clearable=${this.clearable}
|
?clearable=${this.clearable}
|
||||||
?required=${this.required}
|
?required=${this.required}
|
||||||
helpText=${this.helpText}
|
helpText=${this.helpText}
|
||||||
|
@ -71,7 +71,7 @@ export declare class SearchMixinInterface
|
|||||||
*
|
*
|
||||||
* @type {TemplateResult}
|
* @type {TemplateResult}
|
||||||
*/
|
*/
|
||||||
_extraTemplate : TemplateResult
|
_extraTemplate : TemplateResult | typeof nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -409,7 +409,7 @@ export const Et2WithSearchMixin = dedupeMixin(<T extends Constructor<LitElement>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _extraTemplate()
|
protected _extraTemplate() : TemplateResult | typeof nothing
|
||||||
{
|
{
|
||||||
if(!this.searchEnabled && !this.editModeEnabled && !this.allowFreeEntries || this.readonly)
|
if(!this.searchEnabled && !this.editModeEnabled && !this.allowFreeEntries || this.readonly)
|
||||||
{
|
{
|
||||||
|
@ -70,14 +70,6 @@ export class Et2SelectAccount extends SelectAccountMixin(Et2StaticSelectMixin(Et
|
|||||||
this.fetchComplete = Promise.all(fetch);
|
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)
|
set accountType(type : AccountType)
|
||||||
{
|
{
|
||||||
this.__accountType = type;
|
this.__accountType = type;
|
||||||
|
@ -105,15 +105,15 @@ export class Et2SelectCategory extends Et2StaticSelectMixin(Et2Select)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to render each option into the select
|
* Custom, dynamic styling
|
||||||
* Overridden for colors
|
*
|
||||||
|
* CSS variables are not making it through to options, re-declaring them here works
|
||||||
*
|
*
|
||||||
* @param {SelectOption} option
|
|
||||||
* @returns {TemplateResult}
|
* @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`
|
return html`
|
||||||
<style>
|
<style>
|
||||||
${repeat(this.select_options, (option) =>
|
${repeat(this.select_options, (option) =>
|
||||||
@ -128,7 +128,6 @@ export class Et2SelectCategory extends Et2StaticSelectMixin(Et2Select)
|
|||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</style>
|
</style>
|
||||||
${super.render()}
|
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,20 +140,6 @@ export class Et2SelectCategory extends Et2StaticSelectMixin(Et2Select)
|
|||||||
{
|
{
|
||||||
return literal`et2-category-tag`;
|
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);
|
customElements.define("et2-select-cat", Et2SelectCategory);
|
@ -243,16 +243,6 @@ export class Et2SelectEmail extends Et2Select
|
|||||||
</et2-lavatar>`;
|
</et2-lavatar>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
* Overwritten to NOT split RFC822 addresses containing a comma in quoted name part
|
||||||
*
|
*
|
||||||
|
@ -35,6 +35,9 @@ export class Et2Tag extends Et2Widget(SlTag)
|
|||||||
width: 20px;
|
width: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tag__prefix {
|
||||||
|
line-height: normal;
|
||||||
|
}
|
||||||
.tag__content {
|
.tag__content {
|
||||||
padding: 0px 0.2rem;
|
padding: 0px 0.2rem;
|
||||||
flex: 1 2 auto;
|
flex: 1 2 auto;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user