Feature/shoelace 2.4 upgrade (#135)

Update shoelace to 2.9.0
This commit is contained in:
Nathan Gray 2023-09-13 11:55:33 -06:00 committed by GitHub
parent 0f77eca5c4
commit e323cd1d79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
130 changed files with 3852 additions and 1868 deletions

View File

@ -21,18 +21,20 @@ import {fetchAll, nm_action, nm_compare_field} from "../../api/js/etemplate/et2_
import "./CRM"; import "./CRM";
import {egw} from "../../api/js/jsapi/egw_global"; import {egw} from "../../api/js/jsapi/egw_global";
import {LitElement} from "@lion/core"; import {LitElement} from "@lion/core";
import {Et2SelectState} from "../../api/js/etemplate/Et2Select/Et2Select"; import {Et2SelectCountry} from "../../api/js/etemplate/Et2Select/Select/Et2SelectCountry";
import {Et2SelectCountry} from "../../api/js/etemplate/Et2Select/Et2SelectCountry";
import {Et2SelectState} from "../../api/js/etemplate/Et2Select/Select/Et2SelectState";
/** /**
* Object to call app.addressbook.openCRMview with * Object to call app.addressbook.openCRMview with
*/ */
export interface CrmParams { export interface CrmParams
contact_id: number|string; {
crm_list?: "infolog"|"tracker"|"infolog-organisation"; // default: use preference contact_id : number | string;
title?: string; // default: link-title of contact_id crm_list? : "infolog" | "tracker" | "infolog-organisation"; // default: use preference
icon?: string; // default: avatar for contact_id title? : string; // default: link-title of contact_id
index?: number; icon? : string; // default: avatar for contact_id
index? : number;
} }
/** /**

View File

@ -1,4 +1,6 @@
import {Directive, directive, html, repeat} from "@lion/core"; import {html} from "lit";
import {Directive, directive} from "lit/directive.js";
import {repeat} from "lit/directives/repeat.js";
import {et2_activateLinks} from "./et2_core_common"; import {et2_activateLinks} from "./et2_core_common";
/** /**

View File

@ -9,7 +9,8 @@
*/ */
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {css, SlotMixin} from "@lion/core"; import {css} from "lit";
import {SlotMixin} from "@lion/core";
import {SlAvatar} from "@shoelace-style/shoelace"; import {SlAvatar} from "@shoelace-style/shoelace";
import {et2_IDetachedDOM} from "../et2_core_interfaces"; import {et2_IDetachedDOM} from "../et2_core_interfaces";
import {egw} from "../../jsapi/egw_global"; import {egw} from "../../jsapi/egw_global";

View File

@ -1,5 +1,6 @@
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {css, html, LitElement, repeat} from "@lion/core"; import {css, html, LitElement} from "lit";
import {repeat} from "lit/directives/repeat.js";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";
/** /**

View File

@ -10,7 +10,7 @@
import {Et2Avatar} from "./Et2Avatar"; import {Et2Avatar} from "./Et2Avatar";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";
import {css} from "@lion/core"; import {css} from "lit";
export class Et2LAvatar extends Et2Avatar export class Et2LAvatar extends Et2Avatar
{ {

View File

@ -1,7 +1,7 @@
/** /**
* Cropper styles constant * Cropper styles constant
*/ */
import {css} from "@lion/core"; import {css} from "lit";
/*! /*!
* Cropper.js v1.5.12 * Cropper.js v1.5.12

View File

@ -9,7 +9,7 @@
*/ */
import {css, LitElement, PropertyValues} from "@lion/core"; import {css, LitElement, PropertyValues} from "lit";
import '../Et2Image/Et2Image'; import '../Et2Image/Et2Image';
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";

View File

@ -13,7 +13,7 @@ import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import '../Et2Image/Et2Image'; import '../Et2Image/Et2Image';
import {SlButton} from "@shoelace-style/shoelace"; import {SlButton} from "@shoelace-style/shoelace";
import {ButtonMixin} from "./ButtonMixin"; import {ButtonMixin} from "./ButtonMixin";
import {PropertyValues} from "@lion/core"; import {PropertyValues} from "lit";
export class Et2Button extends ButtonMixin(Et2InputWidget(SlButton)) export class Et2Button extends ButtonMixin(Et2InputWidget(SlButton))

View File

@ -14,7 +14,7 @@ import '../Et2Image/Et2Image';
import {SlIconButton} from "@shoelace-style/shoelace"; import {SlIconButton} from "@shoelace-style/shoelace";
import {ButtonMixin} from "./ButtonMixin"; import {ButtonMixin} from "./ButtonMixin";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";
import {css} from "@lion/core"; import {css} from "lit";
export class Et2ButtonIcon extends ButtonMixin(Et2InputWidget(SlIconButton)) export class Et2ButtonIcon extends ButtonMixin(Et2InputWidget(SlIconButton))

View File

@ -8,7 +8,7 @@
* @author Nathan Gray * @author Nathan Gray
*/ */
import {css, html, LitElement} from "@lion/core"; import {css, html, LitElement} from "lit";
import {ButtonMixin} from "./ButtonMixin"; import {ButtonMixin} from "./ButtonMixin";
/** /**

View File

@ -9,7 +9,7 @@
*/ */
import {css} from "@lion/core"; import {css} from "lit";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import '../Et2Image/Et2Image'; import '../Et2Image/Et2Image';
import {SlCheckbox} from "@shoelace-style/shoelace"; import {SlCheckbox} from "@shoelace-style/shoelace";

View File

@ -1,7 +1,8 @@
import {et2_IDetachedDOM} from "../et2_core_interfaces"; import {et2_IDetachedDOM} from "../et2_core_interfaces";
import {et2_checkbox} from "../et2_widget_checkbox"; import {et2_checkbox} from "../et2_widget_checkbox";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {classMap, css, html, LitElement} from "@lion/core"; import {css, html, LitElement} from "lit";
import {classMap} from "lit/directives/class-map.js"
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";
/** /**

View File

@ -9,7 +9,7 @@
*/ */
import {css, html, PropertyValues, render} from "@lion/core"; import {css, html, PropertyValues, render} from "lit";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {SlColorPicker} from "@shoelace-style/shoelace"; import {SlColorPicker} from "@shoelace-style/shoelace";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";

View File

@ -2,7 +2,7 @@
* Sharable date styles constant * Sharable date styles constant
*/ */
import {css} from "@lion/core"; import {css} from "lit";
import {colorsDefStyles} from "../Styles/colorsDefStyles"; import {colorsDefStyles} from "../Styles/colorsDefStyles";
import {cssImage} from "../Et2Widget/Et2Widget"; import {cssImage} from "../Et2Widget/Et2Widget";

View File

@ -9,7 +9,7 @@
*/ */
import {css, html} from "@lion/core"; import {css, html} from "lit";
import 'lit-flatpickr'; import 'lit-flatpickr';
import {dateStyles} from "./DateStyles"; import {dateStyles} from "./DateStyles";
import type {Instance} from 'flatpickr/dist/types/instance'; import type {Instance} from 'flatpickr/dist/types/instance';
@ -19,15 +19,11 @@ import flatpickr from "flatpickr";
import {egw} from "../../jsapi/egw_global"; import {egw} from "../../jsapi/egw_global";
import type {HTMLElementWithValue} from "@lion/form-core/types/FormControlMixinTypes"; import type {HTMLElementWithValue} from "@lion/form-core/types/FormControlMixinTypes";
import {Et2Textbox} from "../Et2Textbox/Et2Textbox"; import {Et2Textbox} from "../Et2Textbox/Et2Textbox";
import {Et2ButtonIcon} from "../Et2Button/Et2ButtonIcon";
import {FormControlMixin} from "@lion/form-core"; import {FormControlMixin} from "@lion/form-core";
import {LitFlatpickr} from "lit-flatpickr"; import {LitFlatpickr} from "lit-flatpickr";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";
const textbox = new Et2Textbox();
const button = new Et2ButtonIcon();
// list of existing localizations from node_modules/flatpicker/dist/l10n directory: // list of existing localizations from node_modules/flatpicker/dist/l10n directory:
const l10n = [ const l10n = [
'ar', 'at', 'az', 'be', 'bg', 'bn', 'bs', 'cat', 'cs', 'cy', 'da', 'de', 'eo', 'es', 'et', 'fa', 'fi', 'fo', 'ar', 'at', 'az', 'be', 'bg', 'bn', 'bs', 'cat', 'cs', 'cy', 'da', 'de', 'eo', 'es', 'et', 'fa', 'fi', 'fo',

View File

@ -9,7 +9,8 @@
*/ */
import {classMap, css, html, LitElement} from "@lion/core"; import {css, html, LitElement} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {sprintf} from "../../egw_action/egw_action_common"; import {sprintf} from "../../egw_action/egw_action_common";
import {dateStyles} from "./DateStyles"; import {dateStyles} from "./DateStyles";
@ -132,6 +133,7 @@ export class Et2DateDuration extends Et2InputWidget(FormControlMixin(LitElement)
} }
.input-group__after { .input-group__after {
display: contents;
margin-inline-start: var(--sl-input-spacing-medium); margin-inline-start: var(--sl-input-spacing-medium);
} }
@ -612,9 +614,9 @@ export class Et2DateDuration extends Et2InputWidget(FormControlMixin(LitElement)
<et2-select value="${this._display.unit || this.displayFormat[0]}"> <et2-select value="${this._display.unit || this.displayFormat[0]}">
${[...this.displayFormat].map((format : string) => ${[...this.displayFormat].map((format : string) =>
html` html`
<sl-menu-item value=${format} ?checked=${this._display.unit === format}> <sl-option value=${format}>
${this.time_formats[format]} ${this.time_formats[format]}
</sl-menu-item>` </sl-option>`
)} )}
</et2-select> </et2-select>
`; `;

View File

@ -8,7 +8,7 @@
*/ */
import {css, html} from "@lion/core"; import {css, html} from "lit";
import {Et2DateDuration, formatOptions} from "./Et2DateDuration"; import {Et2DateDuration, formatOptions} from "./Et2DateDuration";
import {dateStyles} from "./DateStyles"; import {dateStyles} from "./DateStyles";

View File

@ -1,6 +1,8 @@
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {FormControlMixin} from "@lion/form-core"; import {FormControlMixin} from "@lion/form-core";
import {classMap, css, html, ifDefined, LitElement, TemplateResult} from "@lion/core"; import {css, html, LitElement, TemplateResult} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {ifDefined} from "lit/directives/if-defined.js";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";
import {dateStyles} from "./DateStyles"; import {dateStyles} from "./DateStyles";
import {formatDate, parseDate} from "./Et2Date"; import {formatDate, parseDate} from "./Et2Date";

View File

@ -8,7 +8,7 @@
*/ */
import {html, LitElement} from "@lion/core"; import {html, LitElement} from "lit";
import {formatDate, parseDate} from "./Et2Date"; import {formatDate, parseDate} from "./Et2Date";
import {et2_IDetachedDOM} from "../et2_core_interfaces"; import {et2_IDetachedDOM} from "../et2_core_interfaces";
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";

View File

@ -8,7 +8,7 @@
*/ */
import {html} from "@lion/core"; import {html} from "lit";
import {parseDate, parseDateTime} from "./Et2Date"; import {parseDate, parseDateTime} from "./Et2Date";
import {Et2DateReadonly} from "./Et2DateReadonly"; import {Et2DateReadonly} from "./Et2DateReadonly";

View File

@ -9,7 +9,7 @@
*/ */
import {css} from "@lion/core"; import {css} from "lit";
import {Et2Date, formatDate, formatDateTime} from "./Et2Date"; import {Et2Date, formatDate, formatDateTime} from "./Et2Date";
import type {Instance} from "flatpickr/dist/types/instance"; import type {Instance} from "flatpickr/dist/types/instance";
import {default as ShortcutButtonsPlugin} from "shortcut-buttons-flatpickr/dist/shortcut-buttons-flatpickr"; import {default as ShortcutButtonsPlugin} from "shortcut-buttons-flatpickr/dist/shortcut-buttons-flatpickr";

View File

@ -8,7 +8,7 @@
*/ */
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {css, html, LitElement, render} from "@lion/core"; import {css, html, LitElement, render} from "lit";
import {et2_IDetachedDOM} from "../et2_core_interfaces"; import {et2_IDetachedDOM} from "../et2_core_interfaces";
import {activateLinks} from "../ActivateLinksDirective"; import {activateLinks} from "../ActivateLinksDirective";
import {et2_csvSplit} from "../et2_core_common"; import {et2_csvSplit} from "../et2_core_common";

View File

@ -12,7 +12,12 @@
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {et2_button} from "../et2_widget_button"; import {et2_button} from "../et2_widget_button";
import {et2_widget} from "../et2_core_widget"; import {et2_widget} from "../et2_core_widget";
import {classMap, css, html, ifDefined, LitElement, render, repeat, SlotMixin, styleMap} from "@lion/core"; import {css, html, LitElement, render} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {ifDefined} from "lit/directives/if-defined.js";
import {repeat} from "lit/directives/repeat.js";
import {styleMap} from "lit/directives/style-map.js";
import {SlotMixin} from "@lion/core";
import {et2_template} from "../et2_widget_template"; import {et2_template} from "../et2_widget_template";
import {etemplate2} from "../etemplate2"; import {etemplate2} from "../etemplate2";
import {egw, IegwAppLocal} from "../../jsapi/egw_global"; import {egw, IegwAppLocal} from "../../jsapi/egw_global";

View File

@ -9,11 +9,11 @@
*/ */
import {Et2Button} from "../Et2Button/Et2Button";
import {SlButtonGroup, SlDropdown} from "@shoelace-style/shoelace"; import {SlButtonGroup, SlDropdown} from "@shoelace-style/shoelace";
import {css, html, TemplateResult} from "@lion/core"; import {css, html, LitElement, TemplateResult} from "lit";
import {Et2WidgetWithSelectMixin} from "../Et2Select/Et2WidgetWithSelectMixin"; import {Et2WidgetWithSelectMixin} from "../Et2Select/Et2WidgetWithSelectMixin";
import {SelectOption} from "../Et2Select/FindSelectOptions"; import {SelectOption} from "../Et2Select/FindSelectOptions";
import shoelace from "../Styles/shoelace";
/** /**
* A split button - a button with a dropdown list * A split button - a button with a dropdown list
@ -28,13 +28,14 @@ import {SelectOption} from "../Et2Select/FindSelectOptions";
* as for a select box, but the title can also be full HTML if needed. * as for a select box, but the title can also be full HTML if needed.
* *
*/ */
export class Et2DropdownButton extends Et2WidgetWithSelectMixin(Et2Button) export class Et2DropdownButton extends Et2WidgetWithSelectMixin(LitElement)
{ {
static get styles() static get styles()
{ {
return [ return [
...super.styles, ...super.styles,
shoelace,
css` css`
:host { :host {
/* Avoid unwanted style overlap from button */ /* Avoid unwanted style overlap from button */
@ -98,6 +99,7 @@ export class Et2DropdownButton extends Et2WidgetWithSelectMixin(Et2Button)
// We have our own render, so we can handle it internally // We have our own render, so we can handle it internally
} }
render() : TemplateResult render() : TemplateResult
{ {
if(this.readonly) if(this.readonly)
@ -129,10 +131,10 @@ export class Et2DropdownButton extends Et2WidgetWithSelectMixin(Et2Button)
<et2-image slot="prefix" src=${option.icon} icon></et2-image>` : ''; <et2-image slot="prefix" src=${option.icon} icon></et2-image>` : '';
return html` return html`
<sl-menu-item value="${option.value}"> <sl-option value="${option.value}">
${icon} ${icon}
${this.noLang ? option.label : this.egw().lang(option.label)} ${this.noLang ? option.label : this.egw().lang(option.label)}
</sl-menu-item>`; </sl-option>`;
} }
protected _handleSelect(ev) protected _handleSelect(ev)

View File

@ -10,7 +10,7 @@
*/ */
import {Et2DropdownButton} from "../Et2DropdownButton/Et2DropdownButton"; import {Et2DropdownButton} from "../Et2DropdownButton/Et2DropdownButton";
import {css, html, PropertyValues, TemplateResult} from "@lion/core"; import {css, html, PropertyValues, TemplateResult} from "lit";
import {SelectOption} from "../Et2Select/FindSelectOptions"; import {SelectOption} from "../Et2Select/FindSelectOptions";
import {et2_INextmatchHeader, et2_nextmatch} from "../et2_extension_nextmatch"; import {et2_INextmatchHeader, et2_nextmatch} from "../et2_extension_nextmatch";
import {Et2Image} from "../Et2Image/Et2Image"; import {Et2Image} from "../Et2Image/Et2Image";
@ -76,24 +76,24 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
min-width: 15em; min-width: 15em;
} }
sl-menu-item:hover et2-image[src="trash"] { sl-option:hover et2-image[src="trash"] {
display: initial; display: initial;
} }
/* Add star icons - radio button is already in prefix */ /* Add star icons - radio button is already in prefix */
sl-menu-item::part(base) { sl-option::part(base) {
background-image: ${cssImage("fav_filter")}; background-image: ${cssImage("fav_filter")};
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: 16px 16px; background-size: 16px 16px;
background-position: 5px center; background-position: 5px center;
} }
sl-menu-item[checked]::part(base) { sl-option[checked]::part(base) {
background-image: ${cssImage("favorites")}; background-image: ${cssImage("favorites")};
} }
sl-menu-item:last-child::part(base) { sl-option:last-child::part(base) {
background-image: none; background-image: none;
} }
`, `,
@ -185,11 +185,11 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
statustext="${this.egw().lang("Delete")}"></et2-image>`; statustext="${this.egw().lang("Delete")}"></et2-image>`;
return html` return html`
<sl-menu-item value="${option.value}" ?checked="${option.value == this._preferred}"> <sl-option value="${option.value}" ?checked="${option.value == this._preferred}">
${option.value !== Et2Favorites.ADD_VALUE ? radio : ""} ${option.value !== Et2Favorites.ADD_VALUE ? radio : ""}
${icon} ${icon}
${option.label} ${option.label}
</sl-menu-item>`; </sl-option>`;
} }

View File

@ -9,7 +9,8 @@
*/ */
import {css, html, LitElement, SlotMixin} from "@lion/core"; import {css, html, LitElement} from "lit";
import {SlotMixin} from "@lion/core";
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
export class Et2Iframe extends Et2Widget(SlotMixin(LitElement)) export class Et2Iframe extends Et2Widget(SlotMixin(LitElement))

View File

@ -8,8 +8,8 @@
* @author Nathan Gray * @author Nathan Gray
*/ */
import {css, html, LitElement, render} from "lit";
import {css, html, LitElement, render, SlotMixin} from "@lion/core"; import {SlotMixin} from "@lion/core";
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {et2_IDetachedDOM} from "../et2_core_interfaces"; import {et2_IDetachedDOM} from "../et2_core_interfaces";

View File

@ -1,10 +1,11 @@
import {et2_IInput, et2_IInputNode, et2_ISubmitListener} from "../et2_core_interfaces"; import {et2_IInput, et2_IInputNode, et2_ISubmitListener} from "../et2_core_interfaces";
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {css, dedupeMixin, LitElement, PropertyValues} from "@lion/core"; import {css, LitElement, PropertyValues} from "lit";
import {Required} from "../Validators/Required"; import {Required} from "../Validators/Required";
import {ManualMessage} from "../Validators/ManualMessage"; import {ManualMessage} from "../Validators/ManualMessage";
import {LionValidationFeedback, Validator} from "@lion/form-core"; import {LionValidationFeedback, Validator} from "@lion/form-core";
import {et2_csvSplit} from "../et2_core_common"; import {et2_csvSplit} from "../et2_core_common";
import {dedupeMixin} from "@lion/core";
/** /**
* This mixin will allow any LitElement to become an Et2InputWidget * This mixin will allow any LitElement to become an Et2InputWidget

View File

@ -11,7 +11,7 @@
import {ExposeMixin, ExposeValue} from "../Expose/ExposeMixin"; import {ExposeMixin, ExposeValue} from "../Expose/ExposeMixin";
import {css, html, LitElement, TemplateResult} from "@lion/core"; import {css, html, LitElement, TemplateResult} from "lit";
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {et2_IDetachedDOM} from "../et2_core_interfaces"; import {et2_IDetachedDOM} from "../et2_core_interfaces";

View File

@ -1,6 +1,7 @@
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {css, html, LitElement, PropertyValues} from "lit";
import {FormControlMixin, ValidateMixin} from "@lion/form-core"; import {FormControlMixin, ValidateMixin} from "@lion/form-core";
import {css, html, LitElement, PropertyValues, SlotMixin} from "@lion/core"; import {SlotMixin} from "@lion/core";
import {Et2LinkAppSelect} from "./Et2LinkAppSelect"; import {Et2LinkAppSelect} from "./Et2LinkAppSelect";
import {LinkInfo} from "./Et2Link"; import {LinkInfo} from "./Et2Link";
import {Et2Button} from "../Et2Button/Et2Button"; import {Et2Button} from "../Et2Button/Et2Button";

View File

@ -1,9 +1,9 @@
import {cleanSelectOptions, SelectOption} from "../Et2Select/FindSelectOptions"; import {cleanSelectOptions, SelectOption} from "../Et2Select/FindSelectOptions";
import {css, html, SlotMixin, TemplateResult} from "@lion/core"; import {css, html, TemplateResult} from "lit";
import {Et2Select} from "../Et2Select/Et2Select"; import {Et2Select} from "../Et2Select/Et2Select";
export class Et2LinkAppSelect extends SlotMixin(Et2Select) export class Et2LinkAppSelect extends Et2Select
{ {
static get styles() static get styles()
{ {
@ -49,22 +49,11 @@ export class Et2LinkAppSelect extends SlotMixin(Et2Select)
} }
}; };
get slots() /*
{ icon.style.width = "var(--icon-width)";
return { icon.style.height = "var(--icon-width)";
...super.slots,
"": () =>
{
const icon = document.createElement("et2-image"); */
icon.setAttribute("slot", "prefix");
icon.setAttribute("src", "api/navbar");
icon.style.width = "var(--icon-width)";
icon.style.height = "var(--icon-width)";
return icon;
}
}
}
protected __applicationList : string[]; protected __applicationList : string[];
protected __onlyApp : string; protected __onlyApp : string;
@ -104,9 +93,6 @@ export class Et2LinkAppSelect extends SlotMixin(Et2Select)
{ {
super.connectedCallback(); super.connectedCallback();
// Set icon
this.querySelector(":scope > [slot='prefix']").setAttribute("src", this.egw().link_get_registry(this.value, 'icon') ?? this.value + "/navbar");
if(!this.value) if(!this.value)
{ {
// use preference // use preference
@ -118,7 +104,7 @@ export class Et2LinkAppSelect extends SlotMixin(Et2Select)
this.value = this.egw().preference('link_app', appname || this.egw().app_name()); this.value = this.egw().preference('link_app', appname || this.egw().app_name());
} }
// Register to // Register to
this.addEventListener("change", this._handleChange); this.addEventListener("sl-change", this._handleChange);
if(this.__onlyApp) if(this.__onlyApp)
{ {
@ -129,7 +115,7 @@ export class Et2LinkAppSelect extends SlotMixin(Et2Select)
disconnectedCallback() disconnectedCallback()
{ {
super.disconnectedCallback(); super.disconnectedCallback();
this.removeEventListener("change", this._handleChange); this.removeEventListener("sl-change", this._handleChange);
} }
/** /**
@ -173,10 +159,9 @@ export class Et2LinkAppSelect extends SlotMixin(Et2Select)
super.value = new_value; super.value = new_value;
} }
_handleChange(e) handleValueChange(e)
{ {
// Set icon super.handleValueChange(e);
this.querySelector(":scope > [slot='prefix']").setAttribute("src", this.egw().link_get_registry(this.value, 'icon'));
// update preference // update preference
let appname = ""; let appname = "";
@ -198,13 +183,21 @@ export class Et2LinkAppSelect extends SlotMixin(Et2Select)
// Limit to one app // Limit to one app
if(this.onlyApp) if(this.onlyApp)
{ {
select_options.push({value: this.onlyApp, label: this.egw().lang(this.onlyApp)}); select_options.push({
value: this.onlyApp,
label: this.egw().lang(this.onlyApp),
icon: this.egw().link_get_registry(this.onlyApp, 'icon') ?? this.onlyApp + "/navbar"
});
} }
else if(this.applicationList.length > 0) else if(this.applicationList.length > 0)
{ {
select_options = this.applicationList.map((app) => select_options = this.applicationList.map((app) =>
{ {
return {value: app, label: this.egw().lang(app)}; return {
value: app,
label: this.egw().lang(app),
icon: this.egw().link_get_registry(app, 'icon') ?? app + "/navbar"
};
}); });
} }
else else
@ -215,29 +208,27 @@ export class Et2LinkAppSelect extends SlotMixin(Et2Select)
{ {
delete select_options['addressbook-email']; delete select_options['addressbook-email'];
} }
select_options = cleanSelectOptions(select_options);
select_options.map((option) =>
{
option.icon = this.egw().link_get_registry(option.value, 'icon') ?? option.value + "/navbar"
});
} }
if (!this.value) if (!this.value)
{ {
this.value = <string>this.egw().preference('link_app', this.egw().app_name()); this.value = <string>this.egw().preference('link_app', this.egw().app_name());
} }
this.select_options = cleanSelectOptions(select_options); this.select_options = select_options;
} }
_optionTemplate(option : SelectOption) : TemplateResult _optionTemplate(option : SelectOption) : TemplateResult
{ {
return html` return html`
<sl-menu-item value="${option.value}" title="${option.title}"> <sl-option value="${option.value}" title="${option.title}">
${this.appIcons ? "" : option.label} ${this.appIcons ? "" : option.label}
${this._iconTemplate(option.value)} ${this._iconTemplate(option)}
</sl-menu-item>`; </sl-option>`;
}
_iconTemplate(appname)
{
let url = appname ? this.egw().link_get_registry(appname, 'icon') : "";
return html`
<et2-image style="width: var(--icon-width)" slot="prefix" src="${url}"></et2-image>`;
} }
} }

View File

@ -6,8 +6,8 @@
* @link https://www.egroupware.org * @link https://www.egroupware.org
* @author Nathan Gray * @author Nathan Gray
*/ */
import {css, html, LitElement, PropertyValues} from "lit";
import {css, html, LitElement, PropertyValues, SlotMixin} from "@lion/core"; import {SlotMixin} from "@lion/core";
import {Et2LinkAppSelect} from "./Et2LinkAppSelect"; import {Et2LinkAppSelect} from "./Et2LinkAppSelect";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {FormControlMixin} from "@lion/form-core"; import {FormControlMixin} from "@lion/form-core";

View File

@ -10,8 +10,9 @@
*/ */
import {css, html, repeat, TemplateResult} from "@lion/core"; import {css, html, TemplateResult} from "lit";
import {Et2Link, LinkInfo} from "./Et2Link"; import {repeat} from "lit/directives/repeat.js";
import {LinkInfo} from "./Et2Link";
import {egw} from "../../jsapi/egw_global"; import {egw} from "../../jsapi/egw_global";
import {Et2LinkString} from "./Et2LinkString"; import {Et2LinkString} from "./Et2LinkString";
import {egwMenu} from "../../egw_action/egw_menu"; import {egwMenu} from "../../egw_action/egw_menu";

View File

@ -7,7 +7,7 @@
* @author Nathan Gray * @author Nathan Gray
*/ */
import {css} from "@lion/core"; import {css} from "lit";
import {Et2Select} from "../Et2Select/Et2Select"; import {Et2Select} from "../Et2Select/Et2Select";
import {Et2LinkAppSelect} from "./Et2LinkAppSelect"; import {Et2LinkAppSelect} from "./Et2LinkAppSelect";
import {Et2Link} from "./Et2Link"; import {Et2Link} from "./Et2Link";
@ -86,9 +86,9 @@ export class Et2LinkSearch extends Et2Select
super.updated(changedProperties); super.updated(changedProperties);
// Set a value we don't have as an option? That's OK, we'll just add it // Set a value we don't have as an option? That's OK, we'll just add it
if(changedProperties.has("value") && this.value && ( if(changedProperties.has("value") && this.value && this.value.length > 0 && (
this.menuItems && this.menuItems.length == 0 || this.getAllOptions().length == 0 ||
this.menuItems?.filter && this.menuItems.filter(item => this.value.includes(item.value)).length == 0 this.getAllOptions().filter && this.getAllOptions().filter(item => this.getValueAsArray().includes(item.value)).length == 0
)) ))
{ {
this._missingOption(this.value) this._missingOption(this.value)
@ -120,19 +120,16 @@ export class Et2LinkSearch extends Et2Select
option.label = title || Et2Link.MISSING_TITLE; option.label = title || Et2Link.MISSING_TITLE;
option.class = ""; option.class = "";
// It's probably already been rendered, find the item // It's probably already been rendered, find the item
let item = this.menuItems.find(i => i.value === option.value); let item = this.getAllOptions().find(i => i.value === option.value);
if(item) if(item)
{ {
item.textContent = title; item.textContent = title;
item.classList.remove("loading"); item.classList.remove("loading");
this.syncItemsFromValue();
} }
else else
{ {
// Not already rendered, update the select option // Not already rendered, update the select option
this.requestUpdate("select_options"); this.requestUpdate("select_options");
// update the displayed text
this.updateComplete.then(() => this.syncItemsFromValue());
} }
}); });
} }

View File

@ -10,7 +10,8 @@
*/ */
import {css, html, LitElement, PropertyValues, render, TemplateResult, until} from "@lion/core"; import {css, html, LitElement, PropertyValues, render, TemplateResult} from "lit";
import {until} from "lit/directives/until.js";
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {Et2Link, LinkInfo} from "./Et2Link"; import {Et2Link, LinkInfo} from "./Et2Link";
import {et2_IDetachedDOM} from "../et2_core_interfaces"; import {et2_IDetachedDOM} from "../et2_core_interfaces";

View File

@ -12,7 +12,8 @@
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {FormControlMixin, ValidateMixin} from "@lion/form-core"; import {FormControlMixin, ValidateMixin} from "@lion/form-core";
import {css, html, LitElement, ScopedElementsMixin} from "@lion/core"; import {css, html, LitElement} from "lit";
import {ScopedElementsMixin} from "@lion/core";
import {et2_createWidget, et2_widget} from "../et2_core_widget"; import {et2_createWidget, et2_widget} from "../et2_core_widget";
import {et2_file} from "../et2_widget_file"; import {et2_file} from "../et2_widget_file";
import {Et2Button} from "../Et2Button/Et2Button"; import {Et2Button} from "../Et2Button/Et2Button";

View File

@ -1,7 +1,9 @@
/** /**
* Column selector for nextmatch * Column selector for nextmatch
*/ */
import {classMap, css, html, LitElement, repeat, TemplateResult} from "@lion/core"; import {css, html, LitElement, TemplateResult} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {repeat} from "lit/directives/repeat.js";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {et2_nextmatch_customfields} from "../et2_extension_nextmatch"; import {et2_nextmatch_customfields} from "../et2_extension_nextmatch";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";
@ -85,7 +87,7 @@ export class Et2ColumnSelection extends Et2InputWidget(LitElement)
{ {
this.sort = Sortable.create(this.shadowRoot.querySelector('sl-menu'), { this.sort = Sortable.create(this.shadowRoot.querySelector('sl-menu'), {
ghostClass: 'ui-fav-sortable-placeholder', ghostClass: 'ui-fav-sortable-placeholder',
draggable: 'sl-menu-item.column', draggable: 'sl-option.column',
dataIdAttr: 'value', dataIdAttr: 'value',
direction: 'vertical', direction: 'vertical',
delay: 25 delay: 25
@ -141,10 +143,11 @@ export class Et2ColumnSelection extends Et2InputWidget(LitElement)
return html``; return html``;
} }
return html` return html`
<sl-menu-item <sl-option
value="${column.id}" value="${column.id}"
?checked=${alwaysOn || column.visibility == et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE} ?checked=${alwaysOn || column.visibility == et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE}
?disabled=${alwaysOn} ?disabled=${alwaysOn}
.selected="${this.value.some(v => v == column.id)}"
title="${column.title}" title="${column.title}"
class="${classMap({ class="${classMap({
select_row: true, select_row: true,
@ -154,7 +157,7 @@ export class Et2ColumnSelection extends Et2InputWidget(LitElement)
${column.caption} ${column.caption}
<!-- Custom fields get listed separately --> <!-- Custom fields get listed separately -->
${isCustom ? this.customFieldsTemplate(column) : ''} ${isCustom ? this.customFieldsTemplate(column) : ''}
</sl-menu-item>`; </sl-option>`;
} }
/** /**
@ -198,8 +201,8 @@ export class Et2ColumnSelection extends Et2InputWidget(LitElement)
handleSelectAll(event) handleSelectAll(event)
{ {
let checked = (<SlMenuItem>this.shadowRoot.querySelector("sl-menu-item")).checked || false; let checked = (<SlMenuItem>this.shadowRoot.querySelector("sl-option")).checked || false;
this.shadowRoot.querySelectorAll('sl-menu-item').forEach((item) => {item.checked = !checked}); this.shadowRoot.querySelectorAll('sl-option').forEach((item) => {item.checked = !checked});
} }
set columns(new_columns) set columns(new_columns)

View File

@ -1,4 +1,4 @@
import {Et2SelectAccount} from "../../Et2Select/Et2SelectAccount"; import {Et2SelectAccount} from "../../Et2Select/Select/Et2SelectAccount";
import {et2_INextmatchHeader} from "../../et2_extension_nextmatch"; import {et2_INextmatchHeader} from "../../et2_extension_nextmatch";
import {FilterMixin} from "./FilterMixin"; import {FilterMixin} from "./FilterMixin";

View File

@ -2,7 +2,7 @@ import {loadWebComponent} from "../../Et2Widget/Et2Widget";
import {Et2Select} from "../../Et2Select/Et2Select"; import {Et2Select} from "../../Et2Select/Et2Select";
import {Et2InputWidget, Et2InputWidgetInterface} from "../../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget, Et2InputWidgetInterface} from "../../Et2InputWidget/Et2InputWidget";
import {FilterMixin} from "./FilterMixin"; import {FilterMixin} from "./FilterMixin";
import {html, LitElement} from "@lion/core"; import {html, LitElement} from "lit";
/** /**
* Filter by some other type of widget * Filter by some other type of widget

View File

@ -1,6 +1,6 @@
import {egw} from "../../../jsapi/egw_global"; import {egw} from "../../../jsapi/egw_global";
import {et2_INextmatchHeader, et2_nextmatch} from "../../et2_extension_nextmatch"; import {et2_INextmatchHeader, et2_nextmatch} from "../../et2_extension_nextmatch";
import {LitElement} from "@lion/core"; import {LitElement} from "lit";
// Export the Interface for TypeScript // Export the Interface for TypeScript
type Constructor<T = LitElement> = new (...args : any[]) => T; type Constructor<T = LitElement> = new (...args : any[]) => T;

View File

@ -15,8 +15,9 @@ import {SlCard} from "@shoelace-style/shoelace";
import interact from "@interactjs/interactjs"; import interact from "@interactjs/interactjs";
import type {InteractEvent} from "@interactjs/core/InteractEvent"; import type {InteractEvent} from "@interactjs/core/InteractEvent";
import {egw} from "../../jsapi/egw_global"; import {egw} from "../../jsapi/egw_global";
import {classMap, css, html, TemplateResult} from "@lion/core"; import {css, html, TemplateResult} from "lit";
import {HasSlotController} from "@shoelace-style/shoelace/dist/internal/slot"; import {classMap} from "lit/directives/class-map.js";
import type {HasSlotController} from "../../../../node_modules/@shoelace-style/shoelace/dist/internal/slot";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";
import {Et2Dialog} from "../Et2Dialog/Et2Dialog"; import {Et2Dialog} from "../Et2Dialog/Et2Dialog";
import {et2_IResizeable} from "../et2_core_interfaces"; import {et2_IResizeable} from "../et2_core_interfaces";

View File

@ -2,7 +2,7 @@ import {SlMenu} from "@shoelace-style/shoelace";
import {Et2WidgetWithSelectMixin} from "./Et2WidgetWithSelectMixin"; import {Et2WidgetWithSelectMixin} from "./Et2WidgetWithSelectMixin";
import {RowLimitedMixin} from "../Layout/RowLimitedMixin"; import {RowLimitedMixin} from "../Layout/RowLimitedMixin";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";
import {css, html, TemplateResult} from "@lion/core"; import {css, html, TemplateResult} from "lit";
import {SelectOption} from "./FindSelectOptions"; import {SelectOption} from "./FindSelectOptions";
/** /**
@ -40,7 +40,8 @@ export class Et2Listbox extends RowLimitedMixin(Et2WidgetWithSelectMixin(SlMenu)
overflow-x: clip; overflow-x: clip;
} }
/* Ellipsis when too small */ /* Ellipsis when too small */
sl-menu-item.menu-item__label {
sl-option.option__label {
display: block; display: block;
text-overflow: ellipsis; text-overflow: ellipsis;
/* This is usually not used due to flex, but is the basis for ellipsis calculation */ /* This is usually not used due to flex, but is the basis for ellipsis calculation */
@ -153,15 +154,15 @@ export class Et2Listbox extends RowLimitedMixin(Et2WidgetWithSelectMixin(SlMenu)
// Tag used must match this.optionTag, but you can't use the variable directly. // Tag used must match this.optionTag, but you can't use the variable directly.
// Pass option along so SearchMixin can grab it if needed // Pass option along so SearchMixin can grab it if needed
return html` return html`
<sl-menu-item <sl-option
value="${option.value}" value="${option.value}"
title="${!option.title || this.noLang ? option.title : this.egw().lang(option.title)}" title="${!option.title || this.noLang ? option.title : this.egw().lang(option.title)}"
class="${option.class}" .option=${option} class="${option.class}" .option=${option}
?checked=${checked} .selected=${checked}
> >
${icon} ${icon}
${this.noLang ? option.label : this.egw().lang(option.label)} ${this.noLang ? option.label : this.egw().lang(option.label)}
</sl-menu-item>`; </sl-option>`;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,8 @@
*/ */
import {Et2InputWidget, Et2InputWidgetInterface} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget, Et2InputWidgetInterface} from "../Et2InputWidget/Et2InputWidget";
import {html, LitElement, PropertyValues, render, TemplateResult} from "@lion/core"; import {html, LitElement, PropertyValues, render, TemplateResult} from "lit";
import {property} from "lit/decorators/property.js";
import {et2_readAttrWithDefault} from "../et2_core_xml"; import {et2_readAttrWithDefault} from "../et2_core_xml";
import {cleanSelectOptions, find_select_options, SelectOption} from "./FindSelectOptions"; import {cleanSelectOptions, find_select_options, SelectOption} from "./FindSelectOptions";
import {SearchMixinInterface} from "./SearchMixin"; import {SearchMixinInterface} from "./SearchMixin";
@ -17,7 +18,7 @@ import {SearchMixinInterface} from "./SearchMixin";
* Base class for things that do selectbox type behaviour, to avoid putting too much or copying into read-only * Base class for things that do selectbox type behaviour, to avoid putting too much or copying into read-only
* selectboxes, also for common handling of properties for more special selectboxes. * selectboxes, also for common handling of properties for more special selectboxes.
* *
* As with most other widgets that extend Lion components, do not override render(). * As with most other widgets that extend Shoelace components, do not override render() without good reason.
* To extend this mixin, override: * To extend this mixin, override:
* - _optionTargetNode(): Return the HTMLElement where the "options" go. * - _optionTargetNode(): Return the HTMLElement where the "options" go.
* - _optionTemplate(option:SelectOption): Renders the option. To use a special widget, use its tag in render. * - _optionTemplate(option:SelectOption): Renders the option. To use a special widget, use its tag in render.
@ -46,15 +47,6 @@ import {SearchMixinInterface} from "./SearchMixin";
* You can specify something else, or return {} to do your own thing. This is a little more complicated. You should * You can specify something else, or return {} to do your own thing. This is a little more complicated. You should
* also override _inputGroupInputTemplate() to do what you normally would in render(). * also override _inputGroupInputTemplate() to do what you normally would in render().
* *
*
* Technical note:
* LionSelect (and any other LionField) use slots to wrap a real DOM node. ET2 doesn't expect this,
* so we have to create the input node (via slots()) and respect that it is _external_ to the Web Component.
* This complicates things like adding the options, since we can't just override _inputGroupInputTemplate()
* and include them when rendering - the parent expects to find the <select> added via a slot, render() would
* put it inside the shadowDOM. That's fine, but then it doesn't get created until render(), and the parent
* (LionField) can't find it when it looks for it before then.
*
*/ */
// Export the Interface for TypeScript // Export the Interface for TypeScript
type Constructor<T = {}> = new (...args : any[]) => T; type Constructor<T = {}> = new (...args : any[]) => T;
@ -63,29 +55,46 @@ export const Et2WidgetWithSelectMixin = <T extends Constructor<LitElement>>(supe
{ {
class Et2WidgetWithSelect extends Et2InputWidget(superclass) class Et2WidgetWithSelect extends Et2InputWidget(superclass)
{ {
static get properties() /**
{ * The current value of the select, submitted as a name/value pair with form data. When `multiple` is enabled, the
return { * value attribute will be a space-delimited list of values based on the options selected, and the value property will
...super.properties, * be an array.
/** *
* Textual label for first row, eg: 'All' or 'None'. It's value will be '' @property({
*/ noAccessor: true,
emptyLabel: String, converter: {
fromAttribute: (value : string) => value.split(',')
/**
* Select box options
*
* Will be found automatically based on ID and type, or can be set explicitly in the template using
* <option/> children, or using widget.select_options = SelectOption[]
*/
select_options: {type: Object, noAccessor: true},
/**
* Limit size
*/
rows: {type: Number, noAccessor: true, reflect: true}
} }
} })
value : string | string[] = "";
*/
/**
* Textual label for first row, eg: 'All' or 'None'. It's value will be ''
*/
@property({type: String})
emptyLabel : String = "";
/**
* Limit size
*/
@property({type: Number, noAccessor: true, reflect: true})
/**
* Internal list of possible select options
*
* This is where we keep options sent from the server. This is not always the complete list, as extending
* classes may have their own options to add in. For example, static options are kept separate, as are search
* results. The select_options getter should give the complete list.
*/
private __select_options : SelectOption[] = [];
/**
* When we create the select option elements, it takes a while.
* If we don't wait for them, it causes issues in SlSelect
*/
protected _optionRenderPromise : Promise<void> = Promise.resolve();
/** /**
* Options found in the XML when reading the template * Options found in the XML when reading the template
@ -101,6 +110,13 @@ export const Et2WidgetWithSelectMixin = <T extends Constructor<LitElement>>(supe
this.__select_options = <SelectOption[]>[]; this.__select_options = <SelectOption[]>[];
} }
async getUpdateComplete() : Promise<boolean>
{
const result = await super.getUpdateComplete();
await this._optionRenderPromise;
return result;
}
/** @param {import('@lion/core').PropertyValues } changedProperties */ /** @param {import('@lion/core').PropertyValues } changedProperties */
updated(changedProperties : PropertyValues) updated(changedProperties : PropertyValues)
{ {
@ -116,26 +132,48 @@ export const Et2WidgetWithSelectMixin = <T extends Constructor<LitElement>>(supe
} }
} }
}
willUpdate(changedProperties : PropertyValues<this>)
{
// Add in actual option tags to the DOM based on the new select_options // Add in actual option tags to the DOM based on the new select_options
if(changedProperties.has('select_options') || changedProperties.has("emptyLabel")) if(changedProperties.has('select_options') || changedProperties.has("emptyLabel"))
{ {
// Add in options as children to the target node // Add in options as children to the target node
this._renderOptions(); const optionPromise = this._renderOptions();
// This is needed to display initial load value in some cases, like infolog nm header filters // This is needed to display initial load value in some cases, like infolog nm header filters
if(this.handleMenuSlotChange && !this.hasUpdated) if(typeof this.selectionChanged !== "undefined")
{ {
this.handleMenuSlotChange(); optionPromise.then(async() =>
{
await this.updateComplete;
this.selectionChanged();
});
} }
} }
} }
public getValueAsArray()
{
if(Array.isArray(this.value))
{
return this.value;
}
if(this.value == "null" || typeof this.value == "undefined" || !this.emptyLabel && this.value == "")
{
return [];
}
return [this.value];
}
/** /**
* Render select_options as child DOM Nodes * Render select_options as child DOM Nodes
* @protected * @protected
*/ */
protected _renderOptions() protected _renderOptions()
{ {
return Promise.resolve();
// Add in options as children to the target node // Add in options as children to the target node
if(!this._optionTargetNode) if(!this._optionTargetNode)
{ {
@ -156,7 +194,7 @@ export const Et2WidgetWithSelectMixin = <T extends Constructor<LitElement>>(supe
.map(this._groupTemplate.bind(this))}`; .map(this._groupTemplate.bind(this))}`;
render(options, temp_target); render(options, temp_target);
return Promise.all(([...temp_target.querySelectorAll(":scope > *")].map(item => item.render))) this._optionRenderPromise = Promise.all(([...temp_target.querySelectorAll(":scope > *")].map(item => item.render)))
.then(() => .then(() =>
{ {
this._optionTargetNode.replaceChildren( this._optionTargetNode.replaceChildren(
@ -168,23 +206,7 @@ export const Et2WidgetWithSelectMixin = <T extends Constructor<LitElement>>(supe
this.handleMenuSlotChange(); this.handleMenuSlotChange();
} }
}); });
return this._optionRenderPromise;
}
/**
* Overwritten as sometimes called before this._inputNode is available
*
* @param {*} v - modelValue: can be an Object, Number, String depending on the
* input type(date, number, email etc)
* @returns {string} formattedValue
*/
formatter(v)
{
if (!this._inputNode)
{
return v;
}
return super.formatter(v);
} }
/** /**
@ -212,6 +234,13 @@ export const Et2WidgetWithSelectMixin = <T extends Constructor<LitElement>>(supe
this.select_options = <SelectOption[]>new_options; this.select_options = <SelectOption[]>new_options;
} }
/**
* Select box options
*
* Will be found automatically based on ID and type, or can be set explicitly in the template using
* <option/> children, or using widget.select_options = SelectOption[]
*/
@property({type: Object})
get select_options() : SelectOption[] get select_options() : SelectOption[]
{ {
return this.__select_options; return this.__select_options;
@ -262,7 +291,7 @@ export const Et2WidgetWithSelectMixin = <T extends Constructor<LitElement>>(supe
* @param {SelectOption} option * @param {SelectOption} option
* @returns {TemplateResult} * @returns {TemplateResult}
*/ */
_optionTemplate(option : SelectOption) : TemplateResult protected _optionTemplate(option : SelectOption) : TemplateResult
{ {
return html` return html`
<span>Override _optionTemplate(). ${option.value} => ${option.label}</span>`; <span>Override _optionTemplate(). ${option.value} => ${option.label}</span>`;
@ -276,7 +305,7 @@ export const Et2WidgetWithSelectMixin = <T extends Constructor<LitElement>>(supe
} }
return html` return html`
<sl-menu-label>${this.noLang ? option.label : this.egw().lang(option.label)}</sl-menu-label> <small>${this.noLang ? option.label : this.egw().lang(option.label)}</small>
${option.value.map(this._optionTemplate.bind(this))} ${option.value.map(this._optionTemplate.bind(this))}
<sl-divider></sl-divider> <sl-divider></sl-divider>
`; `;

View File

@ -18,6 +18,9 @@ export interface SelectOption
// Show the option, but it is not selectable. // Show the option, but it is not selectable.
// If multiple=true and the option is in the value, it is not removable. // If multiple=true and the option is in the value, it is not removable.
disabled? : boolean; disabled? : boolean;
// If a search is in progress, does this option match.
// Automatically changed.
isMatch? : boolean;
} }
/** /**

File diff suppressed because it is too large Load Diff

View File

@ -7,14 +7,13 @@
* @author Nathan Gray * @author Nathan Gray
*/ */
import {css, CSSResultGroup, html, LitElement, nothing, render, TemplateResult} from "lit";
import {css, html, LitElement, render, SlotMixin} from "@lion/core";
import {cleanSelectOptions, SelectOption} from "./FindSelectOptions"; import {cleanSelectOptions, SelectOption} from "./FindSelectOptions";
import {Validator} from "@lion/form-core"; import {Validator} from "@lion/form-core";
import {Et2Tag} from "./Tag/Et2Tag"; import {Et2Tag} from "./Tag/Et2Tag";
import {SlMenuItem} from "@shoelace-style/shoelace"; import {SlMenuItem} from "@shoelace-style/shoelace";
import {waitForEvent} from "@shoelace-style/shoelace/dist/internal/event";
import {StaticOptions} from "./StaticOptions"; import {StaticOptions} from "./StaticOptions";
import {dedupeMixin} from "@open-wc/dedupe-mixin";
// Otherwise import gets stripped // Otherwise import gets stripped
let keep_import : Et2Tag; let keep_import : Et2Tag;
@ -66,6 +65,13 @@ export declare class SearchMixinInterface
* Check a [local] item to see if it matches * Check a [local] item to see if it matches
*/ */
searchMatch(search : string, options : object, item : LitElement) : boolean searchMatch(search : string, options : object, item : LitElement) : boolean
/**
* Additional customisation location, where we stick the search elements
*
* @type {TemplateResult}
*/
_extraTemplate : TemplateResult
} }
/** /**
@ -74,9 +80,9 @@ export declare class SearchMixinInterface
* *
* Currently I assume we're extending an Et2Select, so changes may need to be made for better abstraction * Currently I assume we're extending an Et2Select, so changes may need to be made for better abstraction
*/ */
export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass : T) => export const Et2WithSearchMixin = dedupeMixin(<T extends Constructor<LitElement>>(superclass : T) =>
{ {
class Et2WidgetWithSearch extends SlotMixin(superclass) class Et2WidgetWithSearch extends superclass
{ {
static get properties() static get properties()
{ {
@ -105,54 +111,18 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
} }
} }
static get styles() static get styles() : CSSResultGroup
{ {
return [ return [
// @ts-ignore // @ts-ignore
...(super.styles ? (Symbol.iterator in Object(super.styles) ? super.styles : [super.styles]) : []), ...(super.styles ? (Symbol.iterator in Object(super.styles) ? super.styles : [super.styles]) : []),
css` css`
/* Move the widget border
.form-control-input {
border: solid var(--sl-input-border-width) var(--sl-input-border-color);
border-radius: var(--sl-input-border-radius-medium);
}
.form-control-input:hover {
background-color: var(--sl-input-background-color-hover);
border-color: var(--sl-input-border-color-hover);
color: var(--sl-input-color-hover);
}
.select--standard .select__control {
border-style: none;
}
/* Move focus highlight */
.form-control-input:focus-within {
box-shadow: var(--sl-focus-ring);
}
.select--standard.select--focused:not(.select--disabled) .select__control {
box-shadow: initial;
}
/* Show / hide SlSelect icons - dropdown arrow, etc but not loading spinner */
:host([allowFreeEntries]) ::slotted(sl-icon[slot="suffix"]) {
display: none;
}
/* Make search textbox take full width */
::slotted(.search_input), ::slotted(.search_input) input, .search_input, .search_input input {
width: 100%;
}
.search_input input {
flex: 1 1 auto;
width: 100%;
}
/* Full width search textbox covers loading spinner, lift it up */ /* Full width search textbox covers loading spinner, lift it up */
::slotted(sl-spinner) { ::slotted(sl-spinner) {
z-index: 2; z-index: 2;
} }
/* Don't show the current value while searching for single, we want the space
This lets the current value shrink to nothing so the input can expand
*/
.select__label {
flex: 1 15 auto;
}
/* Show edit textbox only when editing */ /* Show edit textbox only when editing */
.search_input #edit { .search_input #edit {
display: none; display: none;
@ -163,36 +133,55 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
.search_input.editing #edit { .search_input.editing #edit {
display: initial; display: initial;
} }
:host([search]:not([multiple])) .select--open .select__prefix {
:host([search]) sl-select[open]::part(prefix), :host([allowfreeentries]) sl-select[open]::part(prefix) {
order: 9;
flex: 2 1 auto; flex: 2 1 auto;
flex-wrap: wrap;
width: 100%; width: 100%;
} }
:host([search]:not([multiple])) .select--open .select__label {
margin: 0px; :host([search]) sl-select[open]::part(display-input), :host([allowfreeentries]) sl-select[open]::part(display-input) {
}
:host([allowfreeentries]:not([multiple])) .select--standard.select--open:not(.select--disabled) .select__control .select__prefix {
flex: 1 1 auto;
}
:host([allowfreeentries]:not([multiple])) .select--standard.select--open:not(.select--disabled) .select__control .select__label {
display: none; display: none;
} }
:host([search][multiple]) sl-select[open]::part(expand-icon) {
display: none;
}
:host([multiple]) sl-select[open]::part(tags) {
flex-basis: 100%;
}
:host([multiple]) sl-select[open]::part(combobox) {
flex-flow: wrap;
}
/* Search textbox general styling, starts hidden */ /* Search textbox general styling, starts hidden */
.select__prefix ::slotted(.search_input), .search_input { .search_input {
display: none; display: none;
/* See also etemplate2.css, searchbox border turned off in there */
border: none;
flex: 1 1 auto; flex: 1 1 auto;
order: 2;
margin-left: 0px; margin-left: 0px;
width: 100%;
height: var(--sl-input-height-medium); height: var(--sl-input-height-medium);
position: absolute; width: 100%;
background-color: white; background-color: white;
z-index: var(--sl-z-index-dropdown); z-index: var(--sl-z-index-dropdown);
} }
:host([search]) et2-textbox::part(base) {
border: none;
box-shadow: none;
}
/* Search UI active - show textbox & stuff */ /* Search UI active - show textbox & stuff */
::slotted(.search_input.active), .search_input.active, .search_input.active,
.search_input.editing { .search_input.editing {
display: flex; display: flex;
} }
@ -204,7 +193,8 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
} }
/* Hide options that do not match current search text */ /* Hide options that do not match current search text */
::slotted(.no-match) {
.no-match {
display: none; display: none;
} }
/* Different cursor for editable tags */ /* Different cursor for editable tags */
@ -221,10 +211,6 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
:host([readonly]) .form-control-input:focus-within { :host([readonly]) .form-control-input:focus-within {
box-shadow: none; box-shadow: none;
} }
/* no menu */
:host([readonly]) sl-menu {
display: none;
}
/* normal cursor */ /* normal cursor */
:host([readonly]) .select__control { :host([readonly]) .select__control {
cursor: initial; cursor: initial;
@ -264,6 +250,11 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
// Hold the original option data from earlier search results, since we discard on subsequent search // Hold the original option data from earlier search results, since we discard on subsequent search
private _selected_remote = <SelectOption[]>[]; private _selected_remote = <SelectOption[]>[];
// Hold current search results, selected or otherwise
private _remote_options = <SelectOption[]>[];
private _total_result_count = 0;
/** /**
* These characters will end a free tag * These characters will end a free tag
* @type {string[]} * @type {string[]}
@ -283,7 +274,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
// Hiding the selected options from the dropdown means we can't un-select the tags // Hiding the selected options from the dropdown means we can't un-select the tags
// hidden by the max limit. Prefer no limit. // hidden by the max limit. Prefer no limit.
this.maxTagsVisible = -1; this.maxOptionsVisible = -1;
this.validators = []; this.validators = [];
/** /**
@ -297,16 +288,19 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
*/ */
this.defaultValidators = []; this.defaultValidators = [];
this.handleMenuSelect = this.handleMenuSelect.bind(this); this.handleOptionClick = this.handleOptionClick.bind(this);
this._handleChange = this._handleChange.bind(this); this._handleChange = this._handleChange.bind(this);
this.handleTagEdit = this.handleTagEdit.bind(this); this.handleTagEdit = this.handleTagEdit.bind(this);
this._handleAfterShow = this._handleAfterShow.bind(this); this._handleAfterShow = this._handleAfterShow.bind(this);
this._handleMenuHide = this._handleMenuHide.bind(this);
this._handleSearchBlur = this._handleSearchBlur.bind(this); this._handleSearchBlur = this._handleSearchBlur.bind(this);
this._handleClear = this._handleClear.bind(this); this._handleClear = this._handleClear.bind(this);
this._handleDoubleClick = this._handleDoubleClick.bind(this); this._handleDoubleClick = this._handleDoubleClick.bind(this);
this._handleSearchAbort = this._handleSearchAbort.bind(this); this._handleSearchAbort = this._handleSearchAbort.bind(this);
this._handleSearchClear = this._handleSearchClear.bind(this);
this._handleSearchChange = this._handleSearchChange.bind(this); this._handleSearchChange = this._handleSearchChange.bind(this);
this._handleSearchKeyDown = this._handleSearchKeyDown.bind(this); this._handleSearchKeyDown = this._handleSearchKeyDown.bind(this);
this._handleSearchMouseDown = this._handleSearchMouseDown.bind(this);
this._handleEditKeyDown = this._handleEditKeyDown.bind(this); this._handleEditKeyDown = this._handleEditKeyDown.bind(this);
this._handlePaste = this._handlePaste.bind(this); this._handlePaste = this._handlePaste.bind(this);
} }
@ -324,7 +318,6 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
return; return;
} }
this._addNodes();
this._bindListeners(); this._bindListeners();
} }
@ -363,7 +356,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
} }
else if(this.allowFreeEntries && this.multiple) else if(this.allowFreeEntries && this.multiple)
{ {
this.value.forEach((e) => this.getValueAsArray().forEach((e) =>
{ {
if(!this.select_options.find(o => o.value == e)) if(!this.select_options.find(o => o.value == e))
{ {
@ -402,7 +395,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
} }
// Normally this should be handled in render(), but we have to add our nodes in // Normally this should be handled in render(), but we have to add our nodes in
this._addNodes(); //this._addNodes();
} }
// Update any tags if edit mode changes // Update any tags if edit mode changes
if(changedProperties.has("editModeEnabled") || changedProperties.has("readonly")) if(changedProperties.has("editModeEnabled") || changedProperties.has("readonly"))
@ -416,54 +409,33 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
} }
} }
/** protected _extraTemplate()
* Add the nodes we need to search - adjust parent shadowDOM
*
* @protected
*/
protected _addNodes()
{ {
if(this._activeControls) if(!this.searchEnabled && !this.editModeEnabled && !this.allowFreeEntries || this.readonly)
{ {
// Already there return nothing;
return;
} }
const div = document.createElement("div"); return html`
div.classList.add("search_input"); ${this._searchInputTemplate()}
render(this._searchInputTemplate(), div); ${this._moreResultsTemplate()}
if(!super.multiple) `;
{
div.slot = "prefix";
this.appendChild(div);
return;
}
super.updateComplete.then(() =>
{
let control = this.shadowRoot.querySelector(".form-control-input");
control.append(div);
});
} }
/** protected _moreResultsTemplate()
* Customise how tags are rendered.
* Override to add edit
*
* @param item
* @protected
*/
protected _createTagNode(item)
{ {
let tag = <Et2Tag>document.createElement(this.tagTag); if(this._total_result_count == 0 || this._total_result_count - this._remote_options.length == 0)
tag.editable = this.editModeEnabled && !this.readonly; {
return nothing;
}
const more = this.egw().lang("%1 more...", this._total_result_count - this._remote_options.length);
return tag; return html`<span class="more">${more}</span>`;
} }
protected _searchInputTemplate() protected _searchInputTemplate()
{ {
let edit = null; let edit = nothing;
if(this.editModeEnabled) if(this.editModeEnabled)
{ {
edit = html`<input id="edit" type="text" part="input" autocomplete="off" style="width:100%" edit = html`<input id="edit" type="text" part="input" autocomplete="off" style="width:100%"
@ -472,16 +444,20 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
@blur=${this.stopEdit.bind(this)} @blur=${this.stopEdit.bind(this)}
/>`; />`;
} }
// I can't figure out how to get this full width via CSS
return html` return html`
<et2-textbox id="search" type="text" part="input" clearable <div class="search_input" slot="prefix">
<et2-textbox id="search" type="text" part="input"
exportparts="base:search__base"
clearable
autocomplete="off" autocomplete="off"
placeholder="${this.egw().lang("search")}" placeholder="${this.egw().lang("search")}"
style="width:100%" style="flex: 1 1 auto;"
@keydown=${this._handleSearchKeyDown} @keydown=${this._handleSearchKeyDown}
@blur=${this._handleSearchBlur} @blur=${this._handleSearchBlur}
@sl-clear=${this._handleSearchClear}
></et2-textbox> ></et2-textbox>
${edit} ${edit}
</div>
`; `;
} }
@ -517,6 +493,10 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
this.querySelector(".search_input"); this.querySelector(".search_input");
} }
protected get optionTag()
{
return 'sl-option';
}
/** /**
* Only local options, excludes server options * Only local options, excludes server options
@ -525,7 +505,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
*/ */
protected get localItems() : NodeList protected get localItems() : NodeList
{ {
return this.querySelectorAll(this.optionTag + ":not(.remote)"); return this.select.querySelectorAll(this.optionTag + ":not(.remote)");
} }
/** /**
@ -535,7 +515,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
*/ */
protected get remoteItems() : NodeList protected get remoteItems() : NodeList
{ {
return this.querySelectorAll(this.optionTag + ".remote"); return this.select?.querySelectorAll(this.optionTag + ".remote") ?? [];
} }
/** /**
@ -545,7 +525,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
*/ */
protected get freeEntries() : NodeList protected get freeEntries() : NodeList
{ {
return this.querySelectorAll(this.optionTag + ".freeEntry"); return this.select?.querySelectorAll(this.optionTag + ".freeEntry") ?? [];
} }
get select_options() : SelectOption[] get select_options() : SelectOption[]
@ -558,6 +538,9 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
// Any kept remote options // Any kept remote options
options = options.concat(this._selected_remote ?? []); options = options.concat(this._selected_remote ?? []);
// Current search results
options = options.concat(this._remote_options ?? []);
if(this.allowFreeEntries) if(this.allowFreeEntries)
{ {
this.freeEntries.forEach((item : SlMenuItem) => this.freeEntries.forEach((item : SlMenuItem) =>
@ -600,11 +583,11 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
{ {
return; return;
} }
// If widget is currently open, we may need to re-calculate search / dropdown positioning // If widget is currently open, we may need to re-calculate search / dropdown positioning
if(this.isOpen) if(this.isOpen)
{ {
this.handleMenuShow(); this._handleMenuShow();
} }
} }
@ -641,7 +624,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
const valueArray = Array.isArray(this.value) ? this.value : (!this.value ? [] : this.value.toString().split(',')); const valueArray = Array.isArray(this.value) ? this.value : (!this.value ? [] : this.value.toString().split(','));
// Check any already found options // Check any already found options
if(Object.values(this.menuItems).filter((option) => valueArray.find(val => val == option.value)).length === 0) if(Object.values(this.getAllOptions()).filter((option) => valueArray.find(val => val == option.value)).length === 0)
{ {
return false; return false;
} }
@ -653,7 +636,9 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
protected _bindListeners() protected _bindListeners()
{ {
this.addEventListener("sl-clear", this._handleClear); this.addEventListener("sl-clear", this._handleClear);
this.addEventListener("sl-show", this._handleMenuShow);
this.addEventListener("sl-after-show", this._handleAfterShow); this.addEventListener("sl-after-show", this._handleAfterShow);
this.addEventListener("sl-hide", this._handleMenuHide);
// Need our own change to catch the change event from search input // Need our own change to catch the change event from search input
this.addEventListener("change", this._handleChange); this.addEventListener("change", this._handleChange);
@ -673,14 +658,16 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
this._searchInputNode?.removeEventListener("change", this._searchInputNode.handleChange); this._searchInputNode?.removeEventListener("change", this._searchInputNode.handleChange);
this._searchInputNode?.addEventListener("change", this._handleSearchChange); this._searchInputNode?.addEventListener("change", this._handleSearchChange);
this.dropdown.querySelector('.select__label').addEventListener("change", this.handleTagEdit); // this.dropdown.querySelector('.select__label').addEventListener("change", this.handleTagEdit);
}); });
} }
protected _unbindListeners() protected _unbindListeners()
{ {
this.removeEventListener("sl-select", this._handleSelect); this.removeEventListener("sl-select", this._handleSelect);
this.removeEventListener("sl-show", this._handleMenuShow);
this.removeEventListener("sl-after-show", this._handleAfterShow); this.removeEventListener("sl-after-show", this._handleAfterShow);
this.removeEventListener("sl-hide", this._handleMenuHide);
this.removeEventListener("sl-clear", this._handleClear) this.removeEventListener("sl-clear", this._handleClear)
this.removeEventListener("change", this._handleChange); this.removeEventListener("change", this._handleChange);
this.removeEventListener("paste", this._handlePaste); this.removeEventListener("paste", this._handlePaste);
@ -688,7 +675,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
this._searchInputNode?.removeEventListener("change", this._handleSearchChange); this._searchInputNode?.removeEventListener("change", this._handleSearchChange);
} }
handleMenuShow() _handleMenuShow()
{ {
if(this.readonly) if(this.readonly)
{ {
@ -698,15 +685,11 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
this._activeControls?.classList.toggle("novalue", this.multiple && this.value == '' || !this.multiple); this._activeControls?.classList.toggle("novalue", this.multiple && this.value == '' || !this.multiple);
// Reset for parent calculations, will be adjusted after if needed // Reset for parent calculations, will be adjusted after if needed
this.dropdown.setAttribute("distance", 0); //this.dropdown.setAttribute("distance", 0);
super.handleMenuShow();
if(this.searchEnabled || this.allowFreeEntries) if(this.searchEnabled || this.allowFreeEntries)
{ {
this._activeControls?.classList.add("active"); this._activeControls?.classList.add("active");
this._searchInputNode.focus();
this._searchInputNode.select();
// Hide edit explicitly since it's so hard via CSS // Hide edit explicitly since it's so hard via CSS
if(this._editInputNode) if(this._editInputNode)
{ {
@ -729,6 +712,12 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
*/ */
_handleAfterShow() _handleAfterShow()
{ {
if(this.searchEnabled || this.allowFreeEntries)
{
this._searchInputNode.focus();
this._searchInputNode.select();
}
return;
// Need to give positioner a chance to position. // Need to give positioner a chance to position.
// If we call it right away, it has not updated. // If we call it right away, it has not updated.
// I haven't found an event or Promise to hook on to // I haven't found an event or Promise to hook on to
@ -749,24 +738,22 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
); );
} }
}, 100); }, 100);
} }
focus() focus()
{ {
this.dropdown?.show().then(() => this.show();
{ this._searchInputNode.focus();
this._searchInputNode.focus();
});
} }
handleMenuHide() _handleMenuHide()
{ {
if(this.readonly) if(this.readonly)
{ {
return; return;
} }
clearTimeout(this._searchTimeout); this.clearSearch();
super.handleMenuHide();
// Reset display // Reset display
if(this._searchInputNode) if(this._searchInputNode)
@ -778,11 +765,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
this._editInputNode.style.display = ""; this._editInputNode.style.display = "";
} }
if(this.searchEnabled || this.allowFreeEntries) this._activeControls?.classList.remove("active");
{
this._activeControls?.classList.remove("active");
this.shadowRoot.querySelector('.select__label').style.display = "";
}
} }
_triggerChange(event) _triggerChange(event)
@ -830,14 +813,14 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
/** /**
* An option was selected * An option was selected
*/ */
handleMenuSelect(event) handleOptionClick(event)
{ {
// Need to keep the remote option - only if selected // Need to keep the remote option - only if selected
if(event.detail.item.classList.contains("remote") && !this.select_options.find(o => o.value == event.detail.item.value)) if(event.target.classList.contains("remote") && !this.select_options.find(o => o.value == event.target.value))
{ {
this._selected_remote.push({...event.detail.item.option}); this._selected_remote.push({...event.target.option});
} }
super.handleMenuSelect(event); super.handleOptionClick(event);
this.updateComplete.then(() => this.updateComplete.then(() =>
{ {
@ -850,12 +833,12 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
// If we were overlapping, reset // If we were overlapping, reset
if(this._activeControls.classList.contains("novalue")) if(this._activeControls.classList.contains("novalue"))
{ {
this.handleMenuShow(); this._handleMenuShow();
this._handleAfterShow(); this._handleAfterShow();
} }
// Scroll the new tag into view // Scroll the new tag into view
if(event.detail && event.detail.item) if(event.detail)
{ {
// Causes sidemenu (calendar) to scroll to top & get stuck // Causes sidemenu (calendar) to scroll to top & get stuck
/* /*
@ -881,17 +864,14 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
_handleClear(e) _handleClear(e)
{ {
// Only keep remote options that are still used // Only keep remote options that are still used
this._selected_remote = this._selected_remote.filter((option) => this.getValueAsArray().indexOf(option.value) !== -1); this._selected_remote = this._selected_remote.filter((option) => this.value.indexOf(option.value) !== -1);
if(!this.multiple && this.searchEnabled) if(!this.multiple && this.searchEnabled)
{ {
this._handleSearchAbort(e); this._handleSearchAbort(e);
// Restore label styling
this.shadowRoot.querySelector("[part='display-label']").style.display = "";
// Start searching again // Start searching again
this.updateComplete.then(() => this.handleMenuShow()) this._handleMenuShow();
} }
} }
@ -906,17 +886,6 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
async _handleSearchBlur(event : FocusEvent) async _handleSearchBlur(event : FocusEvent)
{ {
clearTimeout(this._searchTimeout); clearTimeout(this._searchTimeout);
if(event.relatedTarget && event.relatedTarget instanceof SlMenuItem)
{
return;
}
// Try any value they had in progress
if(this._searchInputNode.value && this.allowFreeEntries)
{
this.createFreeEntry(this._searchInputNode.value);
}
this.clearSearch();
} }
/** /**
@ -928,15 +897,14 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
{ {
clearTimeout(this._searchTimeout); clearTimeout(this._searchTimeout);
this._activeControls?.classList.add("active"); this._activeControls?.classList.add("active");
this.dropdown.show();
// Pass off some keys to select // Pass off some keys to select
if(['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) if(['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key))
{ {
// Strip out hidden non-matching selected & disabled items so key navigation works // Strip out hidden non-matching selected & disabled items so key navigation works
this.menuItems = this.menuItems.filter(i => !i.disabled); // TODO
return super.handleKeyDown(event); return;
} }
event.stopPropagation(); event.stopPropagation();
@ -946,12 +914,12 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
{ {
event.preventDefault(); event.preventDefault();
this._searchInputNode.value = ""; this._searchInputNode.value = "";
this.dropdown.hide().then(async() => this.updateComplete.then(async() =>
{ {
// update sizing / position before getting ready for another one // update sizing / position before getting ready for another one
if(this.multiple) if(this.multiple)
{ {
await this.dropdown.show(); // await this.show();
this._searchInputNode.focus(); this._searchInputNode.focus();
} }
}); });
@ -977,6 +945,17 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
} }
} }
/**
* Combobox listens for mousedown, which interferes with search clear button.
* Here we block it from bubbling
* @param {MouseEvent} event
* @protected
*/
protected _handleSearchMouseDown(event : MouseEvent)
{
event.stopPropagation();
}
protected _handleEditKeyDown(event : KeyboardEvent) protected _handleEditKeyDown(event : KeyboardEvent)
{ {
// Stop propagation, or parent key handler will add again // Stop propagation, or parent key handler will add again
@ -1037,7 +1016,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
// Show a spinner // Show a spinner
let spinner = document.createElement("sl-spinner"); let spinner = document.createElement("sl-spinner");
spinner.slot = "suffix"; spinner.slot = "expand-icon";
this.appendChild(spinner); this.appendChild(spinner);
// Hide clear button // Hide clear button
@ -1048,6 +1027,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
} }
// Clear previous results // Clear previous results
this._total_result_count = 0;
this._clearResults(); this._clearResults();
await this.updateComplete; await this.updateComplete;
@ -1058,7 +1038,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
]).then(() => ]).then(() =>
{ {
// Show no results indicator // Show no results indicator
if(this.menuItems.filter(e => !e.classList.contains("no-match")).length == 0) if(this.getAllOptions().filter(e => !e.classList.contains("no-match")).length == 0)
{ {
let target = this._optionTargetNode || this; let target = this._optionTargetNode || this;
let temp = document.createElement("div"); let temp = document.createElement("div");
@ -1074,13 +1054,6 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
{ {
clear_button.style.display = ""; clear_button.style.display = "";
} }
}).then(() =>
{
// Not sure why this stays hidden if there's no results, but it sticks and hides all results afterward
this.dropdown.shadowRoot.querySelector(".dropdown__panel").removeAttribute("hidden");
// Call our resize stuff explicitly
this._handleAfterShow();
}); });
} }
@ -1128,14 +1101,9 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
n.remove(); n.remove();
} }
}) })
// Not searching anymore, clear flag
// Reset remaining options. It might be faster to re-create instead. this.select_options.map((o) => o.isMatch = null);
this._menuItems.forEach((item) => this.requestUpdate("select_options");
{
item.disabled = item.option?.disabled || false;
item.classList.remove("match");
item.classList.remove("no-match");
});
} }
/** /**
@ -1148,14 +1116,11 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
{ {
return new Promise((resolve) => return new Promise((resolve) =>
{ {
this.localItems.forEach((item) => this.select_options.forEach((option) =>
{ {
let match = this.searchMatch(search, item); option.isMatch = this.searchMatch(search, option);
item.classList.toggle("match", match);
// set disabled so arrow keys step over. Might be a better way to handle that
item.disabled = !match;
item.classList.toggle("no-match", !match);
}) })
this.requestUpdate("select_options");
resolve(); resolve();
}); });
} }
@ -1208,13 +1173,13 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
return option.label.toLowerCase().includes(lower_search) || option.value.includes(search) return option.label.toLowerCase().includes(lower_search) || option.value.includes(search)
}); });
// Limit results // Limit results
const totalCount = filtered.length; this._total_result_count = filtered.length;
if(filtered.length > Et2WidgetWithSearch.RESULT_LIMIT) if(filtered.length > Et2WidgetWithSearch.RESULT_LIMIT)
{ {
filtered.splice(Et2WidgetWithSearch.RESULT_LIMIT); filtered.splice(Et2WidgetWithSearch.RESULT_LIMIT);
} }
// Add the matches // Add the matches
this.processRemoteResults(filtered, totalCount); this.processRemoteResults(filtered);
return filtered; return filtered;
}) })
.catch((_err) => .catch((_err) =>
@ -1252,14 +1217,14 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
{ {
// If results have a total included, pull it out. // If results have a total included, pull it out.
// It will cause errors if left in the results // It will cause errors if left in the results
let total = null; this._total_result_count = results.length;
if(typeof results.total !== "undefined") if(typeof results.total !== "undefined")
{ {
total = results.total; this._total_result_count = results.total;
delete results.total; delete results.total;
} }
let entries = cleanSelectOptions(results); let entries = cleanSelectOptions(results);
this.processRemoteResults(entries, total); this.processRemoteResults(entries);
return entries; return entries;
}); });
} }
@ -1267,68 +1232,36 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
/** /**
* Add in remote results * Add in remote results
* @param results * @param results
* @param totalResults If there are more results than were returned, total number of matches
* @protected * @protected
*/ */
protected processRemoteResults(entries, totalResults = 0) protected processRemoteResults(entries)
{ {
if(!entries?.length) if(!entries?.length)
{ {
return Promise.resolve(); return Promise.resolve();
} }
// Add a "remote" class so we can tell these apart from any local results // Add a "remote" class so we can tell these apart from any local results
entries.forEach((entry) => entry.class = (entry.class || "") + " remote"); entries.forEach((entry) =>
let target = this._optionTargetNode || this;
if(target)
{ {
// Add in remote options, avoiding duplicates entry.class = (entry.class || "") + " remote";
this.select_options.filter(function(item) // Server says it's a match
entry.isMatch = true;
});
// Add in remote options, avoiding duplicates
this.select_options.filter(function(item)
{
let i = entries.findIndex(x => (x.value == item.value));
if(i <= -1)
{ {
let i = entries.findIndex(x => (x.value == item.value)); entries.push(item);
if(i <= -1) }
{ return null;
entries.push(item); });
}
return null;
});
let options = html`${entries.map(this._optionTemplate.bind(this))}`; this._remote_options = entries;
this.requestUpdate("select_options");
/**
* Add in new options.
* Rendering directly into target will remove existing options, which we don't need to do
*/
let temp_target = document.createElement("div");
let resultCount = entries.length;
render(options, temp_target);
return Promise.all(([...temp_target.querySelectorAll(":scope > *")].map(item => item.render)))
.then(() =>
{
temp_target.querySelectorAll(":scope > *").forEach((item) =>
{
// Avoid duplicate error
if(!target.querySelector("[value='" + ('' + item.value).replace(/'/g, "\\\'") + "']"))
{
target.appendChild(item);
}
})
this.handleMenuSlotChange();
})
.then(() =>
{
if(totalResults && totalResults > resultCount)
{
// More results available that were not sent
let count = document.createElement("span")
count.classList.add("remote");
count.textContent = this.egw().lang("%1 more...", totalResults - resultCount);
target.appendChild(count);
}
});
}
} }
/** /**
@ -1339,21 +1272,21 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
* @returns {boolean} * @returns {boolean}
* @protected * @protected
*/ */
protected searchMatch(search, item) : boolean protected searchMatch(search, option : SelectOption) : boolean
{ {
if(!item || !item.value) if(!option || !option.value)
{ {
return false; return false;
} }
if(item.textContent?.toLowerCase().includes(search.toLowerCase())) if(option.label?.toLowerCase().includes(search.toLowerCase()))
{ {
return true; return true;
} }
if(typeof item.value == "string") if(typeof option.value == "string")
{ {
return item.value.includes(search.toLowerCase()); return option.value.includes(search.toLowerCase());
} }
return item.value == search; return option.value == search;
} }
/** /**
@ -1378,16 +1311,21 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
this.requestUpdate('select_options'); this.requestUpdate('select_options');
} }
// Make sure not to double-add // Make sure not to double-add, but wait until the option is there
if(this.multiple && this.value.indexOf(text) == -1) this.updateComplete.then(() =>
{ {
this.value.push(text); if(this.multiple && this.getValueAsArray().indexOf(text) == -1)
} {
else if(!this.multiple && this.value !== text) let value = this.getValueAsArray();
{ value.push(text);
this.value = text; this.value = value;
} }
this.requestUpdate("value"); else if(!this.multiple && this.value !== text)
{
this.value = text;
}
this.requestUpdate("value");
});
// If we were overlapping edit inputbox with the value display, reset // If we were overlapping edit inputbox with the value display, reset
if(!this.readonly && this._activeControls?.classList.contains("novalue")) if(!this.readonly && this._activeControls?.classList.contains("novalue"))
@ -1478,7 +1416,7 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
// type to select will focus matching entries, but we don't want to stop the edit yet // type to select will focus matching entries, but we don't want to stop the edit yet
if(typeof abort == "object" && abort.type == "blur") if(typeof abort == "object" && abort.type == "blur")
{ {
if(abort.relatedTarget?.localName == "sl-menu-item") if(abort.relatedTarget?.localName == this.optionTag)
{ {
return; return;
} }
@ -1530,14 +1468,12 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
this.dropdown.panel.setAttribute("hidden", ""); this.dropdown.panel.setAttribute("hidden", "");
}); });
} }
this.syncItemsFromValue();
} }
protected _handleSearchAbort(e) protected _handleSearchAbort(e)
{ {
this._activeControls.classList.remove("active"); this._activeControls.classList.remove("active");
this.clearSearch(); this.clearSearch();
this.syncItemsFromValue();
} }
/** /**
@ -1552,7 +1488,14 @@ export const Et2WithSearchMixin = <T extends Constructor<LitElement>>(superclass
e.preventDefault(); e.preventDefault();
return false; return false;
} }
protected _handleSearchClear(e)
{
e.stopImmediatePropagation();
e.preventDefault();
this.clearSearch();
}
} }
return Et2WidgetWithSearch as unknown as Constructor<SearchMixinInterface> & T; return Et2WidgetWithSearch as unknown as Constructor<SearchMixinInterface> & T;
} });

View File

@ -7,13 +7,13 @@
* @author Ralf Becker <rb@egroupware.org> * @author Ralf Becker <rb@egroupware.org>
*/ */
import {Et2Select} from "./Et2Select"; import {Et2Select} from "../Et2Select";
import {cleanSelectOptions, SelectOption} from "./FindSelectOptions"; import {cleanSelectOptions, SelectOption} from "../FindSelectOptions";
import {SelectAccountMixin} from "./SelectAccountMixin"; import {SelectAccountMixin} from "../SelectAccountMixin";
import {Et2StaticSelectMixin} from "./StaticOptions"; import {Et2StaticSelectMixin} from "../StaticOptions";
import {html, nothing} from "@lion/core"; import {html, nothing} from "lit";
export type AccountType = 'accounts'|'groups'|'both'|'owngroups'; export type AccountType = 'accounts' | 'groups' | 'both' | 'owngroups';
/** /**
* @customElement et2-select-account * @customElement et2-select-account
@ -58,17 +58,16 @@ export class Et2SelectAccount extends SelectAccountMixin(Et2StaticSelectMixin(Et
{ {
if(this.accountType === 'both') if(this.accountType === 'both')
{ {
fetch.push(this.egw().accounts('accounts').then(options => {this.static_options = this.static_options.concat(cleanSelectOptions(options))})); fetch.push(this.egw().accounts('accounts').then(options => {this._static_options = this._static_options.concat(cleanSelectOptions(options))}));
} }
fetch.push(this.egw().accounts('owngroups').then(options => {this.static_options = this.static_options.concat(cleanSelectOptions(options))})); fetch.push(this.egw().accounts('owngroups').then(options => {this._static_options = this._static_options.concat(cleanSelectOptions(options))}));
} }
else else
{ {
fetch.push(this.egw().accounts(this.accountType).then(options => {this.static_options = this.static_options.concat(cleanSelectOptions(options))})); fetch.push(this.egw().accounts(this.accountType).then(options => {this._static_options = this._static_options.concat(cleanSelectOptions(options))}));
} }
this.fetchComplete = Promise.all(fetch) this.fetchComplete = Promise.all(fetch);
.then(() => this._renderOptions());
} }
@ -102,12 +101,7 @@ export class Et2SelectAccount extends SelectAccountMixin(Et2StaticSelectMixin(Et
{ {
return []; return [];
} }
let select_options : Array<SelectOption> = [...(this.static_options || []), ...super.select_options]; return super.select_options;
return select_options.filter((value, index, self) =>
{
return self.findIndex(v => v.value === value.value) === index;
});
} }
set select_options(new_options : SelectOption[]) set select_options(new_options : SelectOption[])

View File

@ -0,0 +1,17 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions as so} from "../StaticOptions";
import {cleanSelectOptions} from "../FindSelectOptions";
export class Et2SelectApp extends Et2StaticSelectMixin(Et2Select)
{
public connectedCallback()
{
super.connectedCallback()
this.fetchComplete = so.app(this, {}).then((options) =>
{
this.set_static_options(cleanSelectOptions(options));
})
}
}
customElements.define("et2-select-app", Et2SelectApp);

View File

@ -0,0 +1,26 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin} from "../StaticOptions";
export class Et2SelectBitwise extends Et2StaticSelectMixin(Et2Select)
{
/* currently handled server-side */
/*
set value(new_value)
{
let oldValue = this._value;
let expanded_value = [];
let options = this.select_options;
for(let index in options)
{
let right = parseInt(options[index].value);
if(!!(new_value & right))
{
expanded_value.push(right);
}
}
super.value = expanded_value;
}
*/
}
customElements.define("et2-select-bitwise", Et2SelectBitwise);

View File

@ -0,0 +1,28 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions} from "../StaticOptions";
export class Et2SelectBool extends Et2StaticSelectMixin(Et2Select)
{
constructor()
{
super();
this._static_options = StaticOptions.bool(this);
}
get value()
{
return super.value;
}
/**
* Boolean option values are "0" and "1", so change boolean to those
* @param {string | string[]} new_value
*/
set value(new_value)
{
super.value = new_value ? "1" : "0";
}
}
customElements.define("et2-select-bool", Et2SelectBool);

