import { LitElement, html, css, PropertyValues } from "lit";
import { customElement, property, state } from "lit/decorators.js";
import { provide } from "@lit-labs/context";
import AutocompleteController from "./controllers/autocomplete-controller";
import { debounce } from "./utils/debounce";
import { AddressContext, addressContext } from "./context/address.context";
import "./input.ts";
import "./dropdown-menu.ts";
import { Address } from "./controllers/autocomplete.parser";
@customElement("hvs-widget")
export default class HvsWidget extends LitElement {
@property({ attribute: "apikey", type: String })
apikey: string = "";
@property({ attribute: "api-url", type: String })
apiURL?: string;
@property({ attribute: "report-url", type: String })
reportUrl?: string;
@property({ attribute: "states", type: String })
states?: string;
@property({ attribute: "preferred-states", type: String })
preferredStates?: string;
@property({ attribute: "limit", type: String })
limit?: string;
@property({ attribute: "no-result-message", type: String })
noResultsMessage?: string = "No results found";
@property({ attribute: "source", type: String })
source?: string;
@property({ attribute: "username", type: String })
userName?: string;
@property({ attribute: "contact-id", type: String })
contactID?: string;
@property({ attribute: "contact-first-name", type: String })
contactFirstName?: string;
@property({ attribute: "contact-last-name", type: String })
contactLastName?: string;
@property({ attribute: "contact-email", type: String })
contactEmail?: string;
@property({ attribute: "new-window", type: Boolean })
newWindow?: boolean;
@property({ attribute: "search-icon", type: String })
searchIcon?: string = "";
@property({ attribute: "placeholder", type: String })
placeholder?: string = "";
@property({ attribute: "is-mobile", type: Number })
isMobile: number = 0;
@property({ attribute: "has-agent-details", type: Number })
hasAgentDetails: number = 0;
@property({ attribute: "protocol", type: String })
protocol: string = "";
@property({ attribute: "redirect-user", type: String })
redirectUser: string = "";
@property({ attribute: "should-store-address", type: Boolean })
shouldStoreAddress: boolean = false;
@property({ attribute: "value", type: String })
value?: string = "";
@property({ attribute: "country", type: String })
country: string = "us";
@property({ attribute: "environment", type: String })
environment: "production" | "staging" = "production";
@state()
query: string = "";
@state()
selectedAddress?: Address;
@state()
showDropdown: boolean = true;
/**
* Tracks whether the user has interacted with the input
* after the dropdown was closed
*/
@state()
private userWantsToReopenDropdown: boolean = false;
@provide({ context: addressContext })
@property({ attribute: false })
public addressProvider: AddressContext = {
setAddress: (address) => {
this.selectedAddress = address;
},
setShowDropdown: (value) => {
this.showDropdown = value;
// If we're closing the dropdown, reset the reopen flag
if (!value) {
this.userWantsToReopenDropdown = false;
}
},
reopenDropdownWithResults: () => {
// Only set this flag if we have results to show
if (this.autocomplete.addresses.length > 0) {
this.userWantsToReopenDropdown = true;
this.showDropdown = true;
}
},
hasSearchResults: () => {
return this.autocomplete.addresses.length > 0;
}
};
private autocomplete = new AutocompleteController(this);
debounceInputHandler = debounce(async (value: string) => {
await this.onInputChanged(value);
}, 1000);
async onInputChanged(value: string) {
// Always show dropdown during typing
this.showDropdown = true;
this.query = value;
// Fetch autocomplete results
await this.autocomplete.fetchAddressAutocomplete(value, {
apikey: this.apikey,
apiURL: this.apiURL ?? "https://api.percyx.com/v1/address/autocomplete-all-v2",
states: this.states,
preferredStates: this.preferredStates,
limit: this.limit,
v2: true,
country: this.country,
});
// If we got results, make sure the dropdown is open
if (this.autocomplete.addresses.length > 0) {
this.showDropdown = true;
}
}
firstUpdated(): void {
this.autocomplete.validateTenant(this.apikey, this.environment);
}
protected updated(
_changedProperties: PropertyValues
): void {
if (_changedProperties.has("apikey")) {
this.autocomplete.validateTenant(this.apikey, this.environment);
}
}
render() {
// Get status of search results and loading
const hasAddresses = this.autocomplete.addresses.length > 0;
const isTyping = this.query.length > 1;
const isLoading = this.autocomplete.isLoading;
// Determine if the dropdown should be displayed based on these conditions:
// 1. Standard dropdown behavior (showDropdown is true + conditions)
// 2. User explicitly wants to view previous results
const standardDropdownCondition =
this.showDropdown && (hasAddresses || isTyping || isLoading);
const userReopenCondition =
this.userWantsToReopenDropdown && hasAddresses;
const shouldOpenDropdown = standardDropdownCondition || userReopenCondition;
return html`
`;
}
static styles = css`
.hvs-container {
width: var(--hvs-input-width, 100%);
min-width: var(--hvs-input-width, 100%);
position: relative;
display: flex;
flex-direction: column;
align-items: center;
/* Shadow/xs */
box-shadow: 0px 1px 2px rgba(16, 24, 40, 0.05);
border-radius: 8px;
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"hvs-widget": HvsWidget;
}
}