egroupware/api/js/etemplate/Et2Url/Et2InvokerMixin.ts
ralf 0ad3cf9832 fix not working onchange with Shoelace components: need to bind sl-change instead of change
adding change handler to invoker mixing to enable/disable invoker when value changes
2022-07-27 12:33:37 +02:00

225 lines
5.3 KiB
TypeScript

/**
* EGroupware eTemplate2 - InvokerMixing
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package api
* @link https://www.egroupware.org
* @author Ralf Becker
*/
/* eslint-disable import/no-extraneous-dependencies */
import {css, dedupeMixin, html, LitElement, SlotMixin} from '@lion/core';
import {Et2InputWidget, Et2InputWidgetInterface} from "../Et2InputWidget/Et2InputWidget";
import {colorsDefStyles} from "../Styles/colorsDefStyles";
/**
* Invoker mixing adds an invoker button to a widget to trigger some action, e.g.:
* - searchbox to delete input
* - url to open url
* - url-email to open mail compose
*
* Inspired by Lion date-picker.
*/
type Constructor<T = Et2InputWidgetInterface> = new (...args : any[]) => T;
export const Et2InvokerMixin = dedupeMixin(<T extends Constructor<LitElement>>(superclass : T) =>
{
class Et2Invoker extends SlotMixin(Et2InputWidget(superclass))
{
/** @type {any} */
static get properties()
{
return {
/**
* Textual label or image specifier for egw.image()
*/
_invokerLabel: {
type: String,
},
_invokerTitle: {
type: String,
},
_invokerAction: {
type: Function,
}
};
}
static get styles()
{
return [
...super.styles,
colorsDefStyles,
css`
::slotted(input), input, ::slotted(select) {
background-color: transparent;
border: none !important;
}
.input-group {
border: 1px solid var(--input-border-color);
}
.input-group__suffix{
text-align: center;
}
.input-group__container {
align-items: center
}
::slotted([slot="suffix"]) {
width: 14px;
border: none !important;
background-color: transparent !important;
width: 1em;
height: 1em;
background-position: center right;
background-size: contain;
background-repeat: no-repeat;
}
::slotted(:disabled) {cursor: default !important;}
:host(:hover) ::slotted([slot="suffix"]) {
cursor: pointer;
}
`,
];
}
get slots()
{
return {
...super.slots,
suffix: () =>
{
return this._invokerTemplate();
},
};
}
/**
* @protected
*/
get _invokerNode()
{
return /** @type {HTMLElement} */ (this.querySelector(`#${this.__invokerId}`));
}
constructor(...args : any[])
{
super(...args);
/** @private */
this.__invokerId = this.__createUniqueIdForA11y();
// default for properties
this._invokerTitle = 'Click to open';
}
/** @private */
__createUniqueIdForA11y()
{
return `${this.localName}-${Math.random().toString(36).substr(2, 10)}`;
}
/**
* @param {PropertyKey} name
* @param {?} oldValue
*/
requestUpdate(name, oldValue)
{
super.requestUpdate(name, oldValue);
if (name === 'disabled' || name === 'showsFeedbackFor' || name === 'modelValue')
{
this._toggleInvokerDisabled();
}
if (name === '_invokerLabel' || name === '_invokerTitle')
{
this._toggleInvoker();
}
if (name === '_invokerAction')
{
if (oldValue) this._invokerNode?.removeEventListener('click', oldValue);
this._invokerNode?.addEventListener('click', this._invokerAction.bind(this), true);
}
}
/**
* (Un)Hide invoker, if no label or action defined
*
* @protected
* */
_toggleInvoker()
{
if (this._invokerNode)
{
this._invokerNode.style.display = !this._invokerLabel ? 'none' : 'inline-block';
const img = this._invokerLabel ? this.egw().image(this._invokerLabel) : null;
if (img)
{
this._invokerNode.style.backgroundImage = 'url('+img+')';
this._invokerNode.innerHTML = '';
}
else
{
this._invokerNode.style.backgroundImage = 'none';
this._invokerNode.innerHTML = this._invokerLabel || '';
}
this._invokerNode.title = this._invokerTitle || '';
}
}
/**
* Method to check if invoker can be activated: not disabled, empty or invalid
*
* @protected
* */
_toggleInvokerDisabled()
{
if (this._invokerNode)
{
const invokerNode = /** @type {HTMLElement & {disabled: boolean}} */ (this._invokerNode);
invokerNode.disabled = this.disabled || !this.value || (this.input && !this.input.reportValidity())
}
}
/**
* Reimplemented to enable/disable invoker on content change
*
* @param _ev
* @returns
*/
_oldChange(_ev: Event): boolean
{
this._toggleInvokerDisabled();
return super._oldChange(_ev);
}
/** @param {import('@lion/core').PropertyValues } changedProperties */
firstUpdated(changedProperties)
{
super.firstUpdated(changedProperties);
this._toggleInvokerDisabled();
this._toggleInvoker();
}
/**
* Subclassers can replace this with their custom extension invoker,
* like `<my-button><calendar-icon></calendar-icon></my-button>`
*/
// eslint-disable-next-line class-methods-use-this
_invokerTemplate()
{
return html`
<button
type="button"
@click="${this._invokerAction}"
id="${this.__invokerId}"
aria-label="${this._invokerTitle}"
title="${this._invokerTitle}"
>
${this._invokerLabel}
</button>
`;
}
}
return Et2Invoker as unknown as Constructor<Et2Invoker> & T;
})