View File

@ -8,10 +8,13 @@
*/ */
import {css, PropertyValues} from "@lion/core"; import {css, html, nothing, PropertyValues, TemplateResult, unsafeCSS} from "lit";
import {Et2Select} from "./Et2Select"; import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions as so} from "./StaticOptions"; import {Et2StaticSelectMixin, StaticOptions as so} from "../StaticOptions";
import {cleanSelectOptions} from "./FindSelectOptions"; import {cleanSelectOptions} from "../FindSelectOptions";
import {StaticValue} from "lit/development/static-html";
import {literal} from "lit/static-html.js";
import {repeat} from "lit/directives/repeat.js";
/** /**
* Customised Select widget for categories * Customised Select widget for categories
@ -25,12 +28,14 @@ export class Et2SelectCategory extends Et2StaticSelectMixin(Et2Select)
...super.styles, ...super.styles,
css` css`
/* Category color on options */ /* Category color on options */
::slotted(*) {
sl-option {
border-left: 6px solid var(--category-color, transparent); border-left: 6px solid var(--category-color, transparent);
} }
/* Border on the (single) selected value */ /* Border on the (single) selected value */
:host(.hasValue:not([multiple])) .select--standard .select__control {
border-left: 6px solid var(--sl-input-border-color); :host(:not([multiple]))::part(combobox) {
border-left: 6px solid var(--category-color, var(--sl-input-border-color));
} }
` `
] ]
@ -73,13 +78,6 @@ export class Et2SelectCategory extends Et2StaticSelectMixin(Et2Select)
(this.getInstanceManager() && this.getInstanceManager().app) || (this.getInstanceManager() && this.getInstanceManager().app) ||
this.egw().app_name(); this.egw().app_name();
} }
// If app passes options (addressbook index) we'll use those instead.
// They will be found automatically by update() after ID is set.
await this.updateComplete;
if(this.select_options.length == 0)
{
}
} }
@ -91,72 +89,57 @@ export class Et2SelectCategory extends Et2StaticSelectMixin(Et2Select)
{ {
this.fetchComplete = so.cat(this).then(options => this.fetchComplete = so.cat(this).then(options =>
{ {
this.static_options = cleanSelectOptions(options); this._static_options = cleanSelectOptions(options);
this.requestUpdate("select_options"); this.requestUpdate("select_options");
}); });
} }
if(changedProperties.has("value") || changedProperties.has('select_options'))
{
this.doLabelChange()
}
} }
/**
* Override from parent (SlSelect) to customise display of the current value. protected handleValueChange(e)
* Here's where we add the icon & color border
*/
doLabelChange()
{ {
// Update the display label when checked menu item's label changes super.handleValueChange(e);
if(this.multiple)
{
return;
}
const checkedItem = this.menuItems.find(item => item.value === this.value); // Just re-draw to get the colors & icon
this.displayLabel = checkedItem ? checkedItem.textContent : ''; this.requestUpdate();
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") || "";
}
} }
/** /**
* Render select_options as child DOM Nodes * Used to render each option into the select
* Overridden for colors
* *
* Overridden here so we can re-do the displayed label after first load of select options. * @param {SelectOption} option
* Initial load order / lifecycle does not have all the options at the right time * @returns {TemplateResult}
* @protected
*/ */
protected _renderOptions() public render() : TemplateResult
{ {
// @ts-ignore Doesn't know about Et2WidgetWithSelectMixin._renderOptions() /** CSS variables are not making it through to options, re-declaring them here works */
return super._renderOptions().then(() => return html`
{ <style>
// @ts-ignore Doesn't know about SlSelect.menuItems ${repeat(this.select_options, (option) =>
if(this.menuItems.length > 0) {
{ if(typeof option.color == "undefined" || !option.color)
this.doLabelChange(); {
} return nothing;
}); }
return unsafeCSS(
(this.getValueAsArray().includes(option.value) ? "::part(combobox) { --category-color: " + option.color + ";}" : "") +
".cat_" + option.value + " {--category-color: " + option.color + ";}"
);
})}
</style>
${super.render()}
`;
} }
/** /**
* Use a custom tag for when multiple=true * Use a custom tag for when multiple=true
* *
* @returns {string} * @returns {string}
*/ */
get tagTag() : string public get tagTag() : StaticValue
{ {
return "et2-category-tag"; return literal`et2-category-tag`;
} }
/** /**

View File

@ -8,10 +8,10 @@
*/ */
import {Et2Select} from "./Et2Select"; import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions as so} from "./StaticOptions"; import {Et2StaticSelectMixin, StaticOptions as so} from "../StaticOptions";
import {egw} from "../../jsapi/egw_global"; import {egw} from "../../../jsapi/egw_global";
import {SelectOption} from "./FindSelectOptions"; import {SelectOption} from "../FindSelectOptions";
/** /**
* Customised Select widget for countries * Customised Select widget for countries
@ -38,7 +38,7 @@ export class Et2SelectCountry extends Et2StaticSelectMixin(Et2Select)
(<Promise<SelectOption[]>>so.country(this, {}, true)).then(options => (<Promise<SelectOption[]>>so.country(this, {}, true)).then(options =>
{ {
this.static_options = options this._static_options = options
this.requestUpdate("select_options"); this.requestUpdate("select_options");
}); });
} }

View File

@ -0,0 +1,14 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions as so} from "../StaticOptions";
export class Et2SelectDay extends Et2StaticSelectMixin(Et2Select)
{
constructor()
{
super();
this._static_options = so.day(this, {});
}
}
customElements.define("et2-select-day", Et2SelectDay);

View File

@ -0,0 +1,53 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions} from "../StaticOptions";
import {cleanSelectOptions} from "../FindSelectOptions";
export class Et2SelectDayOfWeek extends Et2StaticSelectMixin(Et2Select)
{
connectedCallback()
{
super.connectedCallback();
// Wait for connected instead of constructor because attributes make a difference in
// which options are offered
this.fetchComplete = StaticOptions.dow(this, {other: this.other || []}).then(options =>
{
this.set_static_options(cleanSelectOptions(options));
});
}
set value(new_value)
{
let expanded_value = typeof new_value == "object" ? new_value : [];
if(new_value && (typeof new_value == "string" || typeof new_value == "number"))
{
let int_value = parseInt(new_value);
this.updateComplete.then(() =>
{
this.fetchComplete.then(() =>
{
let options = this.select_options;
for(let index in options)
{
let right = parseInt(options[index].value);
if((int_value & right) == right)
{
expanded_value.push("" + right);
}
}
super.value = expanded_value;
})
});
return;
}
super.value = expanded_value;
}
get value()
{
return super.value;
}
}
customElements.define("et2-select-dow", Et2SelectDayOfWeek);

View File

@ -7,11 +7,12 @@
* @author Nathan Gray * @author Nathan Gray
*/ */
import {Et2Select} from "./Et2Select"; import {Et2Select} from "../Et2Select";
import {css, html, nothing, PropertyValues} from "@lion/core"; import {css, html, nothing, PropertyValues} from "lit";
import {IsEmail} from "../Validators/IsEmail"; import {IsEmail} from "../../Validators/IsEmail";
import interact from "@interactjs/interact"; import interact from "@interactjs/interact";
import {Validator} from "@lion/form-core"; import {Validator} from "@lion/form-core";
import {classMap} from "lit/directives/class-map.js";
/** /**
* Select email address(es) * Select email address(es)
@ -100,6 +101,7 @@ export class Et2SelectEmail extends Et2Select
this.defaultValidators.push(new IsEmail(this.allowPlaceholder)); this.defaultValidators.push(new IsEmail(this.allowPlaceholder));
} }
/** @param {import('@lion/core').PropertyValues } changedProperties */ /** @param {import('@lion/core').PropertyValues } changedProperties */
willUpdate(changedProperties : PropertyValues) willUpdate(changedProperties : PropertyValues)
{ {
@ -112,6 +114,40 @@ export class Et2SelectEmail extends Et2Select
} }
} }
updated(changedProperties : Map<string, any>)
{
// Make tags draggable
if(!this.readonly && this.allowFreeEntries && this.allowDragAndDrop)
{
let dragTranslate = {x: 0, y: 0};
const tags = this.shadowRoot.querySelectorAll(".select__tags [part='tag']");
let draggable = interact(tags).draggable({
startAxis: 'xy',
listeners: {
start: function(e)
{
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)`;
},
move: function(e)
{
dragTranslate.x += e.delta.x;
dragTranslate.y += e.delta.y;
e.target.style.transform =
`translate(${dragTranslate.x}px, ${dragTranslate.y}px)`;
}
}
});
// set parent_node with widget context in order to make it accessible after drop
draggable.parent_node = this;
}
}
connectedCallback() connectedCallback()
{ {
super.connectedCallback(); super.connectedCallback();
@ -145,25 +181,7 @@ export class Et2SelectEmail extends Et2Select
} }
}); });
} }
/**
* 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. * Actually query the server.
* *
@ -187,55 +205,21 @@ export class Et2SelectEmail extends Et2Select
* *
* @returns {string} * @returns {string}
*/ */
get tagTag() : string _tagTemplate(option, index)
{ {
return "et2-email-tag"; return html`
} <et2-email-tag
class=${classMap({
/** ...option.classList,
* override tag creation in order to add DND functionality "et2-select-draggable": !this.readonly && this.allowFreeEntries && this.allowDragAndDrop
* @param item })}
* @protected ?.fullEmail=${this.fullEmail}
*/ ?.onlyEmail=${this.onlyEmail}
protected _createTagNode(item) value=${option.value}
{ >
let tag = super._createTagNode(item); ${option.getTextLabel().trim()}
</et2-email-tag>
tag.fullEmail = this.fullEmail; `;
tag.onlyEmail = this.onlyEmail;
// Re-set after setting fullEmail as that can change what we show
tag.textContent = item.getTextLabel().trim();
if(!this.readonly && this.allowFreeEntries && this.allowDragAndDrop)
{
let dragTranslate = {x: 0, y: 0};
tag.class = item.classList.value + " et2-select-draggable";
let draggable = interact(tag).draggable({
startAxis: 'xy',
listeners: {
start: function(e)
{
let dragPosition = {x:e.page.x, y:e.page.y};
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)`;
},
move : function(e)
{
dragTranslate.x += e.delta.x;
dragTranslate.y += e.delta.y;
e.target.style.transform =
`translate(${dragTranslate.x}px, ${dragTranslate.y}px)`;
}
}
});
// set parent_node with widget context in order to make it accessible after drop
draggable.parent_node = this;
}
return tag;
} }
/** /**

View File

@ -0,0 +1,14 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions} from "../StaticOptions";
export class Et2SelectHour extends Et2StaticSelectMixin(Et2Select)
{
constructor()
{
super();
this._static_options = StaticOptions.hour(this, {other: this.other || []});
}
}
customElements.define("et2-select-hour", Et2SelectHour);

View File

@ -0,0 +1,14 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions} from "../StaticOptions";
export class Et2SelectLang extends Et2StaticSelectMixin(Et2Select)
{
constructor()
{
super();
this._static_options = StaticOptions.lang(this, {other: this.other || []});
}
}
customElements.define("et2-select-lang", Et2SelectLang);

View File

@ -0,0 +1,14 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions} from "../StaticOptions";
export class Et2SelectMonth extends Et2StaticSelectMixin(Et2Select)
{
constructor()
{
super();
this._static_options = StaticOptions.month(this);
}
}
customElements.define("et2-select-month", Et2SelectMonth);

View File

@ -0,0 +1,52 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions} from "../StaticOptions";
import {PropertyValues} from 'lit';
export class Et2SelectNumber extends Et2StaticSelectMixin(Et2Select)
{
static get properties()
{
return {
...super.properties,
/**
* Step between numbers
*/
interval: {type: Number},
min: {type: Number},
max: {type: Number},
/**
* Add one or more leading zeros
* Set to how many zeros you want (000)
*/
leading_zero: {type: String},
/**
* Appended after every number
*/
suffix: {type: String}
}
}
constructor()
{
super();
this.min = 1;
this.max = 10;
this.interval = 1;
this.leading_zero = "";
this.suffix = "";
}
updated(changedProperties : PropertyValues)
{
super.updated(changedProperties);
if(changedProperties.has('min') || changedProperties.has('max') || changedProperties.has('interval') || changedProperties.has('suffix'))
{
this._static_options = StaticOptions.number(this);
this.requestUpdate("select_options");
}
}
}
customElements.define("et2-select-number", Et2SelectNumber);

