): TemplateResult {
if (this.shouldStoreAddress) {
return html`${this.createHighlightedAddress(address.display_address)} `;
} else {
return html`
${this.createHighlightedAddress(address.display_address)}
`;
}
}
/**
* Renders the content of an address item in the dropdown
* @param address The address to render
* @returns HTML template for the address item content
*/
renderAddressItemContent(address: Address): TemplateResult {
const link = this.generateSignedLink(address);
// Building cluster (parent)
if (address.startsub && parseInt(address.startsub) >= 1) {
return html`
${address.nsubs}
${this.createHighlightedAddress(address.display_address)}
`;
}
// Subunit within a building
if (address.hash) {
return html`
${this.renderAddressContent(address, link)}
`;
}
// Regular address
return html`
${this.renderAddressContent(address, link)}
`;
}
renderAddresses() {
if (this.addresses.length >= 1) {
return html`${repeat(
this.addresses,
(address) => address.hash,
(addressTemplate, index) => {
// Determine visible classes for the item, enhanced for animations
const isSubBuildingItem = Boolean(addressTemplate.hash?.length);
const isDisplayed = addressTemplate?.hash === this.selectedClusterHash;
// Create animation delay for staggered appearance of subitems
const animationStyle = isSubBuildingItem && isDisplayed ?
`animation-delay: ${(addressTemplate.sub || 0) * 0.05}s` : '';
const classes = {
"dropdown-item": true,
highlighted: index === this.highlightedIndex,
"address-item": true,
selected: this.selectedAddress?.display_address === addressTemplate.display_address,
"sub-building-item": isSubBuildingItem,
"display-sub-building": isDisplayed,
};
/**
* Handle click on a dropdown item
* @param item The address item being clicked
*/
const handleItemClick = (item: Address, currentIndex: number) => {
// Update the highlighted index to match the clicked item
this.highlightedIndex = currentIndex;
// Set the flag if this is a building cluster item
if (item.startsub) {
this.handlingClusterClick = true;
}
this.onSelectedAddressHandler(item);
};
// Add data-cluster-hash attribute if it's a building cluster
const clusterHash = addressTemplate.startsub ?? '';
return html` handleItemClick(addressTemplate, index)}
class="${classMap(classes)}"
style="${animationStyle}"
tabindex="-1"
data-cluster-hash="${clusterHash}"
>
${this.renderAddressItemContent(addressTemplate)}
`;
}
)}`;
}
if (this.address.length && this.addresses.length <= 0) {
return html`
${this.noResultsMessage}
`;
}
return null;
}
// Flag to track if we're handling a cluster click
private handlingClusterClick = false;
/**
* Handles document clicks to determine if the dropdown should be closed
* @param event The click event
*/
private handleDocumentClick = (event: MouseEvent) => {
// If we're handling a cluster click, don't close the dropdown
if (this.handlingClusterClick) {
this.handlingClusterClick = false; // Reset the flag
return;
}
const target = event.target as HTMLElement;
// Skip if the target element is the input itself
if (target.id === 'hvs-input') {
return;
}
// Check if the click is inside this component
const clickedInside = this.contains(target);
// If clicked outside and the dropdown is open, close it
if (!clickedInside && this.isOpen) {
this.adressProvider?.setShowDropdown(false);
}
};
protected firstUpdated() {
// Add event listener to handle clicks outside the dropdown
document.addEventListener('click', this.handleDocumentClick);
}
disconnectedCallback() {
super.disconnectedCallback();
// Clean up event listeners when the component is disconnected
document.removeEventListener('click', this.handleDocumentClick);
}
protected updated(changedProperties: Map) {
// Focus the dropdown list when it opens
if (changedProperties.has('isOpen') && this.isOpen) {
setTimeout(() => {
const dropdownList = this.shadowRoot?.querySelector('.dropdown-list') as HTMLElement;
if (dropdownList) {
dropdownList.focus();
}
}, 0);
}
}
protected render() {
if (this.isLoading) {
this.highlightedIndex = 0;
}
return html`
{
// Stop propagation to ensure document click handler doesn't interfere
e.stopPropagation();
this.handleInputClick();
}}
/>
${this.isLoading ? this.renderSkeleton() : this.renderAddresses()}
`;
}
static styles = css`
/* Base dropdown styling */
.dropdown {
position: relative;
transition: all 0.2s ease;
}
/* Enhanced dropdown list with smoother animations */
.dropdown-list {
position: absolute;
width: 100%;
overflow-y: auto;
z-index: 1000;
outline: none;
max-height: 360px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08), 0 0 4px rgba(0, 0, 0, 0.05);
border-radius: 8px;
transition: all 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}
.dropdown-list:focus {
outline: var(--hvs-dropdown-focus-outline, 2px solid #3b82f6);
outline-offset: 2px;
}
/* Item styling with hover animations */
.dropdown-item {
padding: 10px 12px;
cursor: pointer;
outline: none;
transition: all 0.2s ease;
border-left: 3px solid transparent;
}
.dropdown-item:hover {
transform: translateX(2px);
}
.highlighted {
background-color: var(
--hvs-dropdown-address-selected-bg-color,
#3b82f6
) !important;
color: var(--hvs-dropdown-address-selected-text-color, white) !important;
border-left-color: var(--hvs-dropdown-address-selected-bg-color, #3b82f6) !important;
}
.highlighted a {
color: var(--hvs-dropdown-address-selected-text-color, white) !important;
}
/* Input styling */
.input div {
position: relative;
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
}
.input input {
font-family: var(--hvs-font-family, inherit);
box-sizing: border-box;
padding-top: var(--hvs-input-padding-top, 8px);
padding-bottom: var(--hvs-input-padding-bottom, 8px);
padding-left: var(--hvs-input-padding-left, 12px);
padding-right: var(--hvs-input-padding-right, 32px);
border: none;
border-top-left-radius: var(--hvs-input-border-top-left-radius, 8px);
border-top-right-radius: var(--hvs-input-border-top-right-radius, 8px);
border-bottom-left-radius: var(
--hvs-input-border-bottom-left-radius,
8px
);
border-bottom-right-radius: var(
--hvs-input-border-bottom-right-radius,
8px
);
border-top: var(--hvs-input-border-top);
border-right: var(--hvs-input-border-right);
border-bottom: var(--hvs-input-border-bottom);
border-left: var(--hvs-input-border-left);
border-color: var(--hvs-input-border-color, #d0d5dd);
border-width: var(--hvs-input-border-width, 1px);
border-style: var(--hvs-input-border-style, solid);
width: 100%;
min-width: 100%;
height: var(--hvs-input-height, 40px);
font-style: normal;
font-weight: 400;
font-size: var(--hvs-input-font-size, 16px);
line-height: var(--hvs-input-line-height, 24px);
background: var(--hvs-input-background, transparent);
color: var(--hvs-input-text-color, #667085);
transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
input:focus-visible {
outline: var(--hvs-input-ring-color, #98a2b3 auto 1px);
}
/* Dropdown list container */
ul {
position: absolute;
display: flex;
z-index: 9;
flex-direction: column;
box-sizing: border-box;
align-items: flex-start;
max-height: 360px;
overflow-y: auto;
opacity: 0;
padding: 8px 0;
left: 0;
border-radius: 8px;
width: 100%;
background: var(--hvs-dropdown-menu-color, white);
margin-top: 4px;
filter: drop-shadow(0 4px 6px rgba(0, 0, 0, 0.1))
drop-shadow(0 2px 4px rgba(0, 0, 0, 0.06));
will-change: transform, opacity;
}
ul:has(li):has(.skeleton) {
padding: 1rem;
gap: 8px;
}
ul:has(li):has(.no-results) {
padding: 0;
}
/* List item styling */
li {
list-style: none;
font-family:
ui-sans-serif,
system-ui,
-apple-system,
BlinkMacSystemFont,
"Segoe UI",
Roboto,
"Helvetica Neue",
Arial,
"Noto Sans",
sans-serif,
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji";
box-sizing: border-box;
text-align: left;
cursor: default;
text-decoration: none;
transition: transform 0.2s ease;
}
/* Address item styling */
.address-item {
width: 100%;
padding: 10px 16px;
border-left: 3px solid transparent;
transition: border-left-color 0.2s ease, background-color 0.2s ease;
}
.address-item a {
color: var(--hvs-dropdown-address-text-color, #374151);
text-decoration: none;
width: 100%;
transition: color 0.2s ease;
}
.address-item span.building-cluster {
width: 100%;
display: flex;
flex-direction: row;
gap: 8px;
align-items: center;
}
.address-item span.sub-building {
width: 100%;
display: flex;
align-items: center;
gap: 8px;
padding-left: 8px;
transition: padding-left 0.2s ease;
}
.address-item span.sub-building:hover {
padding-left: 12px;
}
.address-item:nth-child(even) {
background-color: var(--hvs-dropdown-address-bg-color, rgba(243, 244, 246, 0.6));
}
.address-item:hover {
background-color: var(--hvs-dropdown-address-hover-bg-color, #bfdbfe);
border-left-color: var(--hvs-dropdown-address-hover-bg-color, #bfdbfe);
}
.address-item.selected {
background-color: var(--hvs-dropdown-address-selected-bg-color, #3b82f6);
color: var(--hvs-dropdown-address-selected-text-color, white);
border-left-color: var(--hvs-dropdown-address-selected-bg-color, #3b82f6);
}
.address-item.selected a {
color: var(--hvs-dropdown-address-selected-text-color, white);
}
.address-item.selected > div.building-units {
background-color: rgba(255, 255, 255, 0.9);
transform: scale(1.05);
}
/* Special styling for building units */
.sub-building-item {
display: none;
opacity: 0;
transform: translateY(-10px);
height: 0;
}
.sub-building-item.display-sub-building {
display: block;
animation: fade-slide-down 0.3s forwards;
height: auto;
opacity: 1;
transform: translateY(0);
}
.building-units {
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
font-size: small;
padding: 2px 6px;
gap: 4px;
background-color: #e4e6e9;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
}
.building-units:hover {
transform: scale(1.05);
}
.highlight-address {
display: inline-table;
transition: all 0.2s ease;
}
.highlight-address strong {
font-weight: 600;
}
/* No results styling */
.no-results {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
width: 100%;
padding: 16px 0;
color: var(--hvs-dropdown-no-results-text-color, #1f2937);
}
.no-results-container {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
width: 100%;
gap: 8px;
padding: 12px 16px;
background-color: var(--hvs-dropdown-no-results-bg-color, #f3f4f6);
border-radius: 6px;
box-sizing: border-box;
animation: fade-in 0.3s ease;
}
/* Button styling */
.get-report-button {
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 600;
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
display: inline-flex;
justify-content: center;
text-decoration: none;
color: var(--hvs-get-report-button-text-color, white);
background: var(--hvs-get-report-button-bg-color, #0f172aab);
transition: background-color 0.2s ease, transform 0.2s ease;
}
.get-report-button:hover {
background-color: var(--hvs-get-report-button-bg-color, #0f172aab);
opacity: 0.9;
transform: translateY(-1px);
}
/* Loading skeleton animation */
.skeleton {
animation: skeleton-pulse 1.5s ease-in-out infinite;
background: linear-gradient(
90deg,
rgba(229, 231, 235, 0.5) 25%,
rgba(209, 213, 219, 0.5) 50%,
rgba(229, 231, 235, 0.5) 75%
);
background-size: 200% 100%;
border-radius: 0.25rem;
width: 100%;
height: 24px;
margin-bottom: 8px;
}
/* Animation classes */
.animate-close {
animation: slide-up 0.25s cubic-bezier(0.4, 0, 0.2, 1) forwards;
transform-origin: top center;
}
.animate-open {
animation: slide-down 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards;
transform-origin: top center;
}
.fade-in {
animation: fade-in 0.3s ease forwards;
}
.fade-out {
animation: fade-out 0.25s ease forwards;
}
/* Animation keyframes */
@keyframes skeleton-pulse {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes slide-down {
0% {
opacity: 0;
transform: translateY(-8px) scale(0.98);
}
100% {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes slide-up {
0% {
opacity: 1;
transform: translateY(0) scale(1);
}
100% {
opacity: 0;
transform: translateY(-8px) scale(0.98);
height: 0px;
}
}
@keyframes fade-slide-down {
0% {
opacity: 0;
transform: translateY(-8px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
`;
}
declare global {
interface HTMLElementTagNameMap {
"hvs-dropdown-menu": HvsDropdownMenu;
}
}