diff --git a/api/etemplate.php b/api/etemplate.php
index b7e204c804..4afb641cb0 100644
--- a/api/etemplate.php
+++ b/api/etemplate.php
@@ -351,6 +351,9 @@ function send_template()
return ''.$matches[5].'';
}, $str);
+ // use et2-email instead of et2-select-email
+ $str = preg_replace('##s', '', $str);
+
// nextmatch headers
$str = preg_replace_callback('#<(nextmatch-)([^ ]+)(header|filter) ([^>]+?)/>#s', static function (array $matches)
{
diff --git a/api/js/etemplate/Et2Select/Select/Et2SelectEmail.ts b/api/js/etemplate/Et2Select/Select/Et2SelectEmail.ts
deleted file mode 100644
index 83b49094f9..0000000000
--- a/api/js/etemplate/Et2Select/Select/Et2SelectEmail.ts
+++ /dev/null
@@ -1,396 +0,0 @@
-/**
- * EGroupware eTemplate2 - Email-selection 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 {Et2Select} from "../Et2Select";
-import {css, html, nothing, PropertyValues} from "lit";
-import {IsEmail} from "../../Validators/IsEmail";
-import interact from "@interactjs/interact";
-import {Validator} from "@lion/form-core";
-import {classMap} from "lit/directives/class-map.js";
-import {state} from "lit/decorators/state.js";
-
-/**
- * Select email address(es)
- *
- * Allows free entries of valid email addresses, but also searches contacts.
- *
- * You should set multiple="true" for most cases for better UI
- * @see Et2SelectEmail
- */
-export class Et2SelectEmail extends Et2Select
-{
- static get styles()
- {
- return [
- ...super.styles,
- css`
- :host {
- display: block;
- flex: 1 1 auto;
- min-width: 200px;
- }
- ::slotted(sl-icon[slot="suffix"]) {
- display: none;
- }
-
- /* Hide selected options from the dropdown */
- ::slotted([checked])
- {
- display: none;
- }
-
- /*** Drag & Drop ***/
-
- .et2-select-draggable {
- touch-action: none;
- }
-
- :host(.et2_dropZone)::part(combobox) {
- background-color: transparent;
- }
- `
- ];
- }
-
- static get properties()
- {
- return {
- ...super.properties,
-
- /**
- * Allow drag and drop tags between two or more Et2SelectEmail widgets
- */
- allowDragAndDrop: {type: Boolean},
-
- /**
- * Allow placeholders like {{email}}, beside real email-addresses
- */
- allowPlaceholder: {type: Boolean},
-
- /**
- * Include mailing lists: returns them with their integer list_id
- */
- includeLists: {type: Boolean},
-
- /**
- * What to display for the selected email addresses
- *
- * {@link Et2EmailTag#emailDisplay}
- */
- emailDisplay: {type: String}, //"full" | "email" | "name" | "domain";
- }
- }
-
- /* Drag and drop has issues with positioning when we automatically open. Don't open if the user clicks on a tag. */
- @state() _cancelOpen = false;
-
- constructor(...args : any[])
- {
- super(...args);
-
- // Additional option for select email, per ticket #79694
- this._close_on_select = this.egw().preference("select_multiple_close") != "open";
-
- this.search = true;
- this.searchUrl = "EGroupware\\Api\\Etemplate\\Widget\\Taglist::ajax_email";
- this.allowFreeEntries = true;
- this.allowPlaceholder = false;
- this.editModeEnabled = true;
- this.allowDragAndDrop = false;
- this.includeLists = false;
- this.multiple = false;
- this.fullEmail = false;
- this.onlyEmail = false;
- this.defaultValidators.push(new IsEmail(this.allowPlaceholder));
-
- this.handleMouseDown = this.handleMouseDown.bind(this);
- }
-
-
- /** @param {import('@lion/core').PropertyValues } changedProperties */
- willUpdate(changedProperties : PropertyValues)
- {
- super.willUpdate(changedProperties);
-
- if(changedProperties.has('allowPlaceholder'))
- {
- this.defaultValidators = (>this.defaultValidators).filter(v => !(v instanceof IsEmail));
- this.defaultValidators.push(new IsEmail(this.allowPlaceholder));
- }
- }
-
- firstUpdated()
- {
- this.select.addEventListener("mousedown", this.handleMouseDown);
- }
-
- updated(changedProperties : Map)
- {
- super.updated(changedProperties);
-
- // Make tags draggable
- this._cancelOpen = false;
- if(!this.readonly && this.allowFreeEntries && this.allowDragAndDrop)
- {
- this._makeDraggable();
- }
- }
-
- /**
- * Make the tags draggable
- * @protected
- */
- protected _makeDraggable()
- {
- this.select.updateComplete.then(() =>
- {
- let dragTranslate = {x: 0, y: 0};
- const tags = Array.from(this.select.shadowRoot.querySelectorAll(".select__tags et2-email-tag"));
- let draggable = interact(".et2-select-draggable").draggable({
- startAxis: 'xy',
- listeners: {
- start: function(e)
- {
- this.hide();
- let dragPosition = {x: e.page.x, y: e.page.y};
- dragTranslate = {x: 0, y: 0};
- e.target.setAttribute('style', `width:${e.target.clientWidth}px !important`);
- e.target.style.position = 'fixed';
- e.target.style.zIndex = 10;
- e.target.style.transform =
- `translate(${dragPosition.x}px, ${dragPosition.y}px)`;
- }.bind(this),
- move: function(e)
- {
- dragTranslate.x += e.delta.x;
- dragTranslate.y += e.delta.y;
- e.target.style.transform =
- `translate(${dragTranslate.x}px, ${dragTranslate.y}px)`;
- },
- end: function(e)
- {
- // Restore dragged tag to where it came from
- e.target.removeAttribute("style");
- }
- }
- });
- // set parent_node with widget context in order to make it accessible after drop
- draggable.parent_node = this;
- });
- }
-
- protected _bindListeners()
- {
- super._bindListeners();
- if(!this.multiple)
- {
- return;
- }
- this.updateComplete.then(() =>
- {
- interact(this).dropzone({
- accept: `.et2-select-draggable`,
- ondrop: function(e)
- {
- // Restore dragged tag
- e.relatedTarget.removeAttribute("style");
-
- const source = e.relatedTarget.getRootNode().host // Containing sl-select
- .getRootNode().host // Containing et2-select-email
- // remove the dragged value from its origin source
- source.value = source.value.filter(_item => {return e.relatedTarget.value !== _item;})
-
- // Add in as free entry
- e.target.createFreeEntry(e.relatedTarget.value);
- e.target.classList.remove('et2_dropZone');
- },
- ondragenter: function(e)
- {
- this.hide();
- this.classList.add('et2_dropZone');
- }.bind(this),
- ondragleave: function(e)
- {
- this.classList.remove('et2_dropZone');
- }.bind(this)
- });
- });
- }
-
- protected handleMouseDown(e : MouseEvent)
- {
- if(this._cancelOpen)
- {
- e.stopImmediatePropagation();
-
- // We need this hidden _now_ because of drag & drop, can't wait until update
- this._activeControls.classList.remove("active");
- this.select.shadowRoot.querySelector("sl-popup").active = false;
- }
- }
- /**
- * Handle keypresses inside the search input
- * Overridden from parent to also skip the hidden selected options, which other selects do not do
- *
- * @param {KeyboardEvent} event
- * @protected
- */
- protected _handleSearchKeyDown(event : KeyboardEvent)
- {
- // Pass off some keys to select
- if(['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key))
- {
- // Strip out hidden non-matching selected so key navigation works
- this.menuItems = this.menuItems.filter(i => !i.checked);
- }
- return super._handleSearchKeyDown(event);
- }
-
- /**
- * Actually query the server.
- *
- * Overridden to change request to match server
- *
- * @param {string} search
- * @param {object} options
- * @returns {any}
- * @protected
- */
- protected remoteQuery(search : string, options : object)
- {
- return this.egw().request(this.searchUrl, [search, {includeLists: this.includeLists}]).then((results) =>
- {
- // If results have a total included, pull it out.
- // It will cause errors if left in the results
- if(typeof results.total !== "undefined")
- {
- this._total_result_count += results.total;
- delete results.total;
- // Make it an array, since it was probably an object, and cleanSelectOptions() treats objects differently
- results = Object.values(results);
- }
- else
- {
- this._total_result_count += results.length;
- }
- this._total_result_count -= this.processRemoteResults(results);
- });
- }
-
- /**
- * Use a custom tag for when multiple=true
- *
- * @returns {string}
- */
- _tagTemplate(option, index)
- {
- const readonly = (this.readonly || option && typeof (option.disabled) != "undefined" && option.disabled);
- const isEditable = this.editModeEnabled && !readonly;
-
- return html`
- {this._cancelOpen = true;}}
- .value=${option.value.replaceAll("___", " ")}
- >
- ${option.getTextLabel().trim()}
-
- `;
- }
-
- /**
- * Override icon for the select option to use lavatar, same as Et2SelectAccount
- *
- * @param option
- * @protected
- */
- protected _iconTemplate(option)
- {
- // lavatar uses a size property, not a CSS variable
- let style = getComputedStyle(this);
-
- return html`
-
- `;
- }
-
- /**
- * Overwritten to NOT split RFC822 addresses containing a comma in quoted name part
- *
- * E.g. '"Becker, Ralf" '
- *
- * @param val
- */
- set_value(val : string | string[] | number | number[])
- {
- if(typeof val === 'string' && val.indexOf(',') !== -1)
- {
- val = val.split(',');
- for(let n=0; n < val.length-1; n++)
- {
- while (val[n].indexOf('@') === -1 && n < val.length-1)
- {
- val[n] += ',' + val[n+1];
- val.splice(n+1, 1);
- }
- }
- }
- super.set_value(val);
- }
-
-
- /**
- * Sometimes users paste multiple comma separated values at once. Split them then handle normally.
- * Overridden here to handle email addresses that may have commas using the regex from the validator.
- *
- * @param {ClipboardEvent} event
- * @protected
- */
- protected _handlePaste(event : ClipboardEvent)
- {
- event.preventDefault();
-
- let paste = event.clipboardData.getData('text');
- if(!paste)
- {
- return;
- }
- const selection = window.getSelection();
- if(selection.rangeCount)
- {
- selection.deleteFromDocument();
- }
-
- let preg = this.allowPlaceholder ? IsEmail.EMAIL_PLACEHOLDER_PREG : IsEmail.EMAIL_PREG;
- // Trim line start / end anchors off validation regex, make global
- let regex = new RegExp(preg.toString().substring(2, preg.toString().length - 3), 'g');
- let values = paste.match(regex);
- values.forEach(v =>
- {
- this.createFreeEntry(v.trim());
- });
- this.dropdown.hide();
- }
-}
-
-// @ts-ignore TypeScript is not recognizing that this widget is a LitElement
-customElements.define("et2-select-email", Et2SelectEmail);
\ No newline at end of file
diff --git a/api/js/etemplate/et2_widget_taglist.ts b/api/js/etemplate/et2_widget_taglist.ts
index 0f5511f93c..a72192e0d7 100644
--- a/api/js/etemplate/et2_widget_taglist.ts
+++ b/api/js/etemplate/et2_widget_taglist.ts
@@ -11,7 +11,7 @@
import type {Et2Select} from "./Et2Select/Et2Select";
import type {Et2SelectAccount} from "./Et2Select/Select/Et2SelectAccount";
-import type {Et2SelectEmail} from "./Et2Select/Select/Et2SelectEmail";
+import type {Et2Email} from "./Et2Email/Et2Email";
import type {Et2SelectCategory} from "./Et2Select/Select/Et2SelectCategory";
import type {Et2SelectThumbnail} from "./Et2Select/Select/Et2SelectThumbnail";
@@ -28,9 +28,9 @@ export type et2_taglist = Et2Select;
export type et2_taglist_account = Et2SelectAccount;
/**
- * @deprecated use et2_SelectEmail
+ * @deprecated use ET2Email
*/
-export type et2_taglist_email = Et2SelectEmail;
+export type et2_taglist_email = Et2Email;
/**
* @deprecated use Et2SelectCatgory