View File

@ -0,0 +1,15 @@
import {Et2SelectNumber} from "./Et2SelectNumber";
export class Et2SelectPercent extends Et2SelectNumber
{
constructor()
{
super();
this.min = 0;
this.max = 100;
this.interval = 10;
this.suffix = "%%";
}
}
customElements.define("et2-select-percent", Et2SelectPercent);

View File

@ -0,0 +1,14 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions} from "../StaticOptions";
export class Et2SelectPriority extends Et2StaticSelectMixin(Et2Select)
{
constructor()
{
super();
this._static_options = StaticOptions.priority(this);
}
}
customElements.define("et2-select-priority", Et2SelectPriority);

View File

@ -8,12 +8,13 @@
*/ */
import {css, html, LitElement, repeat, TemplateResult} from "@lion/core"; import {css, html, LitElement, TemplateResult} from "lit";
import {et2_IDetachedDOM} from "../et2_core_interfaces"; import {repeat} from "lit/directives/repeat.js";
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {et2_IDetachedDOM} from "../../et2_core_interfaces";
import {Et2StaticSelectMixin, StaticOptions, StaticOptions as so} from "./StaticOptions"; import {Et2Widget} from "../../Et2Widget/Et2Widget";
import {cleanSelectOptions, find_select_options, SelectOption} from "./FindSelectOptions"; import {Et2StaticSelectMixin, StaticOptions, StaticOptions as so} from "../StaticOptions";
import {SelectAccountMixin} from "./SelectAccountMixin"; import {cleanSelectOptions, find_select_options, SelectOption} from "../FindSelectOptions";
import {SelectAccountMixin} from "../SelectAccountMixin";
/** /**
* This is a stripped-down read-only widget used in nextmatch * This is a stripped-down read-only widget used in nextmatch
@ -143,6 +144,11 @@ li {
return this.value; return this.value;
} }
getValueAsArray()
{
return (Array.isArray(this.value) ? this.value : [this.value]);
}
set value(new_value : string | string[]) set value(new_value : string | string[])
{ {
// Split anything that is still a CSV // Split anything that is still a CSV
@ -206,10 +212,11 @@ li {
render() render()
{ {
const value = this.getValueAsArray();
return html` return html`
<ul> <ul>
${repeat( ${repeat(
(Array.isArray(this.value) ? this.value : [this.value]), this.getValueAsArray(),
(val : string) => val, (val) => (val : string) => val, (val) =>
{ {
let option = (<SelectOption[]>this.select_options).find(option => option.value == val); let option = (<SelectOption[]>this.select_options).find(option => option.value == val);
@ -282,14 +289,16 @@ customElements.define("et2-select-app_ro", Et2SelectAppReadonly);
export class Et2SelectBitwiseReadonly extends Et2SelectReadonly export class Et2SelectBitwiseReadonly extends Et2SelectReadonly
{ {
/* Currently handled server side, we get an array
render() render()
{ {
let new_value = []; let new_value = [];
let int_value = parseInt(this.value);
for(let index in this.select_options) for(let index in this.select_options)
{ {
let option = this.select_options[index]; let option = this.select_options[index];
let right = parseInt(option && option.value ? option.value : index); let right = parseInt(option && option.value ? option.value : index);
if(!!(this.value & right)) if(!!(int_value & right))
{ {
new_value.push(right); new_value.push(right);
} }
@ -307,6 +316,8 @@ export class Et2SelectBitwiseReadonly extends Et2SelectReadonly
})} })}
</ul>`; </ul>`;
} }
*/
} }
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement // @ts-ignore TypeScript is not recognizing that this widget is a LitElement
@ -349,7 +360,6 @@ export class Et2SelectPercentReadonly extends Et2SelectReadonly
constructor() constructor()
{ {
super(...arguments); super(...arguments);
this.suffix = "%%";
this.select_options = so.percent(this); this.select_options = so.percent(this);
} }
} }
@ -391,6 +401,23 @@ export class Et2SelectDayOfWeekReadonly extends Et2StaticSelectMixin(Et2SelectRe
this.set_static_options(cleanSelectOptions(options)); this.set_static_options(cleanSelectOptions(options));
}); });
} }
getValueAsArray()
{
let expanded_value = [];
let int_value = parseInt(this.value);
let options = this.select_options;
for(let index in options)
{
let right = parseInt(<string>options[index].value);
if((int_value & right) == right)
{
expanded_value.push("" + right);
}
}
return expanded_value;
}
} }
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement // @ts-ignore TypeScript is not recognizing that this widget is a LitElement
@ -422,7 +449,7 @@ export class Et2SelectNumberReadonly extends Et2StaticSelectMixin(Et2SelectReado
{ {
protected find_select_options(_attrs) protected find_select_options(_attrs)
{ {
this.static_options = so.number(this, _attrs); this._static_options = so.number(this, _attrs);
} }
} }

View File

@ -0,0 +1,45 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions} from "../StaticOptions";
import {SelectOption} from "../FindSelectOptions";
export class Et2SelectState extends Et2StaticSelectMixin(Et2Select)
{
/**
* Two-letter ISO country code
*/
protected __countryCode;
static get properties()
{
return {
...super.properties,
countryCode: String,
}
}
constructor()
{
super();
this.countryCode = 'DE';
}
get countryCode()
{
return this.__countryCode;
}
set countryCode(code : string)
{
this.__countryCode = code;
this._static_options = <SelectOption[]>StaticOptions.state(this, {country_code: code});
this.requestUpdate("select_options");
}
set_country_code(code)
{
this.countryCode = code;
}
}
customElements.define("et2-select-state", Et2SelectState);

