Remove Lion

This commit is contained in:
nathan 2024-05-07 14:46:44 -06:00
parent 2b90e9fc8d
commit ba744d3292
45 changed files with 656 additions and 407 deletions

View File

@ -20,7 +20,7 @@ import {et2_selectbox} from "../../api/js/etemplate/et2_widget_selectbox";
import {fetchAll, nm_action, nm_compare_field} from "../../api/js/etemplate/et2_extension_nextmatch_actions";
import "./CRM";
import {egw} from "../../api/js/jsapi/egw_global";
import {LitElement} from "@lion/core";
import {LitElement} from "lit";
import {Et2SelectCountry} from "../../api/js/etemplate/Et2Select/Select/Et2SelectCountry";
import {Et2SelectState} from "../../api/js/etemplate/Et2Select/Select/Et2SelectState";

View File

@ -17,13 +17,13 @@ import {etemplate2} from "../../api/js/etemplate/etemplate2";
import {Et2Dialog} from "../../api/js/etemplate/Et2Dialog/Et2Dialog";
import {egw} from "../../api/js/jsapi/egw_global.js";
import {egwAction, egwActionObject} from '../../api/js/egw_action/egw_action';
import {LitElement} from "@lion/core";
import {et2_nextmatch} from "../../api/js/etemplate/et2_extension_nextmatch";
import {et2_DOMWidget} from "../../api/js/etemplate/et2_core_DOMWidget";
import {Et2SelectAccount} from "../../api/js/etemplate/Et2Select/Select/Et2SelectAccount";
import {EgwAction} from "../../api/js/egw_action/EgwAction";
import {EgwActionObject} from "../../api/js/egw_action/EgwActionObject";
import type {Et2Button} from "../../api/js/etemplate/Et2Button/Et2Button";
import {LitElement} from "lit";
/**
* UI for Admin

View File

@ -52,7 +52,6 @@ export class Et2Avatar extends Et2Widget(SlAvatar) implements et2_IDetachedDOM
/**
* The label of the image
* Actually not used as label, but we put it as title
* Added here as there's no Lion parent
*/
label: {
type: String

View File

@ -9,20 +9,19 @@
*/
import {css, html} from "lit";
import {css, html, nothing} from "lit";
import 'lit-flatpickr';
import {dateStyles} from "./DateStyles";
import type {Instance} from 'flatpickr/dist/types/instance';
import {default as scrollPlugin} from "flatpickr/dist/plugins/scrollPlugin.js";
import {default as ShortcutButtonsPlugin} from "shortcut-buttons-flatpickr/dist/shortcut-buttons-flatpickr";
import {default as ShortcutButtonsPlugin} from "shortcut-buttons-flatpickr/dist/shortcut-buttons-flatpickr.js";
import flatpickr from "flatpickr";
import {egw} from "../../jsapi/egw_global";
import type {HTMLElementWithValue} from "@lion/form-core/types/FormControlMixinTypes";
import {Et2Textbox} from "../Et2Textbox/Et2Textbox";
import {FormControlMixin} from "@lion/form-core";
import {LitFlatpickr} from "lit-flatpickr";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import shoelace from "../Styles/shoelace";
import {classMap} from "lit/directives/class-map.js";
// list of existing localizations from node_modules/flatpicker/dist/l10n directory:
const l10n = [
@ -224,7 +223,7 @@ export function parseDateTime(dateTimeString)
* Format dates according to user preference
*
* @param {Date} date
* @param {import('@lion/localize/types/LocalizeMixinTypes').FormatDateOptions} [options] Intl options are available
* @param [options] Intl options are available
* set 'dateFormat': "Y-m-d" to specify a particular format
* @returns {string}
*/
@ -261,7 +260,7 @@ export function formatDate(date : Date, options = {dateFormat: ""}) : string
* Format dates according to user preference
*
* @param {Date} date
* @param {import('@lion/localize/types/LocalizeMixinTypes').FormatDateOptions} [options] Intl options are available
* @param [options] Intl options are available
* set 'timeFormat': "12" to specify a particular format
* @returns {string}
*/
@ -305,8 +304,7 @@ export function formatDateTime(date : Date, options = {dateFormat: "", timeForma
return formatDate(date, options) + " " + formatTime(date, options);
}
// !!! ValidateMixin !!!
export class Et2Date extends Et2InputWidget(FormControlMixin(LitFlatpickr))
export class Et2Date extends Et2InputWidget(LitFlatpickr)
{
static get styles()
{
@ -554,7 +552,7 @@ export class Et2Date extends Et2InputWidget(FormControlMixin(LitFlatpickr))
options.onChange = this._updateValueOnChange;
options.onReady = this._onReady;
// Remove Lion's inert attribute so we can work in Et2Dialog
// Remove inert attribute so we can work in Et2Dialog
options.onOpen = [() =>
{
this._instance.calendarContainer?.removeAttribute("inert")
@ -968,17 +966,17 @@ export class Et2Date extends Et2InputWidget(FormControlMixin(LitFlatpickr))
* This is an et2-textbox, which causes some problems with flatpickr
* @protected
*/
get _inputNode() : HTMLElementWithValue
get _inputNode() : Et2Textbox
{
return this.querySelector('[slot="input"]');
return this.shadowRoot?.querySelector('et2-textbox');
}
/**
* The holder of value for flatpickr
*/
get _valueNode() : HTMLElementWithValue
get _valueNode() : Et2Textbox
{
return this.querySelector('et2-textbox');
return this.shadowRoot?.querySelector('et2-textbox');
}
/**
@ -1037,20 +1035,17 @@ export class Et2Date extends Et2InputWidget(FormControlMixin(LitFlatpickr))
this.setDate(date, false, null);
}
render()
{
return html`
<div part="form-control" class="form-control">
<div class="form-field__group-one" part="form-control-label">${this._groupOneTemplate()}</div>
<div class="form-field__group-two" part="form-control-input">${this._groupTwoTemplate()}</div>
</div>
`;
}
protected _inputGroupInputTemplate()
protected _inputTemplate()
{
if(typeof egwIsMobile == "function" && egwIsMobile())
{
// Plain input for mobile
return html`<input type=${this._mobileInputType()}></input>`;
}
// This element gets hidden and used for value, but copied by flatpickr and used for input
return html`
<slot name="input"></slot>
<et2-textbox type="text" placeholder=${this.placeholder} ?required=${this.required}></et2-textbox>
${this._incrementButtonTemplate()}
`;
}
@ -1079,6 +1074,32 @@ export class Et2Date extends Et2InputWidget(FormControlMixin(LitFlatpickr))
</et2-button-icon>
</div>`;
}
render()
{
const labelTemplate = this._labelTemplate();
const helpTemplate = this._helpTextTemplate();
return html`
${super.render()}
<div
part="form-control"
class=${classMap({
'form-control': true,
'form-control--medium': true,
'form-control--has-label': labelTemplate !== nothing,
'form-control--has-help-text': helpTemplate !== nothing
})}
>
${labelTemplate}
<div part="form-control-input" class="form-control-input">
${this._inputTemplate()}
</div>
${helpTemplate}
</div>
`;
}
}
// @ts-ignore TypeScript is not recognizing that Et2Date is a LitElement

View File

@ -9,12 +9,11 @@
*/
import {css, html, LitElement} from "lit";
import {css, html, LitElement, nothing} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {sprintf} from "../../egw_action/egw_action_common";
import {dateStyles} from "./DateStyles";
import {FormControlMixin} from "@lion/form-core";
import shoelace from "../Styles/shoelace";
export interface formatOptions
@ -112,7 +111,7 @@ export function formatDuration(value : number | string, options : formatOptions)
* If not specified, the time is in assumed to be minutes and will be displayed with a calculated unit
* but this can be specified with the properties.
*/
export class Et2DateDuration extends Et2InputWidget(FormControlMixin(LitElement))
export class Et2DateDuration extends Et2InputWidget(LitElement)
{
static get styles()
{
@ -125,7 +124,7 @@ export class Et2DateDuration extends Et2InputWidget(FormControlMixin(LitElement)
max-width: 100%;
}
.input-group {
.form-control-input {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
@ -399,35 +398,29 @@ export class Et2DateDuration extends Et2InputWidget(FormControlMixin(LitElement)
render()
{
return html`
<div part="form-control" class=${classMap({
'form-control': true,
'form-control--has-label': this.label.split("%")[0] || false
})}>
<div class="form-field__group-one form-control__label" part="form-control-label">
${this._groupOneTemplate()}
</div>
<div class="form-field__group-two" part="form-control-input">${this._groupTwoTemplate()}</div>
</div>
`;
}
/**
* @return {TemplateResult}
* @protected
*/
// eslint-disable-next-line class-methods-use-this
_inputGroupInputTemplate()
{
const labelTemplate = this._labelTemplate();
const helpTemplate = this._helpTextTemplate();
return html`
<div class="input-group__input" @sl-change=${() =>
{
this.dispatchEvent(new Event("change", {bubbles: true}));
}}>
<slot name="input">
<div
part="form-control"
class=${classMap({
'form-control': true,
'form-control--medium': true,
'form-control--has-label': labelTemplate !== nothing,
'form-control--has-help-text': helpTemplate !== nothing
})}
>
${labelTemplate}
<div part="form-control-input" class="form-control-input" @sl-change=${() =>
{
this.dispatchEvent(new Event("change", {bubbles: true}));
}}>
${this._inputTemplate()}
${this._formatTemplate()}
</slot>
</div>
${helpTemplate}
</div>
`;
}

View File

@ -1,5 +1,4 @@
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {FormControlMixin} from "@lion/form-core";
import {css, html, LitElement, TemplateResult} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {ifDefined} from "lit/directives/if-defined.js";
@ -14,7 +13,7 @@ import {egw} from "../../jsapi/egw_global";
* If not specified, the time is in assumed to be minutes and will be displayed with a calculated unit
* but this can be specified with the properties.
*/
export class Et2DateRange extends Et2InputWidget(FormControlMixin(LitElement))
export class Et2DateRange extends Et2InputWidget(LitElement)
{
static get styles()
{

View File

@ -28,7 +28,6 @@ export class Et2DateTimeToday extends Et2DateReadonly
* If the date is today, we show just the time. Otherwise, the date.
*
* @param {Date} date
* @param {import('@lion/localize/types/LocalizeMixinTypes').FormatDateOptions} [options] Intl options are available
* @returns {string}
*/
formatDateTime(date : Date, options = {dateFormat: "", timeFormat: ""}) : string

View File

@ -15,7 +15,6 @@ 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 type {etemplate2} from "../etemplate2";
import {egw, IegwAppLocal} from "../../jsapi/egw_global";
@ -126,7 +125,7 @@ export interface DialogButton
*
* Customize initial focus by setting the "autofocus" attribute on a control, otherwise first input will have focus
*/
export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
export class Et2Dialog extends Et2Widget(SlDialog)
{
/**
* Dialogs don't always get added to an etemplate, so we keep our own egw
@ -334,21 +333,6 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
}
}
get slots()
{
return {
...super.slots,
'': () =>
{
// to fix problem with Safari 16.2 of NOT displaying the content, we have to use the following,
// instead of just return this._contentTemplate()
let div = document.createElement("div");
render(this._contentTemplate(), div);
return div.children[0];
}
}
}
/*
* List of properties that get translated
* Done separately to not interfere with properties - if we re-define label property,
@ -525,9 +509,11 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
}
}
firstUpdated()
firstUpdated(changedProperties)
{
super.firstUpdated();
super.firstUpdated(changedProperties);
render(this._contentTemplate(), this);
// If we start open, fire handler to get setup done
if(this.open)
@ -764,7 +750,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
}
if(changedProperties.has("buttons"))
{
render(this._buttonsTemplate(), this);
//render(this._buttonsTemplate(), this);
this.requestUpdate();
}
if(changedProperties.has("width"))
@ -904,7 +890,7 @@ export class Et2Dialog extends Et2Widget(SlotMixin(SlDialog))
<slot>${this.message}</slot>`
}
</div>`;
</div>${this._buttonsTemplate()}`;
}

View File

@ -24,7 +24,6 @@ import {waitForEvent} from "../Et2Widget/event";
import styles from "./Et2Email.styles";
import {SelectOption} from "../Et2Select/FindSelectOptions";
import {IsEmail} from "../Validators/IsEmail";
import {Validator} from "@lion/form-core";
import Sortable from "sortablejs/modular/sortable.complete.esm.js";
import {SearchMixinInterface} from "../Et2Widget/SearchMixin";

View File

@ -186,7 +186,7 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
}
/** @param {import('@lion/core').PropertyValues } changedProperties */
/** @param changedProperties */
updated(changedProperties : PropertyValues)
{
super.updated(changedProperties);

View File

@ -10,10 +10,9 @@
import {css, html, LitElement} from "lit";
import {SlotMixin} from "@lion/core";
import {Et2Widget} from "../Et2Widget/Et2Widget";
export class Et2Iframe extends Et2Widget(SlotMixin(LitElement))
export class Et2Iframe extends Et2Widget(LitElement)
{
static get styles()

View File

@ -29,4 +29,5 @@ export class Et2AppIcon extends Et2Image
return super.parse_href(src);
}
}
customElements.define("et2-appicon", Et2AppIcon as any, {extends: 'img'});
customElements.define("et2-appicon", Et2AppIcon as any);

View File

@ -9,29 +9,29 @@
*/
import {css, html, LitElement, render} from "lit";
import {SlotMixin} from "@lion/core";
import {Et2Widget} from "../Et2Widget/Et2Widget";
import {et2_IDetachedDOM} from "../et2_core_interfaces";
export class Et2Image extends Et2Widget(SlotMixin(LitElement)) implements et2_IDetachedDOM
export class Et2Image extends Et2Widget(LitElement) implements et2_IDetachedDOM
{
static get styles()
{
return [
...super.styles,
css`
:host {
display: inline-block;
}
::slotted(img) {
max-height: 100%;
max-width: 100%;
}
:host([icon]) {
height: 1.3rem;
}
`,
];
:host {
display: inline-block;
}
::slotted(img) {
max-height: 100%;
max-width: 100%;
}
:host([icon]) {
height: 1.3rem;
}
`];
}
static get properties()
@ -42,7 +42,6 @@ export class Et2Image extends Et2Widget(SlotMixin(LitElement)) implements et2_ID
/**
* The label of the image
* Actually not used as label, but we put it as title
* Added here as there's no Lion parent
*/
label: {
type: String
@ -80,16 +79,6 @@ export class Et2Image extends Et2Widget(SlotMixin(LitElement)) implements et2_ID
}
}
get slots()
{
return {
'': () =>
{
return this._imageTemplate();
}
}
}
constructor()
{
super();
@ -248,4 +237,4 @@ export class Et2Image extends Et2Widget(SlotMixin(LitElement)) implements et2_ID
}
}
customElements.define("et2-image", Et2Image, {extends: 'img'});
customElements.define("et2-image", Et2Image)//, {extends: 'img'});

View File

@ -1,15 +1,15 @@
import {css, html, LitElement, nothing, PropertyValues, TemplateResult} from "lit";
import {et2_IInput, et2_IInputNode, et2_ISubmitListener} from "../et2_core_interfaces";
import {Et2Widget} from "../Et2Widget/Et2Widget";
import {css, LitElement, PropertyValues} from "lit";
import {Required} from "../Validators/Required";
import {ManualMessage} from "../Validators/ManualMessage";
import {LionValidationFeedback, Validator} from "@lion/form-core";
import {HasSlotController} from "../Et2Widget/slot";
import {et2_csvSplit} from "../et2_core_common";
import {dedupeMixin} from "@lion/core";
import {property} from "lit/decorators/property.js";
import {Validator} from "../Validators/Validator";
import {ManualMessage} from "../Validators/ManualMessage";
import {Required} from "../Validators/Required";
import {EgwValidationFeedback} from "../Validators/EgwValidationFeedback";
import {dedupeMixin} from "@open-wc/dedupe-mixin";
// LionValidationFeedback needs to be registered manually
window.customElements.define('lion-validation-feedback', LionValidationFeedback);
/**
* This mixin will allow any LitElement to become an Et2InputWidget
@ -69,6 +69,9 @@ const Et2InputWidgetMixin = <T extends Constructor<LitElement>>(superclass : T)
protected isSlComponent = false;
// Allows us to check to see if label or help-text is set. Override to check additional slots.
protected readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
/** WebComponent **/
static get styles()
{
@ -95,6 +98,7 @@ const Et2InputWidgetMixin = <T extends Constructor<LitElement>>(superclass : T)
.form-control__help-text {
position: relative;
width: 100%;
}
`
];
@ -111,11 +115,7 @@ const Et2InputWidgetMixin = <T extends Constructor<LitElement>>(superclass : T)
label: {
type: String, noAccessor: true
},
// readOnly is what the property is in Lion, readonly is the attribute
readOnly: {
type: Boolean,
attribute: 'readonly',
},
// readonly is what is in the templates
// I put this in here so loadWebComponent finds it when it tries to set it from the template
readonly: {
@ -341,7 +341,7 @@ const Et2InputWidgetMixin = <T extends Constructor<LitElement>>(superclass : T)
this.updateComplete.then(() =>
{
// Remove all messages. Manual will be explicitly replaced, other validators will be re-run on blur.
this.querySelectorAll("lion-validation-feedback").forEach(e => e.remove());
this.querySelectorAll("egw-validation-feedback").forEach(e => e.remove());
});
}
@ -400,6 +400,11 @@ const Et2InputWidgetMixin = <T extends Constructor<LitElement>>(superclass : T)
return this.__readonly;
}
/**
* Was from early days (Lion)
* @deprecated
* @param {boolean} new_value
*/
set readOnly(new_value)
{
this.readonly = new_value;
@ -582,7 +587,7 @@ const Et2InputWidgetMixin = <T extends Constructor<LitElement>>(superclass : T)
let fieldName = this.id;
let feedbackData = [];
let resultPromises = [];
this.querySelector("lion-validation-feedback")?.remove();
(<EgwValidationFeedback>this.querySelector("egw-validation-feedback"))?.remove();
// Collect message of a (failing) validator
const doValidate = async function(validator, value)
@ -653,7 +658,7 @@ const Et2InputWidgetMixin = <T extends Constructor<LitElement>>(superclass : T)
// Show feedback from all failing validators
if(feedbackData.length > 0)
{
let feedback = <LionValidationFeedback>document.createElement("lion-validation-feedback");
let feedback = document.createElement("egw-validation-feedback");
feedback.feedbackData = feedbackData;
feedback.slot = "help-text";
this.append(feedback);
@ -719,7 +724,7 @@ const Et2InputWidgetMixin = <T extends Constructor<LitElement>>(superclass : T)
*/
public get hasFeedbackFor() : string[]
{
let feedback = (<LionValidationFeedback>this.querySelector("lion-validation-feedback"))?.feedbackData || [];
let feedback = (this.querySelector("egw-validation-feedback"))?.feedbackData || [];
return feedback.map((f) => f.type);
}
@ -734,7 +739,7 @@ const Et2InputWidgetMixin = <T extends Constructor<LitElement>>(superclass : T)
{
this.submitted = true;
// If using Lion validators, run them now
// If using validators, run them now
if(this.validate)
{
// Force update now
@ -745,6 +750,47 @@ const Et2InputWidgetMixin = <T extends Constructor<LitElement>>(superclass : T)
}
return true;
}
/**
* Common sub-template to add a label.
* This goes inside the form control wrapper div, before and at the same depth as the input controls.
*
*
* @returns {TemplateResult} Either a TemplateResult or nothing (the object). Check for nothing to set
* 'form-control--has-label' class on the wrapper div.
* @protected
*/
protected _labelTemplate() : TemplateResult | typeof nothing
{
const hasLabelSlot = this.hasSlotController?.test('label');
const hasLabel = this.label ? true : !!hasLabelSlot;
return hasLabel ? html`
<label
id="label"
part="form-control-label"
class="form-control__label"
aria-hidden=${hasLabel ? 'false' : 'true'}
@click=${typeof this.handleLabelClick == "function" ? this.handleLabelClick : nothing}
>
<slot name="label">${this.label}</slot>
</label>
` : nothing;
}
protected _helpTextTemplate() : TemplateResult | typeof nothing
{
const hasHelpTextSlot = this.hasSlotController?.test('help-text');
const hasHelpText = this.helpText ? true : !!hasHelpTextSlot;
return hasHelpText ? html`
<div
part="form-control-help-text"
id="help-text"
class="form-control__help-text"
aria-hidden=${hasHelpText ? 'false' : 'true'}
>
<slot name="help-text">${this.helpText}</slot>
</div>` : nothing;
}
}
return Et2InputWidgetClass as Constructor & T;

View File

@ -1,7 +1,5 @@
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {css, html, LitElement, PropertyValues} from "lit";
import {FormControlMixin, ValidateMixin} from "@lion/form-core";
import {SlotMixin} from "@lion/core";
import {Et2LinkAppSelect} from "./Et2LinkAppSelect";
import {LinkInfo} from "./Et2Link";
import {Et2Button} from "../Et2Button/Et2Button";
@ -11,7 +9,7 @@ import {Et2Button} from "../Et2Button/Et2Button";
*
*
*/
export class Et2LinkAdd extends Et2InputWidget(FormControlMixin(ValidateMixin(SlotMixin(LitElement))))
export class Et2LinkAdd extends Et2InputWidget(LitElement)
{
static get styles()
{

View File

@ -6,37 +6,38 @@
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {css, html, LitElement, PropertyValues} from "lit";
import {SlotMixin} from "@lion/core";
import {css, html, LitElement, nothing} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {Et2LinkAppSelect} from "./Et2LinkAppSelect";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {FormControlMixin} from "@lion/form-core";
import {Et2LinkSearch} from "./Et2LinkSearch";
import {Et2Link, LinkInfo} from "./Et2Link";
import {HasSlotController} from "../Et2Widget/slot";
/**
* Find and select a single entry using the link system.
*
*
*/
export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitElement)))
export class Et2LinkEntry extends Et2InputWidget(LitElement)
{
static get styles()
{
return [
...super.styles,
css`
:host {
:host {
display: block;
}
}
:host(.hideApp) ::slotted([slot="app"]) {
:host(.hideApp) ::slotted([slot="app"]) {
display: none;
}
}
.input-group__input {
gap: 0.5rem;
}
.form-control-input {
display: flex;
gap: 0.5rem;
}
`
];
@ -83,52 +84,17 @@ export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitE
}
}
get slots()
{
return {
...super.slots,
app: () =>
{
const app = <Et2LinkAppSelect>document.createElement("et2-link-apps")
if(this.__onlyApp)
{
app.onlyApp = this.__onlyApp;
}
else if(typeof this._value !== "undefined" && this._value.app)
{
app.value = this._value.app;
}
return app;
},
select: () =>
{
const select = <Et2LinkSearch><unknown>document.createElement("et2-link-search");
if(typeof this._value !== "undefined" && this._value.id)
{
if(this._value.title)
{
select.select_options = [{value: this._value.id, label: this._value.title}]
}
select.app = this._value.app;
select.value = this._value.id;
}
return select;
}
}
}
/**
* We only care about this value until render. After the sub-nodes are created,
* we take their "live" values for our value.
*
* N.B.: Single underscore! Otherwise we conflict with parent __value
*
* @type {LinkInfo}
* @private
*/
private _value : LinkInfo;
private __value : LinkInfo = {app: "", id: ""};
protected __onlyApp : string;
protected readonly hasSlotController = new HasSlotController(this, 'help-text', 'label');
constructor()
{
@ -139,18 +105,18 @@ export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitE
{
super.connectedCallback();
this._handleAppChange = this._handleAppChange.bind(this);
this._handleEntrySelect = this._handleEntrySelect.bind(this);
this._handleEntryClear = this._handleEntryClear.bind(this);
this._handleShow = this._handleShow.bind(this);
this._handleHide = this._handleHide.bind(this);
// Clear initial value
this._value = undefined;
this.__value = undefined;
if(!this.readonly)
{
this._bindListeners();
this.updateComplete.then(() =>
{
this._bindListeners();
});
}
}
@ -160,41 +126,14 @@ export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitE
this._unbindListeners();
}
updated(changedProperties : PropertyValues)
{
super.updated(changedProperties);
if(changedProperties.has("required"))
{
this._searchNode.required = this.required;
}
if(changedProperties.has("readonly"))
{
this._appNode.readonly = this._appNode.disabled = this.readonly;
this._searchNode.readonly = this.readonly;
}
// Pass some properties on to app selection
if(changedProperties.has("onlyApp"))
{
this._appNode.onlyApp = this.onlyApp;
}
if(changedProperties.has("applicationList"))
{
this._appNode.applicationList = this.applicationList;
}
if(changedProperties.has("appIcons"))
{
this._appNode.appIcons = this.appIcons;
}
}
set onlyApp(app)
{
this.__onlyApp = app || "";
// If initial value got set before onlyApp, it still needs app in pre-render value
if(this._value && app)
if(this.__value && app)
{
this._value.app = this.__onlyApp;
this.__value.app = this.__onlyApp;
}
if(app)
{
@ -213,16 +152,20 @@ export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitE
set app(app)
{
this.updateComplete.then(() =>
if(typeof this.__value !== "object" || this.__value == null)
{
this._appNode.value = app;
this._searchNode.app = app;
});
this.__value = <LinkInfo>{app: app}
}
else
{
this.__value.app = app;
}
this.requestUpdate("value");
}
get app()
{
return this._appNode?.value || "";
return this.__value?.app || "";
}
set searchOptions(options)
@ -240,12 +183,12 @@ export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitE
get _appNode() : Et2LinkAppSelect
{
return this.querySelector("[slot='app']");
return this.shadowRoot?.querySelector("et2-link-apps");
}
get _searchNode() : Et2LinkSearch
{
return this.querySelector("[slot='select']");
return <Et2LinkSearch>this.shadowRoot?.querySelector("et2-link-search");
}
get placeholder() : string
@ -263,48 +206,32 @@ export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitE
protected _bindListeners()
{
this._appNode.addEventListener("change", this._handleAppChange);
this._searchNode.addEventListener("change", this._handleEntrySelect);
this._searchNode.addEventListener("sl-clear", this._handleEntryClear);
this.addEventListener("sl-show", this._handleShow);
this.addEventListener("sl-hide", this._handleHide);
}
protected _unbindListeners()
{
this._appNode.removeEventListener("change", this._handleAppChange);
this.removeEventListener("sl-select", this._handleEntrySelect);
this.removeEventListener("sl-clear", this._handleEntryClear);
this.removeEventListener("sl-show", this._handleShow);
this.removeEventListener("sl-hide", this._handleHide);
}
/**
* Update the search node's app & clear selected value when
* selected app changes.
* @param event
* @protected
*/
protected _handleAppChange(event)
{
this._searchNode.app = this._appNode.value;
this._searchNode.value = "";
this._searchNode.clearSearch();
this._searchNode.focus();
this.requestUpdate('value');
}
/**
* Hide app selection when there's an entry
* @param event
* @protected
*/
protected _handleEntrySelect(event)
protected handleEntrySelect(event)
{
event.stopPropagation();
this.value = <string>this._searchNode.value ?? "";
this.classList.toggle("hideApp", Boolean(typeof this.value == "object" ? this.value?.id : this.value));
this.dispatchEvent(new Event("change"));
this.updateComplete.then(() =>
{
this.dispatchEvent(new Event("change"));
});
this.requestUpdate('value');
this.validate();
@ -316,13 +243,17 @@ export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitE
* @param event
* @protected
*/
protected _handleEntryClear(event)
protected handleEntryClear(event)
{
this.value = ""
this.classList.remove("hideApp")
this._searchNode.value = "";
this._searchNode.focus();
this.dispatchEvent(new Event("change"));
this.updateComplete.then(() =>
{
this.dispatchEvent(new Event("change"));
});
this.requestUpdate('value');
this.validate();
@ -362,16 +293,12 @@ export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitE
{
return <string>this._searchNode?.value ?? "";
}
return this._searchNode ? <LinkInfo>{
id: this._searchNode.value,
app: this.app,
//search: this._searchNode... // content of search field
} : this._value;
return this.__value;
}
set value(val : LinkInfo | string | number)
{
let value : LinkInfo = {app: this.onlyApp || this.app, id: ""};
let value : LinkInfo = {app: this.onlyApp || (this.app || this._appNode?.value), id: ""};
if(typeof val === 'string' && val.length > 0)
{
@ -399,30 +326,79 @@ export class Et2LinkEntry extends Et2InputWidget(FormControlMixin(SlotMixin(LitE
value = (<LinkInfo>val);
}
// If the searchNode is not there yet, hold value. We'll use these values when we create the
// slotted searchNode.
if(this._searchNode == null)
{
this._value = value;
}
else
{
this.app = this._searchNode.app = value.app;
this._searchNode.value = value.id;
}
this.classList.toggle("hideApp", Boolean(value.id));
const oldValue = this.__value;
this.__value = value;
this.classList.toggle("hideApp", Boolean(this.__value.id));
this.requestUpdate("value", oldValue);
}
protected handleLabelClick()
{
this._searchNode.focus();
}
/**
* @return {TemplateResult}
* Update the search node's app & clear selected value when
* selected app changes.
* @param event
* @protected
*/
_inputGroupInputTemplate()
protected handleAppChange(e)
{
this.app = this._appNode.value;
this._searchNode.app = this._appNode.value;
this._searchNode.value = "";
this._searchNode.clearSearch();
this._searchNode.focus();
this.requestUpdate('value');
}
render()
{
const labelTemplate = this._labelTemplate();
const helpTemplate = this._helpTextTemplate();
return html`
<div class="input-group__input" part="control">
<slot name="app"></slot>
<slot name="select"></slot>
<div
part="form-control"
class=${classMap({
'form-control': true,
'form-control--medium': true,
'form-control--has-label': labelTemplate !== nothing,
'form-control--has-help-text': helpTemplate !== nothing
})}
>
${labelTemplate}
<div part="form-control-input" class="form-control-input">
<et2-link-apps
onlyApp=${this.onlyApp ? this.onlyApp : nothing}
?appIcons=${this.appIcons}
?applicationList=${this.applicationList}
?disabled=${this.disabled}
?readonly=${this.disabled}
.value=${this.__value?.app ? this.__value.app : nothing}
@change=${this.handleAppChange}
></et2-link-apps>
<et2-link-search
?placeholder=${this.placeholder}
?required=${this.required}
?disabled=${this.disabled}
?readonly=${this.readonly}
.app=${this.__value?.app || nothing}
.value=${this.__value?.id || nothing}
@change=${this.handleEntrySelect}
@sl-clear=${this.handleEntryClear}
>
${(this.__value?.title) ? html`
<option value=${this.__value.id}>${this.__value.title}</option>
` : nothing}
</et2-link-search>
</div>
${helpTemplate}
</div>
`;
}

View File

@ -335,5 +335,4 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
}
};
// @ts-ignore TypeScript says there's something wrong with types
customElements.define("et2-link-string", Et2LinkString, {extends: 'ul'});
customElements.define("et2-link-string", Et2LinkString);

View File

@ -11,21 +11,19 @@
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import {FormControlMixin, ValidateMixin} from "@lion/form-core";
import {css, html, LitElement, nothing} from "lit";
import {ScopedElementsMixin} from "@lion/core";
import {et2_createWidget, et2_widget} from "../et2_core_widget";
import {et2_file} from "../et2_widget_file";
import {Et2Button} from "../Et2Button/Et2Button";
import {Et2LinkEntry} from "./Et2LinkEntry";
import {egw} from "../../jsapi/egw_global";
import {LinkInfo} from "./Et2Link";
import type {ValidationType} from "@lion/form-core/types/validate/ValidateMixinTypes";
import {ManualMessage} from "../Validators/ManualMessage";
import {Et2Tabs} from "../Layout/Et2Tabs/Et2Tabs";
import {Et2VfsSelectButton} from "../Et2Vfs/Et2VfsSelectButton";
import {Et2LinkPasteDialog, getClipboardFiles} from "./Et2LinkPasteDialog";
import {waitForEvent} from "../Et2Widget/event";
import {classMap} from "lit/directives/class-map.js";
/**
* Choose an existing entry, VFS file or local file, and link it to the current entry.
@ -33,7 +31,7 @@ import {waitForEvent} from "../Et2Widget/event";
* If there is no "current entry", link information will be stored for submission instead
* of being directly linked.
*/
export class Et2LinkTo extends Et2InputWidget(ScopedElementsMixin(FormControlMixin(ValidateMixin(LitElement))))
export class Et2LinkTo extends Et2InputWidget(LitElement)
{
static get properties()
{
@ -73,15 +71,12 @@ export class Et2LinkTo extends Et2InputWidget(ScopedElementsMixin(FormControlMix
.input-group__container {
flex: 1 1 auto;
}
.input-group {
.form-control-input {
display: flex;
width: 100%;
gap: 0.5rem;
}
.input-group__before {
display: flex;
gap: 0.5rem;
}
::slotted(.et2_file) {
width: 30px;
}
@ -160,13 +155,17 @@ export class Et2LinkTo extends Et2InputWidget(ScopedElementsMixin(FormControlMix
return html`
<slot name="before"></slot>
<et2-vfs-select
id="link"
?readonly=${this.readonly}
method=${method || nothing}
method-id=${method_id || nothing}
multiple
title=${this.egw().lang("select file(s) from vfs")}
.buttonLabel=${this.egw().lang('Link')}
.onchange=${this.handleVfsSelected}
@change=${async() =>
{
this.handleVfsSelected(await this.shadowRoot.getElementById("link")._dialog.getComplete());
}}
>
<et2-button slot="footer" image="copy" id="copy" style="order:3" noSubmit="true"
label=${this.egw().lang("copy")}></et2-button>
@ -586,6 +585,35 @@ export class Et2LinkTo extends Et2InputWidget(ScopedElementsMixin(FormControlMix
{
return ['error', 'success'];
}
render()
{
const labelTemplate = this._labelTemplate();
const helpTemplate = this._helpTextTemplate();
return html`
<div
part="form-control"
class=${classMap({
'form-control': true,
'form-control--medium': true,
'form-control--has-label': labelTemplate !== nothing,
'form-control--has-help-text': helpTemplate !== nothing
})}
>
${labelTemplate}
<div part="form-control-input" class="form-control-input" @sl-change=${() =>
{
this.dispatchEvent(new Event("change", {bubbles: true}));
}}>
${this._inputGroupBeforeTemplate()}
${this._inputGroupInputTemplate()}
</div>
${helpTemplate}
</div>
`;
}
}
// @ts-ignore TypeScript is not recognizing that this widget is a LitElement

View File

@ -546,7 +546,7 @@ export class Et2Select extends Et2WithSearchMixin(Et2WidgetWithSelect)
this.updateComplete.then(() => this.fix_bad_value());
}
/** @param {import('@lion/core').PropertyValues } changedProperties */
/** @param changedProperties */
willUpdate(changedProperties : PropertyValues)
{
super.willUpdate(changedProperties);

View File

@ -44,7 +44,6 @@ import {SearchMixinInterface} from "../Et2Widget/SearchMixin";
*
* Optionally, you can override:
* - _emptyLabelTemplate(): How to render the empty label
* - slots(): Most Lion components have an input slot where the <input> tag is created.
* 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().
*

View File

@ -9,7 +9,6 @@
import {css, CSSResultGroup, html, LitElement, nothing, TemplateResult} from "lit";
import {cleanSelectOptions, SelectOption} from "./FindSelectOptions";
import {Validator} from "@lion/form-core";
import {Et2Tag} from "./Tag/Et2Tag";
import {StaticOptions} from "./StaticOptions";
import {dedupeMixin} from "@open-wc/dedupe-mixin";
@ -17,6 +16,7 @@ import {SlOption} from "@shoelace-style/shoelace";
import {Et2Textbox} from "../Et2Textbox/Et2Textbox";
import {until} from "lit/directives/until.js";
import {waitForEvent} from "../Et2Widget/event";
import {Validator} from "../Validators/Validator";
// Otherwise import gets stripped
let keep_import : Et2Tag;

View File

@ -8,8 +8,7 @@
* @author Hadi Nategh
*/
import {css, html} from "lit";
import {SlotMixin} from "@lion/core";
import {css, html, render} from "lit";
import {Et2InputWidget} from "../Et2InputWidget/Et2InputWidget";
import '../Et2Image/Et2Image';
import {SlSwitch} from "@shoelace-style/shoelace";
@ -21,7 +20,7 @@ import shoelace from "../Styles/shoelace";
* Add "et2SlideSwitch" class to use an alternate UI with images. Use CSS to set the images:
*
*/
export class Et2Switch extends Et2InputWidget(SlotMixin(SlSwitch))
export class Et2Switch extends Et2InputWidget(SlSwitch)
{
static get styles()
{
@ -104,17 +103,6 @@ export class Et2Switch extends Et2InputWidget(SlotMixin(SlSwitch))
}
}
get slots()
{
return {
...super.slots,
'': () =>
{
return this.labelTemplate();
}
}
}
constructor()
{
super();
@ -125,6 +113,7 @@ export class Et2Switch extends Et2InputWidget(SlotMixin(SlSwitch))
updated(changedProperties)
{
render(this.labelTemplate(), this);
if(changedProperties.has("toggleOn") || changedProperties.has("toggleOff") || changedProperties.has("label"))
{
if(!this.toggleOn && !this.toggleOff && this._labelNode)

View File

@ -69,7 +69,7 @@ export class Et2Textbox extends Et2InputWidget(SlInput)
super.connectedCallback();
}
/** @param {import('@lion/core').PropertyValues } changedProperties */
/** @param changedProperties */
updated(changedProperties : PropertyValues)
{
super.updated(changedProperties);

View File

@ -9,9 +9,9 @@
/* eslint-disable import/no-extraneous-dependencies */
import {css, html, LitElement} from 'lit';
import {dedupeMixin, SlotMixin} from '@lion/core';
import {Et2InputWidget, Et2InputWidgetInterface} from "../Et2InputWidget/Et2InputWidget";
import {colorsDefStyles} from "../Styles/colorsDefStyles";
import {dedupeMixin} from "@open-wc/dedupe-mixin";
/**
* Invoker mixing adds an invoker button to a widget to trigger some action, e.g.:
@ -25,7 +25,7 @@ import {colorsDefStyles} from "../Styles/colorsDefStyles";
type Constructor<T = Et2InputWidgetInterface> = new (...args : any[]) => T;
export const Et2InvokerMixin = dedupeMixin(<T extends Constructor<LitElement>>(superclass : T) =>
{
class Et2Invoker extends SlotMixin(Et2InputWidget(superclass))
class Et2Invoker extends Et2InputWidget(superclass)
{
/** @type {any} */
static get properties()
@ -193,7 +193,7 @@ export const Et2InvokerMixin = dedupeMixin(<T extends Constructor<LitElement>>(s
return super._oldChange(_ev);
}
/** @param {import('@lion/core').PropertyValues } changedProperties */
/** @param changedProperties */
firstUpdated(changedProperties)
{
super.firstUpdated(changedProperties);

View File

@ -259,4 +259,4 @@ export class Et2VfsMime extends Et2ImageExpose
}
customElements.define("et2-vfs-mime", Et2VfsMime as any, {extends: 'img'});
customElements.define("et2-vfs-mime", Et2VfsMime as any);

View File

@ -9,7 +9,7 @@ import type {IegwAppLocal} from "../../jsapi/egw_global";
import {egw} from "../../jsapi/egw_global";
import {ClassWithAttributes, ClassWithInterfaces} from "../et2_core_inheritance";
import {css, LitElement, PropertyValues, unsafeCSS} from "lit";
import {dedupeMixin} from "@lion/core";
import {dedupeMixin} from "@open-wc/dedupe-mixin";
import type {et2_container} from "../et2_core_baseWidget";
import type {et2_DOMWidget} from "../et2_core_DOMWidget";
@ -495,7 +495,7 @@ const Et2WidgetMixin = <T extends Constructor>(superClass : T) =>
* A property has changed, and we want to make adjustments to other things
* based on that
*
* @param {import('@lion/core').PropertyValues } changedProperties
* @param changedProperties
*/
updated(changedProperties : PropertyValues)
{

View File

@ -13,12 +13,10 @@ import {SlSplitPanel} from "@shoelace-style/shoelace";
import {et2_IDOMNode, et2_IResizeable} from "../../et2_core_interfaces";
import {et2_DOMWidget} from "../../et2_core_DOMWidget";
import {css, html} from "lit";
import {SlotMixin} from "@lion/core";
import {colorsDefStyles} from "../../Styles/colorsDefStyles";
export class Et2Split extends Et2Widget(SlotMixin(SlSplitPanel))
export class Et2Split extends Et2Widget(SlSplitPanel)
{
static get styles()
{
return [

View File

@ -0,0 +1,72 @@
import {customElement} from "lit/decorators/custom-element.js";
import {property} from "lit/decorators/property.js";
import {html, LitElement} from "lit";
/**
* @desc Takes care of accessible rendering of error messages
* Should be used in conjunction with FormControl having ValidateMixin applied
*
* Based on Lion
*/
@customElement("egw-validation-feedback")
export class EgwValidationFeedback extends LitElement
{
@property({type: Array, attribute: false})
feedbackData = [];
/**
* @overridable
* @param {Object} opts
* @param {string | Node | TemplateResult } opts.message message or feedback node or TemplateResult
* @param {string} [opts.type]
* @param {Validator} [opts.validator]
* @protected
*/
// eslint-disable-next-line class-methods-use-this
_messageTemplate({message})
{
return message;
}
/**
* @param changedProperties
*/
updated(changedProperties)
{
super.updated(changedProperties);
if(this.feedbackData && this.feedbackData[0])
{
this.setAttribute('type', this.feedbackData[0].type);
this.currentType = this.feedbackData[0].type;
window.clearTimeout(this.removeMessage);
// TODO: this logic should be in ValidateMixin, so that [show-feedback-for] is in sync,
// plus duration should be configurable
if(this.currentType === 'success')
{
this.removeMessage = window.setTimeout(() =>
{
this.removeAttribute('type');
/** @type {messageMap[]} */
this.feedbackData = [];
}, 3000);
}
}
else if(this.currentType !== 'success')
{
this.removeAttribute('type');
}
}
render()
{
return html`
${this.feedbackData &&
this.feedbackData.map(
({message, type, validator}) => html`
${this._messageTemplate({message, type, validator})}
`,
)}
`;
}
}

View File

@ -1,4 +1,4 @@
import {Pattern} from "@lion/form-core";
import {Pattern} from "./StringValidators";
export class IsEmail extends Pattern
{

View File

@ -1,11 +1,11 @@
import {ResultValidator} from "@lion/form-core";
import {Validator} from "./Validator";
/**
* Manual validator for server-side validation messages passed
* from Etemplate. It just gives whatever message is passed in when created.
*
*/
export class ManualMessage extends ResultValidator
export class ManualMessage extends Validator
{
static get validatorName()
{

View File

@ -1,4 +1,4 @@
import {Pattern} from "@lion/form-core";
import {Pattern} from "./StringValidators"
export class Regex extends Pattern
{

View File

@ -1,6 +1,6 @@
import {Required as LionRequired} from "@lion/form-core";
import {Validator} from "./Validator";
export class Required extends LionRequired
export class Required extends Validator
{
/**
* Returns a Boolean. True if the test fails

View File

@ -0,0 +1,110 @@
import {Validator} from './Validator';
/**
* @param {?} value
*/
const isString = value => typeof value === 'string';
export class IsString extends Validator {
static get validatorName() {
return 'IsString';
}
/**
* @param {?} value
*/
// eslint-disable-next-line class-methods-use-this
execute(value) {
let hasError = false;
if (!isString(value)) {
hasError = true;
}
return hasError;
}
}
export class EqualsLength extends Validator {
static get validatorName() {
return 'EqualsLength';
}
execute(value, length = this.param) {
let hasError = false;
if (!isString(value) || value.length !== length) {
hasError = true;
}
return hasError;
}
}
export class MinLength extends Validator {
static get validatorName() {
return 'MinLength';
}
execute(value, min = this.param) {
let hasError = false;
if (!isString(value) || value.length < min) {
hasError = true;
}
return hasError;
}
}
export class MaxLength extends Validator {
static get validatorName() {
return 'MaxLength';
}
execute(value, max = this.param) {
let hasError = false;
if (!isString(value) || value.length > max) {
hasError = true;
}
return hasError;
}
}
export class MinMaxLength extends Validator {
static get validatorName() {
return 'MinMaxLength';
}
/**
* @param {?} value
*/
execute(value, { min = 0, max = 0 } = this.param) {
let hasError = false;
if (!isString(value) || value.length < min || value.length > max) {
hasError = true;
}
return hasError;
}
}
/**
* @param {?} value
* @param {RegExp} pattern
*/
const hasPattern = (value, pattern) => pattern.test(value);
export class Pattern extends Validator {
static get validatorName() {
return 'Pattern';
}
execute(value, pattern = this.param) {
if (!(pattern instanceof RegExp)) {
throw new Error(
'Psst... Pattern validator expects RegExp object as parameter e.g, new Pattern(/#LionRocks/) or new Pattern(RegExp("#LionRocks")',
);
}
let hasError = false;
if (!isString(value) || !hasPattern(value, pattern)) {
hasError = true;
}
return hasError;
}
}

View File

@ -0,0 +1,102 @@
export class Validator
{
/**
*
* @param {?} [param]
* @param {Object.<string,?>} [config]
*/
constructor(param, config)
{
/** @type {?} */
this.__param = param;
/** @type {Object.<string,?>} */
this.__config = config || {};
this.type = (config && config.type) || 'error'; // Default type supported by ValidateMixin
}
static get validatorName()
{
return '';
}
/**
* @desc The function that returns a Boolean
* @param {?} [modelValue]
* @param {?} [param]
* @param {{}} [config]
* @returns {Boolean|Promise<Boolean>}
*/
// eslint-disable-next-line no-unused-vars, class-methods-use-this
execute(modelValue, param, config) : boolean | Promise<boolean>
{
const ctor = /** @type {typeof Validator} */ (this.constructor);
if(!ctor.validatorName)
{
throw new Error(
'A validator needs to have a name! Please set it via "static get validatorName() { return \'IsCat\'; }"',
);
}
return true;
}
set param(p)
{
this.__param = p;
if(this.dispatchEvent)
{
this.dispatchEvent(new Event('param-changed'));
}
}
get param()
{
return this.__param;
}
set config(c)
{
this.__config = c;
if(this.dispatchEvent)
{
this.dispatchEvent(new Event('config-changed'));
}
}
get config()
{
return this.__config;
}
/**
* @overridable
* @param {MessageData} [data]
* @returns {Promise<string|Node>}
* @protected
*/
async _getMessage(data)
{
const ctor = /** @type {typeof Validator} */ (this.constructor);
const composedData = {
name: ctor.validatorName,
type: this.type,
params: this.param,
config: this.config,
...data,
};
if(this.config.getMessage)
{
if(typeof this.config.getMessage === 'function')
{
return this.config.getMessage(composedData);
}
throw new Error(
`You must provide a value for getMessage of type 'function', you provided a value of type: ${typeof this
.config.getMessage}`,
);
}
return ctor.getMessage(composedData);
}
}

View File

@ -102,6 +102,7 @@ import "./Et2Vfs/Et2VfsSelectButton";
import "./Et2Vfs/Et2VfsSelectDialog";
import "./Et2Vfs/Et2VfsSelectRow";
import "./Et2Vfs/Et2VfsUid";
import "./Validators/EgwValidationFeedback";
import "./Et2Textbox/Et2Password";
import './Et2Textbox/Et2Searchbox';
import "./Et2Tree/Et2Tree";

View File

@ -2112,7 +2112,7 @@ table.et2_grid tbody.ui-sortable:not(.ui-sortable-disabled) > tr:not(.th) {
* Message styles
*/
/* Style used for a generic message (such as success messages or validation errors) */
div.message, lion-validation-feedback[type] {
div.message, egw-validation-feedback[type] {
display: block;
border: 1px solid;
border-color: var(--primary-color, gray);
@ -2124,7 +2124,7 @@ div.message, lion-validation-feedback[type] {
font-size: 12px;
}
div.message.floating, lion-validation-feedback[type] {
div.message.floating, egw-validation-feedback[type] {
position: absolute;
margin: 0px;
z-index: 1;
@ -2132,14 +2132,14 @@ div.message.floating, lion-validation-feedback[type] {
left: 0px;
}
lion-validation-feedback[type] {
egw-validation-feedback[type] {
top: initial;
margin-top: calc(-0.2 * var(--sl-input-height-medium));
width: fit-content;
white-space: nowrap;
}
.message.validation_error, lion-validation-feedback[type="error"] {
.message.validation_error, egw-validation-feedback[type="error"] {
color: var(--error-color);
border-color: var(--error-color);
background-repeat: no-repeat;

View File

@ -1,5 +1,5 @@
import shoelace from "../../api/js/etemplate/Styles/shoelace";
import {css} from "@lion/core";
import {css} from "lit";
import {Et2PortletFavorite} from "../../home/js/Et2PortletFavorite";
/**

View File

@ -1,6 +1,7 @@
import {Et2Date, parseDate} from "../../api/js/etemplate/Et2Date/Et2Date";
import {css} from "@lion/core";
import {CalendarApp} from "./app";
import {css, html, render} from "lit";
import {app} from "../../api/js/jsapi/egw_global";
export class SidemenuDate extends Et2Date
{
@ -35,20 +36,6 @@ export class SidemenuDate extends Et2Date
];
}
get slots()
{
return {
...super.slots,
input: () =>
{
// This element gets hidden and used for value - overridden from parent
const text = document.createElement('input');
text.type = "text";
return text;
}
}
}
constructor()
{
super();
@ -60,8 +47,10 @@ export class SidemenuDate extends Et2Date
this._handleHeaderChange = this._handleHeaderChange.bind(this);
}
async connectedCallback()
{
render(this._inputTemplate(), this);
super.connectedCallback();
this.removeEventListener("change", this._oldChange);
@ -360,7 +349,22 @@ export class SidemenuDate extends Et2Date
// Go directly
app.calendar.update_state(update);
}
/**
* The interactive (form) element.
* @protected
*/
get _inputNode()
{
return this.querySelector('input');
}
protected _inputTemplate()
{
// Plain input
return html`
<input type="text"></input>`;
}
}
// @ts-ignore TypeScript is not recognizing that Et2Date is a LitElement
customElements.define("calendar-date", SidemenuDate);

View File

@ -1,10 +1,11 @@
import {Et2Portlet} from "../../api/js/etemplate/Et2Portlet/Et2Portlet";
import {classMap, css, html} from "@lion/core";
import shoelace from "../../api/js/etemplate/Styles/shoelace";
import {etemplate2} from "../../api/js/etemplate/etemplate2";
import type {SelectOption} from "../../api/js/etemplate/Et2Select/FindSelectOptions";
import {Et2Favorites} from "../../api/js/etemplate/Et2Favorites/Et2Favorites";
import {Et2Dialog} from "../../api/js/etemplate/Et2Dialog/Et2Dialog";
import {css, html} from "lit";
import {classMap} from "lit/directives/class-map.js";
export class Et2PortletFavorite extends Et2Portlet
{

View File

@ -1,6 +1,6 @@
import {Et2Portlet} from "../../api/js/etemplate/Et2Portlet/Et2Portlet";
import shoelace from "../../api/js/etemplate/Styles/shoelace";
import {css} from "@lion/core";
import {css} from "lit";
import {SelectOption} from "../../api/js/etemplate/Et2Select/FindSelectOptions";
import {Et2Dialog} from "../../api/js/etemplate/Et2Dialog/Et2Dialog";

View File

@ -1,6 +1,6 @@
import {Et2Portlet} from "../../api/js/etemplate/Et2Portlet/Et2Portlet";
import {et2_createWidget} from "../../api/js/etemplate/et2_core_widget";
import {css, html, TemplateResult} from "@lion/core";
import {css, html, TemplateResult} from "lit";
import shoelace from "../../api/js/etemplate/Styles/shoelace";
import type {SelectOption} from "../../api/js/etemplate/Et2Select/FindSelectOptions";
import {Et2Dialog} from "../../api/js/etemplate/Et2Dialog/Et2Dialog";

View File

@ -1,7 +1,8 @@
import shoelace from "../../api/js/etemplate/Styles/shoelace";
import {css, html, TemplateResult, unsafeHTML} from "@lion/core";
import {Et2Portlet} from "../../api/js/etemplate/Et2Portlet/Et2Portlet";
import type {SelectOption} from "../../api/js/etemplate/Et2Select/FindSelectOptions";
import {css, html, TemplateResult} from "lit";
import {unsafeHTML} from "lit/directives/unsafe-html.js"
/**
* Home portlet to show a note

View File

@ -1,7 +1,9 @@
import {Et2Portlet} from "../../api/js/etemplate/Et2Portlet/Et2Portlet";
import {classMap, css, html, nothing, repeat, TemplateResult} from "@lion/core";
import shoelace from "../../api/js/etemplate/Styles/shoelace";
import {SelectOption} from "../../api/js/etemplate/Et2Select/FindSelectOptions";
import {css, html, nothing, TemplateResult} from "lit";
import {classMap} from "lit/directives/class-map.js";
import {repeat} from "lit/directives/repeat.js";
/**
* Show current and forecast weather

72
package-lock.json generated
View File

@ -10,8 +10,6 @@
"license": "GPL-2.0",
"dependencies": {
"@bundled-es-modules/pdfjs-dist": "^2.5.207-rc1",
"@lion/core": "^0.21.1",
"@lion/form-core": "^0.16.0",
"@rollup/plugin-commonjs": "^24.0.1",
"@shoelace-style/shoelace": "2.15.0",
"@types/jquery": "^3.5.29",
@ -2295,11 +2293,6 @@
"node": ">=6.9.0"
}
},
"node_modules/@bundled-es-modules/message-format": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/message-format/-/message-format-6.0.4.tgz",
"integrity": "sha512-NGUoPxqsBzDwvRhY3A3L/AhS1hzS9OWappfyDOyCwE7G3W4ua28gau7QwvJz7QzA6ArbAdeb8c1mLjvd1WUFAA=="
},
"node_modules/@bundled-es-modules/pdfjs-dist": {
"version": "2.5.207-rc1",
"resolved": "https://registry.npmjs.org/@bundled-es-modules/pdfjs-dist/-/pdfjs-dist-2.5.207-rc1.tgz",
@ -3061,35 +3054,6 @@
"integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==",
"dev": true
},
"node_modules/@lion/core": {
"version": "0.21.1",
"resolved": "https://registry.npmjs.org/@lion/core/-/core-0.21.1.tgz",
"integrity": "sha512-6lCJ7ZLHQBcsZu/XBOEePG4KxxNFI1OD+1wSA4f9KMyHJVA4+FXZEv1mYniWVsfdPXyyAgKnmfB23xD4Z3kwng==",
"dependencies": {
"@open-wc/dedupe-mixin": "^1.3.0",
"@open-wc/scoped-elements": "^2.0.1",
"lit": "^2.0.2"
}
},
"node_modules/@lion/form-core": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/@lion/form-core/-/form-core-0.16.0.tgz",
"integrity": "sha512-b3Tw0y/5eoIkfowJUqH4JIPvleFOsN09MM6Pb8j7QIyweRk/YlSMbirAlEYmdeTo8aOgGRJhbmrOKmukpsPA/g==",
"dependencies": {
"@lion/core": "^0.21.0",
"@lion/localize": "^0.23.0"
}
},
"node_modules/@lion/localize": {
"version": "0.23.0",
"resolved": "https://registry.npmjs.org/@lion/localize/-/localize-0.23.0.tgz",
"integrity": "sha512-pGN2JzEPukvgtB3+BpeT13KZnI7mCqyq7m/a+A973XFhH1PJlAyEXdkv3bVJsIpNolqXRVP0lU4vgisMsO8IkQ==",
"dependencies": {
"@bundled-es-modules/message-format": "6.0.4",
"@lion/core": "^0.21.0",
"singleton-manager": "^1.4.3"
}
},
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz",
@ -3171,12 +3135,14 @@
"node_modules/@open-wc/dedupe-mixin": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@open-wc/dedupe-mixin/-/dedupe-mixin-1.3.0.tgz",
"integrity": "sha512-UfdK1MPnR6T7f3svzzYBfu3qBkkZ/KsPhcpc3JYhsUY4hbpwNF9wEQtD4Z+/mRqMTJrKg++YSxIxE0FBhY3RIw=="
"integrity": "sha512-UfdK1MPnR6T7f3svzzYBfu3qBkkZ/KsPhcpc3JYhsUY4hbpwNF9wEQtD4Z+/mRqMTJrKg++YSxIxE0FBhY3RIw==",
"dev": true
},
"node_modules/@open-wc/scoped-elements": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@open-wc/scoped-elements/-/scoped-elements-2.0.1.tgz",
"integrity": "sha512-JS6ozxUFwFX3+Er91v9yQzNIaFn7OnE0iESKTbFvkkKdNwvAPtp1fpckBKIvWk8Ae9ZcoI9DYZuT2DDbMPcadA==",
"dev": true,
"dependencies": {
"@lit/reactive-element": "^1.0.0",
"@open-wc/dedupe-mixin": "^1.3.0",
@ -3891,12 +3857,6 @@
"integrity": "sha512-ARATsLdrGPUnaBvxLhUlnltcMgn7pQG312S8ccdYlnyijabrX9RN/KN/iGj9Am96CoW8e/K9628BA7Bv4XHdrA==",
"dev": true
},
"node_modules/@types/prop-types": {
"version": "15.7.12",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz",
"integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==",
"peer": true
},
"node_modules/@types/qs": {
"version": "6.9.7",
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz",
@ -3909,16 +3869,6 @@
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
"dev": true
},
"node_modules/@types/react": {
"version": "18.2.72",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.72.tgz",
"integrity": "sha512-/e7GWxGzXQF7OJAua7UAYqYi/4VpXEfbGtmYQcAQwP3SjjjAXfybTf/JK5S+SaetB/ChXl8Y2g1hCsj7jDXxcg==",
"peer": true,
"dependencies": {
"@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@ -5190,7 +5140,8 @@
"node_modules/@webcomponents/scoped-custom-element-registry": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/@webcomponents/scoped-custom-element-registry/-/scoped-custom-element-registry-0.0.3.tgz",
"integrity": "sha512-lpSzgDCGbM99dytb3+J3Suo4+Bk1E13MPnWB42JK8GwxSAxFz+tC7TTv2hhDSIE2IirGNKNKCf3m08ecu6eAsQ=="
"integrity": "sha512-lpSzgDCGbM99dytb3+J3Suo4+Bk1E13MPnWB42JK8GwxSAxFz+tC7TTv2hhDSIE2IirGNKNKCf3m08ecu6eAsQ==",
"dev": true
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
@ -7138,12 +7089,6 @@
"node": ">=14"
}
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
"peer": true
},
"node_modules/custom-element-jet-brains-integration": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/custom-element-jet-brains-integration/-/custom-element-jet-brains-integration-1.2.1.tgz",
@ -12995,7 +12940,7 @@
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"devOptional": true,
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
@ -13459,11 +13404,6 @@
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
"dev": true
},
"node_modules/singleton-manager": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/singleton-manager/-/singleton-manager-1.4.3.tgz",
"integrity": "sha512-Jy1Ib9cO9xCQ6UZ/vyFOqqWMnSpfZ8/Sc2vme944aWsCLO+lMPiFG9kGZGpyiRT9maYeI0JyZH1CGgjmkSN8VA=="
},
"node_modules/sinon": {
"version": "11.1.2",
"resolved": "https://registry.npmjs.org/sinon/-/sinon-11.1.2.tgz",

View File

@ -82,8 +82,6 @@
},
"dependencies": {
"@bundled-es-modules/pdfjs-dist": "^2.5.207-rc1",
"@lion/core": "^0.21.1",
"@lion/form-core": "^0.16.0",
"@rollup/plugin-commonjs": "^24.0.1",
"@shoelace-style/shoelace": "2.15.0",
"@types/jquery": "^3.5.29",