egroupware_official/api/js/etemplate/Et2DropdownButton/Et2DropdownButton.ts

202 lines
5.3 KiB
TypeScript
Raw Normal View History

2022-05-12 23:22:49 +02:00
/**
* EGroupware eTemplate2 - Dropdown Button widget
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {SlButtonGroup, SlDropdown} from "@shoelace-style/shoelace";
import {css, html, LitElement, TemplateResult} from "lit";
2023-08-18 16:47:37 +02:00
import {Et2WidgetWithSelectMixin} from "../Et2Select/Et2WidgetWithSelectMixin";
2022-05-12 23:22:49 +02:00
import {SelectOption} from "../Et2Select/FindSelectOptions";
import shoelace from "../Styles/shoelace";
import {ifDefined} from "lit/directives/if-defined.js";
2022-05-12 23:22:49 +02:00
/**
* A split button - a button with a dropdown list
*
* There are several parts to the button UI:
* - Container: This is what is percieved as the dropdown button, the whole package together
* - Button: The part on the left that can be clicked
* - Arrow: The button to display the choices
* - Menu: The list of choices
*
* Menu options are passed via the select_options. They are normally the same
* as for a select box, but the title can also be full HTML if needed.
*
*/
export class Et2DropdownButton extends Et2WidgetWithSelectMixin(LitElement)
2022-05-12 23:22:49 +02:00
{
static get styles()
{
return [
...super.styles,
shoelace,
2022-05-12 23:22:49 +02:00
css`
:host {
/* Avoid unwanted style overlap from button */
border: none;
background-color: none;
2022-05-18 19:01:27 +02:00
}
:host, sl-menu {
/**
Adapt shoelace color variables to what we want
Maybe some logical variables from etemplate2.css here?
*/
--sl-color-primary-50: rgb(244, 246, 247);
--sl-color-primary-100: var(--gray-10);
--sl-color-primary-300: var(--input-border-color);
--sl-color-primary-400: var(--input-border-color);
2022-05-18 19:01:27 +02:00
--sl-color-primary-600: var(--primary-background-color);
--sl-color-primary-700: #505050;
2022-05-12 23:22:49 +02:00
}
:host(:active), :host([active]) {
background-color: initial;
}
2022-05-18 22:39:25 +02:00
sl-button-group {
display: initial;
}
#main {
flex: 1 1 auto;
}
2022-05-12 23:22:49 +02:00
`,
];
}
static get properties()
{
return {
...super.properties
};
}
2022-05-12 23:22:49 +02:00
// Make sure imports stay
private _group : SlButtonGroup;
private _dropdow : SlDropdown;
constructor()
{
super();
// Bind handlers - parent already got click
this._handleSelect = this._handleSelect.bind(this);
}
2022-05-12 23:22:49 +02:00
connectedCallback()
{
super.connectedCallback();
// Rebind click to just the main button, not the whole thing
2022-05-12 23:22:49 +02:00
this.removeEventListener("click", this._handleClick);
}
protected _renderOptions()
{
// We have our own render, so we can handle it internally
2022-05-12 23:22:49 +02:00
}
render() : TemplateResult
2022-05-12 23:22:49 +02:00
{
if(this.readonly)
{
return html``;
}
2022-05-12 23:22:49 +02:00
return html`
<sl-button-group>
2024-08-13 21:17:05 +02:00
<sl-button size="${egwIsMobile() ? "large" : "medium"}" id="main" part="main"
?disabled=${this.disabled}
@click=${this._handleClick}
>
${this.label}
</sl-button>
2024-08-13 21:17:05 +02:00
<sl-dropdown placement="bottom-end" hoist part="dropdown">
2024-08-08 21:58:05 +02:00
<slot name="trigger" slot="trigger">
2024-08-13 21:17:05 +02:00
<sl-button part="trigger" size="${egwIsMobile() ? "large" : "medium"}" slot="trigger" caret
?disabled=${this.disabled}></sl-button>
2024-08-08 21:58:05 +02:00
</slot>
2024-08-13 21:17:05 +02:00
<sl-menu @sl-select=${this._handleSelect} part="menu">
${(this.select_options || []).map((option : SelectOption) => this._optionTemplate(option))}
<slot></slot>
2022-05-12 23:22:49 +02:00
</sl-menu>
</sl-dropdown>
</sl-button-group>
`;
}
2022-05-18 19:01:27 +02:00
_optionTemplate(option : SelectOption) : TemplateResult
2022-05-12 23:22:49 +02:00
{
let icon = option.icon ? html`
<et2-image slot="prefix" src=${option.icon} icon></et2-image>` : '';
return html`
<sl-menu-item
value="${option.value}"
type="${ifDefined(option.checkbox)}checkbox"
?checked=${option.checked}
>
2022-05-12 23:22:49 +02:00
${icon}
${this.noLang ? option.label : this.egw().lang(option.label)}
</sl-menu-item>`;
2022-05-12 23:22:49 +02:00
}
protected _handleSelect(ev)
{
this._value = ev.detail.item.value;
// Trigger a change event
this.dispatchEvent(new Event("change"));
// Let it bubble, if anyone else is interested
}
get value() : string
{
return this._value;
}
set value(new_value)
{
let oldValue = this.value;
this._value = new_value;
this.requestUpdate("value", oldValue);
}
2022-05-18 19:01:27 +02:00
get _optionTargetNode() : HTMLElement
{
return this.shadowRoot.querySelector("sl-menu");
}
2022-05-12 23:22:49 +02:00
get buttonNode()
{
return this.shadowRoot.querySelector("#main");
}
get triggerButtonNode()
{
return this.shadowRoot.querySelector("[slot='trigger']");
}
get dropdownNode()
{
return this.shadowRoot.querySelector("sl-dropdown");
2022-05-12 23:22:49 +02:00
}
blur()
{
this.shadowRoot.querySelector("sl-button-group")?.dispatchEvent(new Event('blur'));
}
focus()
{
this.shadowRoot.querySelector("sl-button-group")?.dispatchEvent(new Event('focus'));
}
2022-05-12 23:22:49 +02:00
}
// @ts-ignore TypeScript is not recognizing that Et2Button is a LitElement
customElements.define("et2-dropdown-button", Et2DropdownButton);