View File

@ -0,0 +1,61 @@
import {Et2SelectApp} from "./Et2SelectApp";
import {SelectOption} from "../FindSelectOptions";
export class Et2SelectTab extends Et2SelectApp
{
constructor()
{
super();
this.allowFreeEntries = true;
}
set value(new_value)
{
if(!new_value)
{
super.value = new_value;
return;
}
const values = Array.isArray(new_value) ? new_value : [new_value];
const options = this.select_options;
values.forEach(value =>
{
if(!options.filter(option => option.value == value).length)
{
const matches = value.match(/^([a-z0-9]+)\-/i);
let option : SelectOption = {value: value, label: value};
if(matches)
{
option = options.filter(option => option.value == matches[1])[0] || {
value: value,
label: this.egw().lang(matches[1])
};
option.value = value;
option.label += ' ' + this.egw().lang('Tab');
}
try
{
const app = opener?.framework.getApplicationByName(value);
if(app && app.displayName)
{
option.label = app.displayName;
}
}
catch(e)
{
// ignore security exception, if opener is not accessible
}
this.select_options.concat(option);
}
})
super.value = new_value;
}
get value()
{
return super.value;
}
}
customElements.define("et2-select-tab", Et2SelectTab);

View File

@ -7,9 +7,9 @@
* @author Nathan Gray * @author Nathan Gray
*/ */
import {Et2Select} from "./Et2Select"; import {Et2Select} from "../Et2Select";
import {css} from "@lion/core"; import {css} from "lit";
import {SelectOption} from "./FindSelectOptions"; import {SelectOption} from "../FindSelectOptions";
export class Et2SelectThumbnail extends Et2Select export class Et2SelectThumbnail extends Et2Select
{ {

View File

@ -0,0 +1,14 @@
import {Et2Select} from "../Et2Select";
import {Et2StaticSelectMixin, StaticOptions} from "../StaticOptions";
export class Et2SelectTimezone extends Et2StaticSelectMixin(Et2Select)
{
constructor()
{
super();
this._static_options = StaticOptions.timezone(this, {other: this.other || []});
}
}
customElements.define("et2-select-timezone", Et2SelectTimezone);

View File

@ -0,0 +1,25 @@
import {Et2SelectNumber} from "./Et2SelectNumber";
import {PropertyValues} from "lit";
import {StaticOptions} from "../StaticOptions";
export class Et2SelectYear extends Et2SelectNumber
{
constructor()
{
super();
this.min = -3;
this.max = 2;
}
updated(changedProperties : PropertyValues)
{
super.updated(changedProperties);
if(changedProperties.has('min') || changedProperties.has('max') || changedProperties.has('interval') || changedProperties.has('suffix'))
{
this._static_options = StaticOptions.year(this);
}
}
}
customElements.define("et2-select-year", Et2SelectYear);

View File

@ -1,5 +1,5 @@
import {SelectOption} from "./FindSelectOptions"; import {SelectOption} from "./FindSelectOptions";
import {LitElement} from "@lion/core"; import {LitElement} from "lit";
/** /**
* EGroupware eTemplate2 - SelectAccountMixin * EGroupware eTemplate2 - SelectAccountMixin

View File

@ -0,0 +1,25 @@
/**
* Import all our sub-types
*/
import './Select/Et2SelectAccount';
import './Select/Et2SelectApp';
import './Select/Et2SelectBitwise';
import './Select/Et2SelectBool';
import './Select/Et2SelectCategory';
import './Select/Et2SelectCountry';
import './Select/Et2SelectDay';
import './Select/Et2SelectDayOfWeek';
import './Select/Et2SelectEmail';
import './Select/Et2SelectHour';
import './Select/Et2SelectLang';
import './Select/Et2SelectMonth';
import './Select/Et2SelectNumber';
import './Select/Et2SelectPercent';
import './Select/Et2SelectPriority';
import './Select/Et2SelectReadonly';
import './Select/Et2SelectState';
import './Select/Et2SelectTab';
import './Select/Et2SelectThumbnail';
import './Select/Et2SelectTimezone';
import './Select/Et2SelectYear';

View File

@ -0,0 +1,285 @@
/**
* Some static options, no need to transfer them over and over.
* We still need the same thing on the server side to validate, so they
* have to match. See Etemplate\Widget\Select::typeOptions()
* The type specific legacy options wind up in attrs.other, but should be explicitly
* defined and set.
*
* @param {type} widget
*/
import { sprintf } from "../../egw_action/egw_action_common";
import { cleanSelectOptions, find_select_options } from "./FindSelectOptions";
/**
* Base class for things that have static options
*
* We keep static options separate and concatenate them in to allow for extra options without
* overwriting them when we get static options from the server
*/
export const Et2StaticSelectMixin = (superclass) => {
class Et2StaticSelectOptions extends (superclass) {
constructor(...args) {
super(...args);
this.static_options = [];
this.fetchComplete = Promise.resolve();
// Trigger the options to get rendered into the DOM
this.requestUpdate("select_options");
}
get select_options() {
// @ts-ignore
const options = super.select_options || [];
// make sure result is unique
return [...new Map([...(this.static_options || []), ...options].map(item => [item.value, item])).values()];
}
set select_options(new_options) {
// @ts-ignore IDE doesn't recognise property
super.select_options = new_options;
}
set_static_options(new_static_options) {
this.static_options = new_static_options;
this.requestUpdate("select_options");
}
/**
* Override the parent fix_bad_value() to wait for server-side options
* to come back before we check to see if the value is not there.
*/
fix_bad_value() {
this.fetchComplete.then(() => {
// @ts-ignore Doesn't know it's an Et2Select
if (typeof super.fix_bad_value == "function") {
// @ts-ignore Doesn't know it's an Et2Select
super.fix_bad_value();
}
});
}
}
return Et2StaticSelectOptions;
};
/**
* Some options change, or are too complicated to have twice, so we get the
* options from the server once, then keep them to use if they're needed again.
* We use the options string to keep the different possibilities (eg. categories
* for different apps) separate.
*
* @param {et2_selectbox} widget Selectbox we're looking at
* @param {string} options_string
* @param {Object} attrs Widget attributes (not yet fully set)
* @param {boolean} return_promise true: always return a promise
* @returns {Object[]|Promise<Object[]>} Array of options, or empty and they'll get filled in later, or Promise
*/
export const StaticOptions = new class StaticOptionsType {
cached_server_side(widget, type, options_string, return_promise) {
// normalize options by removing trailing commas
options_string = options_string.replace(/,+$/, '');
const cache_id = widget.nodeName + '_' + options_string;
const cache_owner = widget.egw().getCache('Et2Select');
let cache = cache_owner[cache_id];
if (typeof cache === 'undefined') {
// Fetch with json instead of jsonq because there may be more than
// one widget listening for the response by the time it gets back,
// and we can't do that when it's queued.
const req = widget.egw().json('EGroupware\\Api\\Etemplate\\Widget\\Select::ajax_get_options', [type, options_string, widget.value]).sendRequest();
if (typeof cache === 'undefined') {
cache_owner[cache_id] = req;
}
cache = req;
}
if (typeof cache.then === 'function') {
// pending, wait for it
const promise = cache.then((response) => {
cache = cache_owner[cache_id] = response.response[0].data || undefined;
if (return_promise)
return cache;
// Set select_options in attributes in case we get a response before
// the widget is finished loading (otherwise it will re-set to {})
//widget.select_options = cache;
// Avoid errors if widget is destroyed before the timeout
if (widget && typeof widget.id !== 'undefined') {
if (typeof widget.set_static_options == "function") {
widget.set_static_options(cache);
}
else if (typeof widget.set_select_options == "function") {
widget.set_select_options(find_select_options(widget, {}, cache));
}
}
});
return return_promise ? promise : [];
}
else {
// Check that the value is in there
// Make sure we are not requesting server for an empty value option or
// other widgets but select-timezone as server won't find anything and
// it will fall into an infinitive loop, e.g. select-cat widget.
if (widget.value && widget.value != "" && widget.value != "0" && type == "select-timezone") {
var missing_option = true;
for (var i = 0; i < cache.length && missing_option; i++) {
if (cache[i].value == widget.value) {
missing_option = false;
}
}
// Try again - ask the server with the current value this time
if (missing_option) {
delete cache_owner[cache_id];
return this.cached_server_side(widget, type, options_string);
}
else {
if (widget.value && widget && widget.get_value() !== widget.value) {
egw.window.setTimeout(function () {
// Avoid errors if widget is destroyed before the timeout
if (this.widget && typeof this.widget.id !== 'undefined') {
this.widget.set_value(this.widget.options.value);
}
}.bind({ widget: widget }), 1);
}
}
}
return return_promise ? Promise.resolve(cache) : cache;
}
}
cached_from_file(widget, file) {
const cache_owner = widget.egw().getCache('Et2Select');
let cache = cache_owner[file];
if (typeof cache === 'undefined') {
cache_owner[file] = cache = widget.egw().window.fetch(file)
.then((response) => {
// Get the options
if (!response.ok) {
throw response;
}
return response.json();
})
.then(options => {
var _a;
// Need to clean the options because file may be key=>value, may have option list, may be mixed
cache_owner[file] = (_a = cleanSelectOptions(options)) !== null && _a !== void 0 ? _a : [];
return cache_owner[file];
});
}
else if (cache && typeof cache.then === "undefined") {
return Promise.resolve(cache);
}
return cache;
}
priority(widget) {
return [
{ value: "1", label: 'low' },
{ value: "2", label: 'normal' },
{ value: "3", label: 'high' },
{ value: "0", label: 'undefined' }
];
}
bool(widget) {
return [
{ value: "0", label: 'no' },
{ value: "1", label: 'yes' }
];
}
month(widget) {
return [
{ value: "1", label: 'January' },
{ value: "2", label: 'February' },
{ value: "3", label: 'March' },
{ value: "4", label: 'April' },
{ value: "5", label: 'May' },
{ value: "6", label: 'June' },
{ value: "7", label: 'July' },
{ value: "8", label: 'August' },
{ value: "9", label: 'September' },
{ value: "10", label: 'October' },
{ value: "11", label: 'November' },
{ value: "12", label: 'December' }
];
}
number(widget, attrs = {
min: undefined,
max: undefined,
interval: undefined,
format: undefined
}) {
var _a, _b, _c, _d;
var options = [];
var min = (_a = attrs.min) !== null && _a !== void 0 ? _a : parseFloat(widget.min);
var max = (_b = attrs.max) !== null && _b !== void 0 ? _b : parseFloat(widget.max);
var interval = (_c = attrs.interval) !== null && _c !== void 0 ? _c : parseFloat(widget.interval);
var format = (_d = attrs.format) !== null && _d !== void 0 ? _d : '%d';
// leading zero specified in interval
if (widget.leading_zero && widget.leading_zero[0] == '0') {
format = '%0' + ('' + interval).length + 'd';
}
// Suffix
if (widget.suffix) {
format += widget.egw().lang(widget.suffix);
}
// Avoid infinite loop if interval is the wrong direction
if ((min <= max) != (interval > 0)) {
interval = -interval;
}
for (var i = 0, n = min; n <= max && i <= 100; n += interval, ++i) {
options.push({ value: "" + n, label: sprintf(format, n) });
}
return options;
}
percent(widget) {
return this.number(widget);
}
year(widget, attrs) {
if (typeof attrs != 'object') {
attrs = {};
}
var t = new Date();
attrs.min = t.getFullYear() + parseInt(widget.min);
attrs.max = t.getFullYear() + parseInt(widget.max);
return this.number(widget, attrs);
}
day(widget, attrs) {
attrs.other = [1, 31, 1];
return this.number(widget, attrs);
}
hour(widget, attrs) {
var options = [];
var timeformat = widget.egw().preference('common', 'timeformat');
for (var h = 0; h <= 23; ++h) {
options.push({
value: h,
label: timeformat == 12 ?
((12 ? h % 12 : 12) + ' ' + (h < 12 ? egw.lang('am') : egw.lang('pm'))) :
sprintf('%02d', h)
});
}
return options;
}
app(widget, attrs) {
var options = ',' + (attrs.other || []).join(',');
return this.cached_server_side(widget, 'select-app', options);
}
cat(widget) {
var options = [widget.globalCategories, /*?*/ , widget.application, widget.parentCat];
if (typeof options[3] == 'undefined') {
options[3] = widget.application ||
// When the widget is first created, it doesn't have a parent and can't find it's instanceManager
(widget.getInstanceManager() && widget.getInstanceManager().app) ||
widget.egw().app_name();
}
return this.cached_server_side(widget, 'select-cat', options.join(','), true);
}
country(widget, attrs, return_promise) {
var options = ',';
return this.cached_server_side(widget, 'select-country', options, return_promise);
}
state(widget, attrs) {
var options = attrs.country_code ? attrs.country_code : 'de';
return this.cached_server_side(widget, 'select-state', options);
}
dow(widget, attrs) {
var options = (widget.rows || "") + ',' + (attrs.other || []).join(',');
return this.cached_server_side(widget, 'select-dow', options, true);
}
lang(widget, attrs) {
var options = ',' + (attrs.other || []).join(',');
return this.cached_server_side(widget, 'select-lang', options);
}
timezone(widget, attrs) {
var options = ',' + (attrs.other || []).join(',');
return this.cached_server_side(widget, 'select-timezone', options);
}
};
//# sourceMappingURL=StaticOptions.js.map

View File

@ -8,11 +8,18 @@
* @param {type} widget * @param {type} widget
*/ */
import {sprintf} from "../../egw_action/egw_action_common"; import {sprintf} from "../../egw_action/egw_action_common";
import {Et2SelectReadonly} from "./Et2SelectReadonly"; import {Et2SelectReadonly} from "./Select/Et2SelectReadonly";
import {cleanSelectOptions, find_select_options, SelectOption} from "./FindSelectOptions"; import {cleanSelectOptions, find_select_options, SelectOption} from "./FindSelectOptions";
import {Et2Select, Et2WidgetWithSelect} from "./Et2Select"; import {Et2Select, Et2WidgetWithSelect} from "./Et2Select";
import {state} from "lit/decorators/state.js";
export type Et2SelectWidgets = Et2Select | Et2WidgetWithSelect | Et2SelectReadonly; export type Et2SelectWidgets = Et2Select | Et2WidgetWithSelect | Et2SelectReadonly;
type NumberOptions = {
min? : number,
max? : number,
interval? : number,
format? : string
};
// Export the Interface for TypeScript // Export the Interface for TypeScript
type Constructor<T = {}> = new (...args : any[]) => T; type Constructor<T = {}> = new (...args : any[]) => T;
@ -31,27 +38,25 @@ export const Et2StaticSelectMixin = <T extends Constructor<Et2WidgetWithSelect>>
// Hold the static widget options separately so other options (like sent from server in sel_options) won't // Hold the static widget options separately so other options (like sent from server in sel_options) won't
// conflict or be wiped out // conflict or be wiped out
protected static_options : SelectOption[]; @state()
protected _static_options : SelectOption[] = [];
// If widget needs to fetch options from server, we might want to wait for them // If widget needs to fetch options from server, we might want to wait for them
protected fetchComplete : Promise<SelectOption[] | void>; @state()
protected fetchComplete : Promise<SelectOption[] | void> = Promise.resolve();
constructor(...args) async getUpdateComplete() : Promise<boolean>
{ {
super(...args); const result = await super.getUpdateComplete();
await this.fetchComplete;
this.static_options = []; return result;
this.fetchComplete = Promise.resolve();
// Trigger the options to get rendered into the DOM
this.requestUpdate("select_options");
} }
get select_options() : SelectOption[] get select_options() : SelectOption[]
{ {
// @ts-ignore // @ts-ignore
const options = super.select_options || []; const options = super.select_options || [];
const statics = this.static_options || []; const statics = this._static_options || [];
if(options.length == 0) if(options.length == 0)
{ {
@ -62,7 +67,7 @@ export const Et2StaticSelectMixin = <T extends Constructor<Et2WidgetWithSelect>>
return options; return options;
} }
// Merge & make sure result is unique // Merge & make sure result is unique
return [...new Map([...(this.static_options || []), ...options].map(item => return [...new Map([...(this._static_options || []), ...options].map(item =>
[item.value, item])).values()]; [item.value, item])).values()];
} }
@ -75,7 +80,7 @@ export const Et2StaticSelectMixin = <T extends Constructor<Et2WidgetWithSelect>>
set_static_options(new_static_options) set_static_options(new_static_options)
{ {
this.static_options = new_static_options; this._static_options = new_static_options;
this.requestUpdate("select_options"); this.requestUpdate("select_options");
} }
@ -273,19 +278,14 @@ export const StaticOptions = new class StaticOptionsType
]; ];
} }
number(widget : Et2SelectWidgets, attrs = { number(widget : Et2SelectWidgets, attrs : NumberOptions = {}) : SelectOption[]
min: undefined,
max: undefined,
interval: undefined,
format: undefined
}) : SelectOption[]
{ {
var options = []; const options = [];
var min = parseFloat(attrs.min ?? widget.min ?? 1); const min = parseFloat(attrs.min ?? widget.min ?? 1);
var max = parseFloat(attrs.max ?? widget.max ?? 10); const max = parseFloat(attrs.max ?? widget.max ?? 10);
var interval = parseFloat(attrs.interval ?? widget.interval ?? 1); let interval = parseFloat(attrs.interval ?? widget.interval ?? 1);
var format = attrs.format ?? '%d'; let format = attrs.format ?? '%d';
// leading zero specified in interval // leading zero specified in interval
if(widget.leading_zero && widget.leading_zero[0] == '0') if(widget.leading_zero && widget.leading_zero[0] == '0')
@ -313,7 +313,7 @@ export const StaticOptions = new class StaticOptionsType
percent(widget : Et2SelectWidgets) : SelectOption[] percent(widget : Et2SelectWidgets) : SelectOption[]
{ {
return this.number(widget, {min: 0, max: 100, interval: 10, format: undefined}); return this.number(widget, {min: 0, max: 100, interval: 10, format: "%d%%"});
} }
year(widget : Et2SelectWidgets, attrs?) : SelectOption[] year(widget : Et2SelectWidgets, attrs?) : SelectOption[]
@ -323,15 +323,14 @@ export const StaticOptions = new class StaticOptionsType
attrs = {} attrs = {}
} }
var t = new Date(); var t = new Date();
attrs.min = t.getFullYear() + parseInt(widget.min); attrs.min = t.getFullYear() + parseInt(attrs.min ?? widget.min ?? -3);
attrs.max = t.getFullYear() + parseInt(widget.max); attrs.max = t.getFullYear() + parseInt(attrs.max ?? widget.max ?? 2);
return this.number(widget, attrs); return this.number(widget, attrs);
} }
day(widget : Et2SelectWidgets, attrs) : SelectOption[] day(widget : Et2SelectWidgets, attrs) : SelectOption[]
{ {
attrs.other = [1, 31, 1]; return this.number(widget, {min: 1, max: 31, interval: 1});
return this.number(widget, attrs);
} }
hour(widget : Et2SelectWidgets, attrs) : SelectOption[] hour(widget : Et2SelectWidgets, attrs) : SelectOption[]

