mirror of
https://github.com/EGroupware/egroupware.git
synced 2025-03-11 21:50:15 +01:00
Get category icons & colors working for select category
Also some refactoring of things to where they should be
This commit is contained in:
parent
2a79264674
commit
260d8f523a
@ -44,7 +44,9 @@ foreach($categories as $cat)
|
|||||||
{
|
{
|
||||||
// Use slightly more specific selector that just class, to allow defaults
|
// Use slightly more specific selector that just class, to allow defaults
|
||||||
// if the category has no color
|
// if the category has no color
|
||||||
$content .= ".egwGridView_scrollarea tr.row_category.cat_{$cat['id']} > td:first-child, .select-cat li.cat_{$cat['id']}, .et2_selectbox ul.chzn-results li.cat_{$cat['id']}, .et2_selectbox ul.chzn-choices li.cat_{$cat['id']}, .nextmatch_header_row .et2_selectbox.select-cat.cat_{$cat['id']} a.chzn-single , et2-select-cat > .cat_{$cat['id']} {border-left-color: {$cat['data']['color']};} .cat_{$cat['id']}.fullline_cat_bg, div.cat_{$cat['id']}, span.cat_{$cat['id']} { background-color: {$cat['data']['color']};} /*{$cat['name']}*/\n";
|
$content .= "/** {$cat['name']} **/\n/*webComponent*/\n";
|
||||||
|
$content .= ":root,:host {--cat-{$cat['id']}-color: {$cat['data']['color']};}, :host.cat_{$cat['id']}, .cat_{$cat['id']} {--category-color: {$cat['data']['color']};} et2-select-cat > .cat_{$cat['id']} {--category-color: {$cat['data']['color']};} \n";
|
||||||
|
$content .= "/*legacy*/\n.egwGridView_scrollarea tr.row_category.cat_{$cat['id']} > td:first-child, .select-cat li.cat_{$cat['id']}, .et2_selectbox ul.chzn-results li.cat_{$cat['id']}, .et2_selectbox ul.chzn-choices li.cat_{$cat['id']}, .nextmatch_header_row .et2_selectbox.select-cat.cat_{$cat['id']} a.chzn-single , .cat_{$cat['id']}.fullline_cat_bg, div.cat_{$cat['id']}, span.cat_{$cat['id']} { background-color: {$cat['data']['color']};} \n";
|
||||||
}
|
}
|
||||||
if (!empty($cat['data']['icon']))
|
if (!empty($cat['data']['icon']))
|
||||||
{
|
{
|
||||||
|
@ -13,10 +13,11 @@ import {StaticOptions} from "./StaticOptions";
|
|||||||
import {Et2widgetWithSelectMixin} from "./Et2WidgetWithSelectMixin";
|
import {Et2widgetWithSelectMixin} from "./Et2WidgetWithSelectMixin";
|
||||||
import {SelectOption} from "./FindSelectOptions";
|
import {SelectOption} from "./FindSelectOptions";
|
||||||
import {Et2InvokerMixin} from "../Et2Url/Et2InvokerMixin";
|
import {Et2InvokerMixin} from "../Et2Url/Et2InvokerMixin";
|
||||||
import {SlSelect} from "@shoelace-style/shoelace";
|
import {SlMenuItem, SlSelect} from "@shoelace-style/shoelace";
|
||||||
import {egw} from "../../jsapi/egw_global";
|
import {egw} from "../../jsapi/egw_global";
|
||||||
import shoelace from "../Styles/shoelace";
|
import shoelace from "../Styles/shoelace";
|
||||||
import {Et2WithSearchMixin} from "./SearchMixin";
|
import {Et2WithSearchMixin} from "./SearchMixin";
|
||||||
|
import {Et2Tag} from "./Tag/Et2Tag";
|
||||||
|
|
||||||
// export Et2WidgetWithSelect which is used as type in other modules
|
// export Et2WidgetWithSelect which is used as type in other modules
|
||||||
export class Et2WidgetWithSelect extends Et2widgetWithSelectMixin(SlSelect)
|
export class Et2WidgetWithSelect extends Et2widgetWithSelectMixin(SlSelect)
|
||||||
@ -49,12 +50,33 @@ export class Et2Select extends Et2WithSearchMixin(Et2InvokerMixin(Et2WidgetWithS
|
|||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Get rid of padding before/after options */
|
||||||
|
sl-menu::part(base) {
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Avoid double scrollbar if there are a lot of options */
|
/* Avoid double scrollbar if there are a lot of options */
|
||||||
.select__menu
|
.select__menu
|
||||||
{
|
{
|
||||||
max-height: initial;
|
max-height: initial;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 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 {
|
||||||
|
max-height: 5em;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
gap: 0.1rem 0.5rem;
|
||||||
|
}
|
||||||
|
/* 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%);
|
box-shadow: 1px 1px 1px rgb(0 0 0 / 60%);
|
||||||
}`
|
}`
|
||||||
@ -260,16 +282,48 @@ export class Et2Select extends Et2WithSearchMixin(Et2InvokerMixin(Et2WidgetWithS
|
|||||||
}
|
}
|
||||||
|
|
||||||
// propagate multiple to selectbox
|
// propagate multiple to selectbox
|
||||||
if (changedProperties.has('multiple'))
|
if(changedProperties.has('multiple'))
|
||||||
{
|
{
|
||||||
// switch the expand button off
|
// switch the expand button off
|
||||||
if (this.multiple)
|
if(this.multiple)
|
||||||
{
|
{
|
||||||
this.expand_multiple_rows = 0;
|
this.expand_multiple_rows = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this method from SlSelect to stick our own tags in there
|
||||||
|
*/
|
||||||
|
syncItemsFromValue()
|
||||||
|
{
|
||||||
|
if(typeof super.syncItemsFromValue === "function")
|
||||||
|
{
|
||||||
|
super.syncItemsFromValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only applies to multiple
|
||||||
|
if(typeof this.displayTags !== "object" || !this.multiple)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let overflow = null;
|
||||||
|
if(this.maxTagsVisible > 0 && this.displayTags.length > this.maxTagsVisible)
|
||||||
|
{
|
||||||
|
overflow = this.displayTags.pop();
|
||||||
|
}
|
||||||
|
const checkedItems = Object.values(this.menuItems).filter(item => this.value.includes(item.value));
|
||||||
|
this.displayTags = checkedItems.map(item => this._createTagNode(item));
|
||||||
|
|
||||||
|
// Re-slice & add overflow tag
|
||||||
|
if(overflow)
|
||||||
|
{
|
||||||
|
this.displayTags = this.displayTags.slice(0, this.maxTagsVisible);
|
||||||
|
this.displayTags.push(overflow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_emptyLabelTemplate() : TemplateResult
|
_emptyLabelTemplate() : TemplateResult
|
||||||
{
|
{
|
||||||
if(!this.empty_label || this.multiple)
|
if(!this.empty_label || this.multiple)
|
||||||
@ -280,18 +334,93 @@ export class Et2Select extends Et2WithSearchMixin(Et2InvokerMixin(Et2WidgetWithS
|
|||||||
<sl-menu-item value="">${this.empty_label}</sl-menu-item>`;
|
<sl-menu-item value="">${this.empty_label}</sl-menu-item>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag used for rendering options
|
||||||
|
* Used for finding & filtering options, they're created by the mixed-in class
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public get optionTag()
|
||||||
|
{
|
||||||
|
return "sl-menu-item";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
_optionTemplate(option : SelectOption) : TemplateResult
|
_optionTemplate(option : SelectOption) : TemplateResult
|
||||||
{
|
{
|
||||||
let icon = option.icon ? html`
|
let icon = option.icon ? html`
|
||||||
<et2-image slot="prefix" part="icon" style="width: var(--icon-width)"
|
<et2-image slot="prefix" part="icon" style="width: var(--icon-width)"
|
||||||
src="${option.icon}"></et2-image>` : "";
|
src="${option.icon}"></et2-image>` : "";
|
||||||
|
|
||||||
|
// Tag used must match this.optionTag, but you can't use the variable directly
|
||||||
return html`
|
return html`
|
||||||
<sl-menu-item value="${option.value}" title="${option.title}" class="${option.class}">
|
<sl-menu-item value="${option.value}" title="${option.title}" class="${option.class}">
|
||||||
${icon}
|
${icon}
|
||||||
${option.label}
|
${option.label}
|
||||||
</sl-menu-item>`;
|
</sl-menu-item>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag used for rendering tags when multiple=true
|
||||||
|
* Used for creating, finding & filtering options.
|
||||||
|
* @see createTagNode()
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
public get tagTag()
|
||||||
|
{
|
||||||
|
return "et2-tag";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Customise how tags are rendered. This overrides what SlSelect
|
||||||
|
* does in syncItemsFromValue().
|
||||||
|
* This is a copy+paste from SlSelect.syncItemsFromValue().
|
||||||
|
*
|
||||||
|
* @param item
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected _createTagNode(item)
|
||||||
|
{
|
||||||
|
const tag = <Et2Tag>document.createElement(this.tagTag);
|
||||||
|
tag.value = item.value;
|
||||||
|
tag.textContent = this.getItemLabel(item);
|
||||||
|
tag.class = item.classList.value + " search_tag";
|
||||||
|
tag.addEventListener("dblclick", this._handleDoubleClick);
|
||||||
|
tag.addEventListener("click", this.handleTagInteraction);
|
||||||
|
tag.addEventListener("keydown", this.handleTagInteraction);
|
||||||
|
tag.addEventListener("sl-remove", (event) =>
|
||||||
|
{
|
||||||
|
event.stopPropagation();
|
||||||
|
if(!this.disabled)
|
||||||
|
{
|
||||||
|
item.checked = false;
|
||||||
|
this.syncValueFromItems();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let image = this._createImage(item);
|
||||||
|
if(image)
|
||||||
|
{
|
||||||
|
tag.prepend(image);
|
||||||
|
}
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _createImage(item)
|
||||||
|
{
|
||||||
|
let image = item.querySelector("et2-image");
|
||||||
|
if(image)
|
||||||
|
{
|
||||||
|
image = image.clone();
|
||||||
|
image.slot = "prefix";
|
||||||
|
image.class = "tag_image";
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public get menuItems() : HTMLElement[]
|
||||||
|
{
|
||||||
|
return [...this.querySelectorAll<SlMenuItem>(this.optionTag)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define("et2-select", Et2Select);
|
customElements.define("et2-select", Et2Select);
|
||||||
@ -359,9 +488,13 @@ export class Et2SelectCategory extends Et2Select
|
|||||||
return [
|
return [
|
||||||
...super.styles,
|
...super.styles,
|
||||||
css`
|
css`
|
||||||
::slotted(*) {
|
/* Category color on options */
|
||||||
border-left: 3px solid transparent;
|
::slotted(*) {
|
||||||
}
|
border-left: 3px solid var(--category-color, transparent);
|
||||||
|
}
|
||||||
|
.select--standard .select__control {
|
||||||
|
border-left: 4px solid transparent;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -391,6 +524,71 @@ export class Et2SelectCategory extends Et2Select
|
|||||||
|
|
||||||
this.select_options = so.cat(this);
|
this.select_options = so.cat(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
connectedCallback()
|
||||||
|
{
|
||||||
|
super.connectedCallback();
|
||||||
|
|
||||||
|
if(typeof this.application == 'undefined')
|
||||||
|
{
|
||||||
|
this.application =
|
||||||
|
// When the widget is first created, it doesn't have a parent and can't find it's instanceManager
|
||||||
|
(this.getInstanceManager() && this.getInstanceManager().app) ||
|
||||||
|
this.egw().app_name();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
updated(changedProperties : PropertyValues)
|
||||||
|
{
|
||||||
|
super.updated(changedProperties);
|
||||||
|
|
||||||
|
if(changedProperties.has("value"))
|
||||||
|
{
|
||||||
|
this.doLabelChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
doLabelChange()
|
||||||
|
{
|
||||||
|
// Update the display label when checked menu item's label changes
|
||||||
|
if(this.multiple)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedItem = this.menuItems.find(item => item.value === this.value);
|
||||||
|
this.displayLabel = checkedItem ? checkedItem.textContent : '';
|
||||||
|
this.querySelector("[slot=prefix].tag_image")?.remove();
|
||||||
|
if(checkedItem)
|
||||||
|
{
|
||||||
|
let image = this._createImage(checkedItem)
|
||||||
|
if(image)
|
||||||
|
{
|
||||||
|
this.append(image);
|
||||||
|
}
|
||||||
|
this.dropdown.querySelector(".select__control").style.borderColor =
|
||||||
|
getComputedStyle(checkedItem).getPropertyValue("--category-color") || "transparent";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get tagTag() : string
|
||||||
|
{
|
||||||
|
return "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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement
|
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
import {Et2Select} from "./Et2Select";
|
import {Et2Select} from "./Et2Select";
|
||||||
import {SelectOption} from "./FindSelectOptions";
|
import {SelectOption} from "./FindSelectOptions";
|
||||||
import {html} from "@lion/core";
|
import {Et2Image} from "../Et2Image/Et2Image";
|
||||||
|
|
||||||
export type AccountType = 'accounts'|'groups'|'both'|'owngroups';
|
export type AccountType = 'accounts'|'groups'|'both'|'owngroups';
|
||||||
|
|
||||||
@ -90,11 +90,11 @@ export class Et2SelectAccount extends Et2Select
|
|||||||
* @protected
|
* @protected
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
protected _tagImageTemplate(item)
|
protected _createImage(item) : Et2Image
|
||||||
{
|
{
|
||||||
return html`
|
const image = super._createImage(item);
|
||||||
<et2-image slot="prefix" part="icon"
|
image.src = "/egroupware/api/avatar.php?account_id=" + item.value + "&etag=1";
|
||||||
src="/egroupware/api/avatar.php?account_id=${item.value}&etag=1"></et2-image>`;
|
return image;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,6 +29,12 @@ export class Et2SelectEmail extends Et2Select
|
|||||||
::slotted(sl-icon[slot="suffix"]) {
|
::slotted(sl-icon[slot="suffix"]) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide selected options from the dropdown */
|
||||||
|
::slotted([checked])
|
||||||
|
{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -142,10 +142,6 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
|
|||||||
:host([allowFreeEntries]) ::slotted([slot="suffix"]) {
|
:host([allowFreeEntries]) ::slotted([slot="suffix"]) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
/* Get rid of padding before/after options */
|
|
||||||
sl-menu::part(base) {
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
/* Make search textbox take full width */
|
/* Make search textbox take full width */
|
||||||
::slotted(.search_input), ::slotted(.search_input) input, .search_input, .search_input input {
|
::slotted(.search_input), ::slotted(.search_input) input, .search_input, .search_input input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -195,30 +191,10 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
|
|||||||
::slotted(.no-match) {
|
::slotted(.no-match) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
/* Hide selected options from the dropdown */
|
|
||||||
::slotted([checked])
|
|
||||||
{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
/* Different cursor for editable tags */
|
/* Different cursor for editable tags */
|
||||||
:host([allowfreeentries]) .search_tag::part(base) {
|
:host([allowfreeentries]) .search_tag::part(base) {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
}
|
}
|
||||||
/* 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 {
|
|
||||||
max-height: 5em;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
gap: 0.1rem 0.5rem;
|
|
||||||
}
|
|
||||||
/* Keep overflow tag right-aligned. It's the only sl-tag. */
|
|
||||||
.select__tags sl-tag {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
`
|
`
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -337,7 +313,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
|
|||||||
|
|
||||||
protected _searchInputTemplate()
|
protected _searchInputTemplate()
|
||||||
{
|
{
|
||||||
let edit = '';
|
let edit = null;
|
||||||
if(this.editModeEnabled)
|
if(this.editModeEnabled)
|
||||||
{
|
{
|
||||||
edit = html`<input id="edit" type="text" part="input"
|
edit = html`<input id="edit" type="text" part="input"
|
||||||
@ -385,20 +361,6 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
|
|||||||
this.querySelector(".search_input");
|
this.querySelector(".search_input");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tag used for rendering options
|
|
||||||
* Used for finding & filtering options, they're created by the mixed-in class
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
public get optionTag()
|
|
||||||
{
|
|
||||||
return "sl-menu-item";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected get menuItems()
|
|
||||||
{
|
|
||||||
return this.querySelectorAll(this.optionTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only local options, excludes server options
|
* Only local options, excludes server options
|
||||||
@ -432,7 +394,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
|
|||||||
// Overridden to add options if allowFreeEntries=true
|
// Overridden to add options if allowFreeEntries=true
|
||||||
if(this.allowFreeEntries)
|
if(this.allowFreeEntries)
|
||||||
{
|
{
|
||||||
if(typeof this.value == "string" && !Object.values(this.menuItems).find(o => o.value == this.value))
|
if(typeof this.value == "string" && !this.menuItems.find(o => o.value == this.value))
|
||||||
{
|
{
|
||||||
this.createFreeEntry(this.value);
|
this.createFreeEntry(this.value);
|
||||||
}
|
}
|
||||||
@ -440,7 +402,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
|
|||||||
{
|
{
|
||||||
this.value.forEach((e) =>
|
this.value.forEach((e) =>
|
||||||
{
|
{
|
||||||
if(!Object.values(this.menuItems).find(o => o.value == e))
|
if(!this.menuItems.find(o => o.value == e))
|
||||||
{
|
{
|
||||||
this.createFreeEntry(e);
|
this.createFreeEntry(e);
|
||||||
}
|
}
|
||||||
@ -856,85 +818,6 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
|
|||||||
this.handleMenuShow();
|
this.handleMenuShow();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Override this method from SlSelect to stick our own tags in there
|
|
||||||
*/
|
|
||||||
syncItemsFromValue()
|
|
||||||
{
|
|
||||||
if(typeof super.syncItemsFromValue === "function")
|
|
||||||
{
|
|
||||||
super.syncItemsFromValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only applies to multiple
|
|
||||||
if(typeof this.displayTags !== "object" || !this.multiple)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let overflow = null;
|
|
||||||
if(this.maxTagsVisible > 0 && this.displayTags.length > this.maxTagsVisible)
|
|
||||||
{
|
|
||||||
overflow = this.displayTags.pop();
|
|
||||||
}
|
|
||||||
const checkedItems = Object.values(this.menuItems).filter(item => this.value.includes(item.value));
|
|
||||||
this.displayTags = checkedItems.map(item => this._tagTemplate(item));
|
|
||||||
|
|
||||||
// Re-slice & add overflow tag
|
|
||||||
if(overflow)
|
|
||||||
{
|
|
||||||
this.displayTags = this.displayTags.slice(0, this.maxTagsVisible);
|
|
||||||
this.displayTags.push(overflow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Customise how tags are rendered. This overrides what SlSelect
|
|
||||||
* does in syncItemsFromValue().
|
|
||||||
* This is a copy+paste from SlSelect.syncItemsFromValue().
|
|
||||||
*
|
|
||||||
* @param item
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
protected _tagTemplate(item)
|
|
||||||
{
|
|
||||||
return html`
|
|
||||||
<et2-tag class="${item.classList.value} search_tag"
|
|
||||||
removable
|
|
||||||
value="${item.value}"
|
|
||||||
@dblclick=${this._handleDoubleClick}
|
|
||||||
@click=${this.handleTagInteraction}
|
|
||||||
@keydown=${this.handleTagInteraction}
|
|
||||||
@sl-remove=${(event) =>
|
|
||||||
{
|
|
||||||
event.stopPropagation();
|
|
||||||
if(!this.disabled)
|
|
||||||
{
|
|
||||||
item.checked = false;
|
|
||||||
this.syncValueFromItems();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
${this._tagImageTemplate(item)}
|
|
||||||
${this.getItemLabel(item)}
|
|
||||||
</et2-tag>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _tagImageTemplate(item)
|
|
||||||
{
|
|
||||||
let image = item.querySelector("et2-image");
|
|
||||||
if(image)
|
|
||||||
{
|
|
||||||
image = image.clone();
|
|
||||||
image.slot = "prefix";
|
|
||||||
image.class = "tag_image";
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected _handleSearchAbort(e)
|
protected _handleSearchAbort(e)
|
||||||
{
|
{
|
||||||
this._activeControls.classList.remove("active");
|
this._activeControls.classList.remove("active");
|
||||||
|
54
api/js/etemplate/Et2Select/Tag/Et2CategoryTag.ts
Normal file
54
api/js/etemplate/Et2Select/Tag/Et2CategoryTag.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* EGroupware eTemplate2 - Category Tag WebComponent
|
||||||
|
*
|
||||||
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
||||||
|
* @package api
|
||||||
|
* @link https://www.egroupware.org
|
||||||
|
* @author Nathan Gray
|
||||||
|
*/
|
||||||
|
import {css, html, TemplateResult} from "@lion/core";
|
||||||
|
import shoelace from "../../Styles/shoelace";
|
||||||
|
import {Et2Tag} from "./Et2Tag";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tag is usually used in a Et2CategorySelect with multiple=true, but there's no reason it can't go anywhere
|
||||||
|
*/
|
||||||
|
export class Et2CategoryTag extends Et2Tag
|
||||||
|
{
|
||||||
|
private value : string;
|
||||||
|
|
||||||
|
static get styles()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
super.styles,
|
||||||
|
shoelace, css`
|
||||||
|
.tag {
|
||||||
|
gap: var(--sl-spacing-2x-small);
|
||||||
|
/* --category-color is passed through in _styleTemplate() */
|
||||||
|
border-left: 5px solid var(--category-color, transparent);
|
||||||
|
}
|
||||||
|
`];
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(...args : [])
|
||||||
|
{
|
||||||
|
super(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due to how the scoping / encapulation works, we need to re-assign the category color
|
||||||
|
* variable here so it can be passed through. .cat_# {--category-color} is not visible.
|
||||||
|
*
|
||||||
|
* @returns {TemplateResult}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
protected _styleTemplate() : TemplateResult
|
||||||
|
{
|
||||||
|
let cat_var = "var(--cat-" + this.value + "-color)"
|
||||||
|
// @formatter:off
|
||||||
|
return html`<style>.tag { --category-color: ${cat_var}}</style>`;
|
||||||
|
//@formatter:on
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
customElements.define("et2-category-tag", Et2CategoryTag);
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
import {Et2Widget} from "../../Et2Widget/Et2Widget";
|
import {Et2Widget} from "../../Et2Widget/Et2Widget";
|
||||||
import {SlTag} from "@shoelace-style/shoelace";
|
import {SlTag} from "@shoelace-style/shoelace";
|
||||||
import {classMap, css, html} from "@lion/core";
|
import {classMap, css, html, TemplateResult} from "@lion/core";
|
||||||
import shoelace from "../../Styles/shoelace";
|
import shoelace from "../../Styles/shoelace";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -36,15 +36,31 @@ export class Et2Tag extends Et2Widget(SlTag)
|
|||||||
`];
|
`];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static get properties()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
...super.properties,
|
||||||
|
value: {type: String, reflect: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constructor(...args : [])
|
constructor(...args : [])
|
||||||
{
|
{
|
||||||
super(...args);
|
super(...args);
|
||||||
|
this.value = "";
|
||||||
this.pill = true;
|
this.pill = true;
|
||||||
|
this.removable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected _styleTemplate() : TemplateResult
|
||||||
|
{
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
render()
|
render()
|
||||||
{
|
{
|
||||||
return html`
|
return html`
|
||||||
|
${this._styleTemplate()}
|
||||||
<span
|
<span
|
||||||
part="base"
|
part="base"
|
||||||
class=${classMap({
|
class=${classMap({
|
||||||
|
@ -54,6 +54,7 @@ import './Et2Select/Et2SelectAccount';
|
|||||||
import './Et2Select/Et2SelectEmail';
|
import './Et2Select/Et2SelectEmail';
|
||||||
import './Et2Select/Et2SelectReadonly';
|
import './Et2Select/Et2SelectReadonly';
|
||||||
import './Et2Select/Tag/Et2Tag';
|
import './Et2Select/Tag/Et2Tag';
|
||||||
|
import './Et2Select/Tag/Et2CategoryTag'
|
||||||
import './Et2Textarea/Et2Textarea';
|
import './Et2Textarea/Et2Textarea';
|
||||||
import './Et2Textbox/Et2Textbox';
|
import './Et2Textbox/Et2Textbox';
|
||||||
import './Et2Textbox/Et2TextboxReadonly';
|
import './Et2Textbox/Et2TextboxReadonly';
|
||||||
|
Loading…
Reference in New Issue
Block a user