import { computePosition, flip, inline, offset, shift } from "@floating-ui/dom"; import { LitElement, PropertyValueMap, css, html } from "lit"; import { customElement, property, queryAssignedElements, state } from 'lit/decorators.js'; import { classMap } from "lit/directives/class-map.js"; import { Ref, createRef, ref } from "lit/directives/ref.js"; import { ifDefined } from "lit/directives/if-defined.js"; import { BaseElement } from "../hierarchy/BaseElement"; import { BtnSizes, BtnVariants } from "./Btn"; @customElement("thermal-dropdown") export class ThermalDropdown extends BaseElement { static shadowRootOptions: ShadowRootInit = { ...LitElement.shadowRootOptions, // mode: "open" } @queryAssignedElements({ slot: 'option' }) _options!: Array; protected dropdownRef: Ref = createRef(); protected invokerRef: Ref = createRef(); protected optionsRef: Ref = createRef(); @property({ type: String, reflect: true }) isOpen: string = "close"; @property({ type: String, reflect: true, attribute: true }) @state() interactive: "on" | "off" = "on"; @property({ type: String, reflect: true}) public variant?: BtnVariants; @property({ type: String, reflect: true, attribute: true }) public size?: BtnSizes; @property({ type: String }) public plain?: boolean; setOpen() { this.isOpen = "open"; } setClose() { this.isOpen = "close"; } toggle() { if (this.interactive === "off") { return; } if (this.isOpen === "open") this.isOpen = "close"; else this.isOpen = "open"; } connectedCallback(): void { super.connectedCallback(); } disconnectedCallback(): void { super.disconnectedCallback(); } placeOptions() { computePosition(this.invokerRef.value!, this.optionsRef.value!, { middleware: [offset(2), flip(), inline(), shift()], placement: "bottom-start", strategy: "fixed" }) .then(({ x, y }) => { if (this.optionsRef.value) { this.optionsRef.value.style.left = `${x}px`; this.optionsRef.value.style.top = `${y}px`; } }); } protected updated(_changedProperties: PropertyValueMap | Map): void { super.updated(_changedProperties); this.placeOptions(); } protected firstUpdated(_changedProperties: PropertyValueMap | Map): void { super.firstUpdated(_changedProperties); this._options.forEach(option => { option.childNodes.forEach(child => child.addEventListener("click", () => { this.setClose(); })); }); } attributeChangedCallback(name: string, _old: string | null, value: string | null): void { if (name === "isopen") { if (value === "open") { this.optionsRef.value?.classList.add("dropdown-options__show"); this.dropdownRef.value?.classList.add("dropdown__open"); } else { this.optionsRef.value?.classList.remove("dropdown-options__show"); this.dropdownRef.value?.classList.remove("dropdown__open"); } } } static styles = css` .mayNot { opacity: .5; cursor: not-allowed; } .dropdown { width: max-content; } .dropdown-invoker { width: max-content; display: flex; } .dropdown-invoker-wrapper { display: flex; align-items: center; } .dropdown-invoker-wrapper-icon { width: calc( var( --thermal-gap ) * .856 ); line-height: 0; padding-left: calc( var( --thermal-gap ) * .5 ); } .dropdown-options { z-index: 9999; width: max-content; /** position: absolute; */ position: fixed; top: 0; left: 0; padding: 5px 10px; border: 1px solid var( --thermal-slate ); border-radius: var( --thermal-radius ); background-color: var( --thermal-slate-light ); box-shadow: var( --thermal-shadow ); display: none; ::slotted( div:not(:last-child) ) { margin-bottom: calc( var( --thermal-gap ) * .5 ); } } .dropdown-options__show { display: block; } .clicker { display: none; } .dropdown__open { .clicker { z-index: 9998; display: block; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; } } slot[name="option"]::slotted(*) { display: block; width: 100%; margin-top: 5px; margin-bottom: 5px; width: 100%; } `; render() { const invokerClasses = { "dropdown-invoker": true, may: this.interactive === "on", mayNot: this.interactive === "off" }; return html` `; } }