View File

@ -6,7 +6,7 @@
* @link https://www.egroupware.org * @link https://www.egroupware.org
* @author Nathan Gray * @author Nathan Gray
*/ */
import {css, html, TemplateResult} from "@lion/core"; import {css, html, TemplateResult} from "lit";
import shoelace from "../../Styles/shoelace"; import shoelace from "../../Styles/shoelace";
import {Et2Tag} from "./Et2Tag"; import {Et2Tag} from "./Et2Tag";

View File

@ -6,7 +6,8 @@
* @link https://www.egroupware.org * @link https://www.egroupware.org
* @author Nathan Gray * @author Nathan Gray
*/ */
import {classMap, css, html, nothing, PropertyValues, TemplateResult} from "@lion/core"; import {css, html, nothing, PropertyValues, TemplateResult} from "lit";
import {classMap} from "lit/directives/class-map.js";
import shoelace from "../../Styles/shoelace"; import shoelace from "../../Styles/shoelace";
import {Et2Tag} from "./Et2Tag"; import {Et2Tag} from "./Et2Tag";
@ -95,8 +96,8 @@ export class Et2EmailTag extends Et2Tag
this.onlyEmail = false; this.onlyEmail = false;
this.handleMouseEnter = this.handleMouseEnter.bind(this); this.handleMouseEnter = this.handleMouseEnter.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this); this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.handleClick = this.handleClick.bind(this); this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleContactClick = this.handleContactClick.bind(this); this.handleContactMouseDown = this.handleContactMouseDown.bind(this);
} }
connectedCallback() connectedCallback()
@ -166,7 +167,7 @@ export class Et2EmailTag extends Et2Tag
this.shadowRoot.querySelector(".tag").classList.remove("contact_plus"); this.shadowRoot.querySelector(".tag").classList.remove("contact_plus");
} }
handleClick(e : MouseEvent) handleMouseDown(e : MouseEvent)
{ {
e.stopPropagation(); e.stopPropagation();
@ -177,7 +178,7 @@ export class Et2EmailTag extends Et2Tag
this.egw().open('', 'addressbook', 'add', extra); this.egw().open('', 'addressbook', 'add', extra);
} }
handleContactClick(e : MouseEvent) handleContactMouseDown(e : MouseEvent)
{ {
e.stopPropagation(); e.stopPropagation();
this.checkContact(this.value).then((result) => this.checkContact(this.value).then((result) =>
@ -255,7 +256,7 @@ export class Et2EmailTag extends Et2Tag
button_or_avatar = html` button_or_avatar = html`
<et2-lavatar slot="prefix" part="icon" <et2-lavatar slot="prefix" part="icon"
@click=${this.handleContactClick} @mousedown=${this.handleContactMouseDown}
.size=${style.getPropertyValue("--icon-width")} .size=${style.getPropertyValue("--icon-width")}
lname=${option.lname || nothing} lname=${option.lname || nothing}
fname=${option.fname || nothing} fname=${option.fname || nothing}
@ -269,7 +270,7 @@ export class Et2EmailTag extends Et2Tag
// Show a button to add as new contact // Show a button to add as new contact
classes['tag__has_plus'] = true; classes['tag__has_plus'] = true;
button_or_avatar = html` button_or_avatar = html`
<et2-button-icon image="add" @click=${this.handleClick} <et2-button-icon image="add" @mousedown=${this.handleMouseDown}
label="${this.egw().lang("Add a new contact")}" label="${this.egw().lang("Add a new contact")}"
statustext="${this.egw().lang("Add a new contact")}"> statustext="${this.egw().lang("Add a new contact")}">
</et2-button-icon>`; </et2-button-icon>`;

View File

@ -8,7 +8,8 @@
*/ */
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, TemplateResult} from "@lion/core"; import {css, html, TemplateResult} from "lit";
import {classMap} from "lit/directives/class-map.js";
import shoelace from "../../Styles/shoelace"; import shoelace from "../../Styles/shoelace";
/** /**
@ -23,7 +24,6 @@ export class Et2Tag extends Et2Widget(SlTag)
shoelace, css` shoelace, css`
:host { :host {
flex: 1 1 auto; flex: 1 1 auto;
overflow: hidden;
} }
.tag--pill { .tag--pill {
@ -113,11 +113,12 @@ export class Et2Tag extends Et2Widget(SlTag)
<sl-icon-button <sl-icon-button
part="remove-button" part="remove-button"
exportparts="base:remove-button__base" exportparts="base:remove-button__base"
name="x" name="x-lg"
library="system" library="system"
label=${this.egw().lang('remove')} label=${this.egw().lang('remove')}
class="tag__remove" class="tag__remove"
@click=${this.handleRemoveClick} @click=${this.handleRemoveClick}
tabindex="-1"
></sl-icon-button> ></sl-icon-button>
` `
: ''} : ''}

View File

@ -6,7 +6,7 @@
* @link https://www.egroupware.org * @link https://www.egroupware.org
* @author Nathan Gray * @author Nathan Gray
*/ */
import {css} from "@lion/core"; import {css} from "lit";
import shoelace from "../../Styles/shoelace"; import shoelace from "../../Styles/shoelace";
import {Et2Tag} from "./Et2Tag"; import {Et2Tag} from "./Et2Tag";

View File

@ -20,8 +20,8 @@ async function before(editable = true)
// @ts-ignore // @ts-ignore
element = await fixture<Et2Select>(html` element = await fixture<Et2Select>(html`
<et2-select label="I'm a select" value="one" multiple="true" .editModeEnabled=${editable}> <et2-select label="I'm a select" value="one" multiple="true" .editModeEnabled=${editable}>
<sl-menu-item value="one">One</sl-menu-item> <sl-option value="one">One</sl-option>
<sl-menu-item value="two">Two</sl-menu-item> <sl-option value="two">Two</sl-option>
</et2-select> </et2-select>
`); `);
// Stub egw() // Stub egw()

View File

@ -57,7 +57,7 @@ describe('Et2EmailTag', () =>
assert.equal(extra['presets[email]'], 'test@example.com'); assert.equal(extra['presets[email]'], 'test@example.com');
} }
}); });
component.handleClick(new MouseEvent('click')); component.handleMouseDown(new MouseEvent('click'));
}); });
it('should open addressbook CRM on avatar click', async() => it('should open addressbook CRM on avatar click', async() =>
@ -79,6 +79,6 @@ describe('Et2EmailTag', () =>
assert.deepEqual(extra, {title: contact.n_fn, icon: contact.photo}); assert.deepEqual(extra, {title: contact.n_fn, icon: contact.photo});
} }
}); });
await component.handleContactClick(new MouseEvent('click')); await component.handleContactMouseDown(new MouseEvent('click'));
}); });
}); });

View File

@ -97,8 +97,8 @@ describe("Multiple", () =>
// @ts-ignore // @ts-ignore
element = await fixture<Et2Select>(html` element = await fixture<Et2Select>(html`
<et2-select label="I'm a select" multiple="true"> <et2-select label="I'm a select" multiple="true">
<sl-menu-item value="one">One</sl-menu-item> <sl-option value="one">One</sl-option>
<sl-menu-item value="two">Two</sl-menu-item> <sl-option value="two">Two</sl-option>
</et2-select> </et2-select>
`); `);
element.set_value("one,two"); element.set_value("one,two");
@ -111,7 +111,7 @@ describe("Multiple", () =>
it("Can remove tags", async() => it("Can remove tags", async() =>
{ {
assert.equal(element.querySelectorAll("sl-menu-item").length, 2, "Did not find options"); assert.equal(element.querySelectorAll("sl-option").length, 2, "Did not find options");
assert.sameMembers(element.value, ["one", "two"]); assert.sameMembers(element.value, ["one", "two"]);
let tags = element.shadowRoot.querySelectorAll('.select__tags > *'); let tags = element.shadowRoot.querySelectorAll('.select__tags > *');

View File

@ -86,7 +86,7 @@ describe("Select widget", () =>
await element.updateComplete; await element.updateComplete;
/** TESTING **/ /** TESTING **/
assert.equal(element.querySelectorAll("sl-menu-item").length, 2); assert.equal(element.querySelectorAll("sl-option").length, 2);
}); });
it("merges static options with sel_options", async() => it("merges static options with sel_options", async() =>
@ -109,7 +109,7 @@ describe("Select widget", () =>
/** TESTING **/ /** TESTING **/
// @ts-ignore o.value isn't known by TypeScript, but it's there // @ts-ignore o.value isn't known by TypeScript, but it's there
let option_keys = Object.values(element.querySelectorAll("sl-menu-item")).map(o => o.value); let option_keys = Object.values(element.querySelectorAll("sl-option")).map(o => o.value);
assert.include(option_keys, "option", "Static option missing"); assert.include(option_keys, "option", "Static option missing");
assert.includeMembers(option_keys, ["1", "2", "option"], "Option mis-match"); assert.includeMembers(option_keys, ["1", "2", "option"], "Option mis-match");
assert.equal(option_keys.length, 3); assert.equal(option_keys.length, 3);

View File

@ -97,13 +97,13 @@ describe("Trigger search", () =>
// @ts-ignore // @ts-ignore
element = await fixture<Et2Select>(html` element = await fixture<Et2Select>(html`
<et2-select label="I'm a select" search=true> <et2-select label="I'm a select" search=true>
<sl-menu-item value="one">One</sl-menu-item> <sl-option value="one">One</sl-option>
<sl-menu-item value="two">Two</sl-menu-item> <sl-option value="two">Two</sl-option>
<sl-menu-item value="three">Three</sl-menu-item> <sl-option value="three">Three</sl-option>
<sl-menu-item value="four">Four</sl-menu-item> <sl-option value="four">Four</sl-option>
<sl-menu-item value="five">Five</sl-menu-item> <sl-option value="five">Five</sl-option>
<sl-menu-item value="six">Six</sl-menu-item> <sl-option value="six">Six</sl-option>
<sl-menu-item value="seven">Seven</sl-menu-item> <sl-option value="seven">Seven</sl-option>
</et2-select> </et2-select>
`); `);
// Stub egw() // Stub egw()

View File

@ -11,7 +11,7 @@
import {Et2Widget} from "../Et2Widget/Et2Widget"; import {Et2Widget} from "../Et2Widget/Et2Widget";
import {SlSpinner} from "@shoelace-style/shoelace"; import {SlSpinner} from "@shoelace-style/shoelace";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";
import {css} from "@lion/core"; import {css} from "lit";
export class Et2Spinner extends Et2Widget(SlSpinner) export class Et2Spinner extends Et2Widget(SlSpinner)
{ {

View File

@ -8,8 +8,8 @@
* @author Hadi Nategh * @author Hadi Nategh
*/ */
import {css, html} from "lit";
import {css, html, SlotMixin} from "@lion/core"; import {SlotMixin} from "@lion/core";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import '../Et2Image/Et2Image'; import '../Et2Image/Et2Image';
import {SlSwitch} from "@shoelace-style/shoelace"; import {SlSwitch} from "@shoelace-style/shoelace";

View File

@ -9,7 +9,7 @@
*/ */
import {css} from "@lion/core"; import {css} from "lit";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {SlTextarea} from "@shoelace-style/shoelace"; import {SlTextarea} from "@shoelace-style/shoelace";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";

View File

@ -9,7 +9,7 @@
*/ */
import {Et2Textbox} from "./Et2Textbox"; import {Et2Textbox} from "./Et2Textbox";
import {css, html, render} from "@lion/core"; import {css, html, render} from "lit";
export class Et2Number extends Et2Textbox export class Et2Number extends Et2Textbox
{ {

View File

@ -11,7 +11,9 @@
import {Et2InvokerMixin} from "../Et2Url/Et2InvokerMixin"; import {Et2InvokerMixin} from "../Et2Url/Et2InvokerMixin";
import {Et2Textbox} from "./Et2Textbox"; import {Et2Textbox} from "./Et2Textbox";
import {Et2Dialog} from "../Et2Dialog/Et2Dialog"; import {Et2Dialog} from "../Et2Dialog/Et2Dialog";
import {classMap, html, ifDefined} from "@lion/core"; import {html} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {ifDefined} from "lit/directives/if-defined.js";
import {egw} from "../../jsapi/egw_global"; import {egw} from "../../jsapi/egw_global";
const isChromium = navigator.userAgentData?.brands.some(b => b.brand.includes('Chromium')); const isChromium = navigator.userAgentData?.brands.some(b => b.brand.includes('Chromium'));

View File

@ -9,7 +9,7 @@
*/ */
import {css, PropertyValues} from "@lion/core"; import {css, PropertyValues} from "lit";
import {Regex} from "../Validators/Regex"; import {Regex} from "../Validators/Regex";
import {SlInput} from "@shoelace-style/shoelace"; import {SlInput} from "@shoelace-style/shoelace";
import shoelace from "../Styles/shoelace"; import shoelace from "../Styles/shoelace";

View File

@ -8,7 +8,8 @@
*/ */
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import {css, dedupeMixin, html, LitElement, SlotMixin} from '@lion/core'; import {css, html, LitElement} from 'lit';
import {dedupeMixin, SlotMixin} from '@lion/core';
import {Et2InputWidget, Et2InputWidgetInterface} from "../Et2InputWidget/Et2InputWidget"; import {Et2InputWidget, Et2InputWidgetInterface} from "../Et2InputWidget/Et2InputWidget";
import {colorsDefStyles} from "../Styles/colorsDefStyles"; import {colorsDefStyles} from "../Styles/colorsDefStyles";

View File

@ -11,7 +11,7 @@
import {Et2InvokerMixin} from "./Et2InvokerMixin"; import {Et2InvokerMixin} from "./Et2InvokerMixin";
import {Et2Textbox} from "../Et2Textbox/Et2Textbox"; import {Et2Textbox} from "../Et2Textbox/Et2Textbox";
import {colorsDefStyles} from "../Styles/colorsDefStyles"; import {colorsDefStyles} from "../Styles/colorsDefStyles";
import {css} from "@lion/core"; import {css} from "lit";
import {egw} from "../../jsapi/egw_global"; import {egw} from "../../jsapi/egw_global";
/** /**

View File

@ -12,7 +12,7 @@ import {Et2InvokerMixin} from "./Et2InvokerMixin";
import {IsEmail} from "../Validators/IsEmail"; import {IsEmail} from "../Validators/IsEmail";
import {Et2Textbox} from "../Et2Textbox/Et2Textbox"; import {Et2Textbox} from "../Et2Textbox/Et2Textbox";
import {colorsDefStyles} from "../Styles/colorsDefStyles"; import {colorsDefStyles} from "../Styles/colorsDefStyles";
import {css} from "@lion/core"; import {css} from "lit";
import {egw} from "../../jsapi/egw_global"; import {egw} from "../../jsapi/egw_global";
/** /**

View File

@ -11,7 +11,7 @@
import {Et2UrlPhone} from "./Et2UrlPhone"; import {Et2UrlPhone} from "./Et2UrlPhone";
import {Et2UrlEmail} from "./Et2UrlEmail"; import {Et2UrlEmail} from "./Et2UrlEmail";
import {colorsDefStyles} from "../Styles/colorsDefStyles"; import {colorsDefStyles} from "../Styles/colorsDefStyles";
import {css} from "@lion/core"; import {css} from "lit";
/** /**
* @customElement et2-url-phone * @customElement et2-url-phone

View File

@ -11,7 +11,7 @@
import {Et2InvokerMixin} from "./Et2InvokerMixin"; import {Et2InvokerMixin} from "./Et2InvokerMixin";
import {Et2Textbox} from "../Et2Textbox/Et2Textbox"; import {Et2Textbox} from "../Et2Textbox/Et2Textbox";
import {colorsDefStyles} from "../Styles/colorsDefStyles"; import {colorsDefStyles} from "../Styles/colorsDefStyles";
import {css} from "@lion/core"; import {css} from "lit";
/** /**
* @customElement et2-url-phone * @customElement et2-url-phone

View File

@ -9,7 +9,7 @@
/* eslint-disable import/no-extraneous-dependencies */ /* eslint-disable import/no-extraneous-dependencies */
import {Et2Description} from "../Et2Description/Et2Description"; import {Et2Description} from "../Et2Description/Et2Description";
import {css, TemplateResult} from "@lion/core"; import {css, TemplateResult} from "lit";
import {Et2Url} from "./Et2Url"; import {Et2Url} from "./Et2Url";
/** /**

View File

@ -1,7 +1,7 @@
import {ExposeValue} from "../Expose/ExposeMixin"; import {ExposeValue} from "../Expose/ExposeMixin";
import {et2_vfsMode} from "../et2_widget_vfs"; import {et2_vfsMode} from "../et2_widget_vfs";
import {Et2ImageExpose} from "../Expose/Et2ImageExpose"; import {Et2ImageExpose} from "../Expose/Et2ImageExpose";
import {css, html} from "@lion/core"; import {css, html} from "lit";
export class Et2VfsMime extends Et2ImageExpose export class Et2VfsMime extends Et2ImageExpose

View File

@ -7,7 +7,7 @@
* @author Ralf Becker <rb@egroupware.org> * @author Ralf Becker <rb@egroupware.org>
*/ */
import {Et2SelectAccountReadonly} from "../Et2Select/Et2SelectReadonly"; import {Et2SelectAccountReadonly} from "../Et2Select/Select/Et2SelectReadonly";
export class Et2VfsUid extends Et2SelectAccountReadonly export class Et2VfsUid extends Et2SelectAccountReadonly
{ {

View File

@ -8,7 +8,8 @@ import {et2_cloneObject, et2_csvSplit} from "../et2_core_common";
import type {IegwAppLocal} from "../../jsapi/egw_global"; import type {IegwAppLocal} from "../../jsapi/egw_global";
import {egw} from "../../jsapi/egw_global"; import {egw} from "../../jsapi/egw_global";
import {ClassWithAttributes, ClassWithInterfaces} from "../et2_core_inheritance"; import {ClassWithAttributes, ClassWithInterfaces} from "../et2_core_inheritance";
import {css, dedupeMixin, LitElement, PropertyValues, unsafeCSS} from "@lion/core"; import {css, LitElement, PropertyValues, unsafeCSS} from "lit";
import {dedupeMixin} from "@lion/core";
import type {et2_container} from "../et2_core_baseWidget"; import type {et2_container} from "../et2_core_baseWidget";
import type {et2_DOMWidget} from "../et2_core_DOMWidget"; import type {et2_DOMWidget} from "../et2_core_DOMWidget";

View File

@ -12,7 +12,7 @@
import {ExposeMixin, ExposeValue, MediaValue} from "./ExposeMixin"; import {ExposeMixin, ExposeValue, MediaValue} from "./ExposeMixin";
import {Et2Description} from "../Et2Description/Et2Description"; import {Et2Description} from "../Et2Description/Et2Description";
import {et2_IDetachedDOM} from "../et2_core_interfaces"; import {et2_IDetachedDOM} from "../et2_core_interfaces";
import {html} from "@lion/core"; import {html} from "lit";
/** /**
* Shows a description and if you click on it, it shows the file specified by href in gallery. * Shows a description and if you click on it, it shows the file specified by href in gallery.

View File

@ -11,7 +11,7 @@
// Don't import this more than once // Don't import this more than once
import "../../../../node_modules/blueimp-gallery/js/blueimp-gallery.min"; import "../../../../node_modules/blueimp-gallery/js/blueimp-gallery.min";
import {css, html, LitElement, render} from "@lion/core"; import {css, html, LitElement, render} from "lit";
import {et2_nextmatch} from "../et2_extension_nextmatch"; import {et2_nextmatch} from "../et2_extension_nextmatch";
import {Et2Dialog} from "../Et2Dialog/Et2Dialog"; import {Et2Dialog} from "../Et2Dialog/Et2Dialog";
import {ET2_DATAVIEW_STEPSIZE} from "../et2_dataview_controller"; import {ET2_DATAVIEW_STEPSIZE} from "../et2_dataview_controller";

View File

@ -9,7 +9,8 @@
*/ */
import {classMap, css, html, LitElement} from "@lion/core"; import {css, html, LitElement} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {Et2Widget} from "../../Et2Widget/Et2Widget"; import {Et2Widget} from "../../Et2Widget/Et2Widget";
import {et2_IDetachedDOM} from "../../et2_core_interfaces"; import {et2_IDetachedDOM} from "../../et2_core_interfaces";

View File

@ -8,7 +8,7 @@
*/ */
import {Et2Widget} from "../../Et2Widget/Et2Widget"; import {Et2Widget} from "../../Et2Widget/Et2Widget";
import {css} from "@lion/core"; import {css} from "lit";
import {SlDetails} from "@shoelace-style/shoelace"; import {SlDetails} from "@shoelace-style/shoelace";
import shoelace from "../../Styles/shoelace"; import shoelace from "../../Styles/shoelace";

Some files were not shown because too many files have changed in this diff Show More