Adding a dropdown component
All checks were successful
Build, Test, and Publish (to Private NPM Registry) UI Components Library / publish (push) Successful in 1m15s

This commit is contained in:
Alan Bridgeman 2026-07-08 19:14:39 -05:00
parent d3792e7be9
commit e250e9ed30
8 changed files with 724 additions and 0 deletions

View file

@ -0,0 +1,23 @@
# Accessible Dropdown
Dropdowns allow "hanging" menus when a trigger element is activated.
## Usage
to use this component:
```ejs
<%- useComponent('dropdown', { menuId: 'example-dropdown', contents: `...` }) %>
```
## Parameters
| Parameter | type | Description |
| ---------------------- | ----------------- | ------------------------------------------------------------ |
| `menuId` | string | The ID of the dropdown |
| `triggerContent` | HTML | The HTML of the dropdown trigger (button) |
| `contents` | HTML | The content of the dropdown |
| `desktopOnly` | boolean | If the dropdown is for desktop only |
| `mobileOnly` | boolean | If the dropdown is for mobile only |
| `triggerStyleClasses` | string / string[] | The class(es) assigned to the dropdown trigger element |
| `menuStyleClasses` | string / string[] | The class(es) assigned to the dropdown menu element |
| `dropdownExtraStyles` | Various | Extra CSS to be included in the components Shadow DOM |
| `dropdownExtraScripts` | Various | Extra JS scripts to be included in the components Shadow DOM |

View file

@ -0,0 +1,367 @@
/* ==========================================================================
Shadow DOM Styles (Component Wrapper & Mechanics)
========================================================================== */
/* HOST (Replaces .has-dropdown) */
:host {
/* POSITIONING - Relative to contain the absolute dropdown */
position: relative;
/* LAYOUT - Flex for trigger + dropdown */
display: flex;
flex-direction: column;
align-items: stretch;
/* WIDTH - Full width on mobile for easier tapping */
width: 100%;
/*font-family: var(--font-sans);*/
}
/* Reset dropdowns for mobile to be static lists */
.dropdown-menu {
/* POSITIONING - static on mobile */
position: static;
/* WIDTH - Full width on mobile */
width: 100%;
min-width: 0; /* Remove fixed width for mobile */
/* LIST STYLE - Remove bullets */
list-style: none;
/* SHADOW - Remove shadow for mobile since it's not floating */
box-shadow: none;
/* BORDER - Remove border for mobile since it's not floating */
border: none;
border-left: 2px solid var(--surface-3); /* Visual guide for hierarchy */
/* OPACITY / VISIBILITY - Always visible on mobile */
opacity: 1;
visibility: visible;
/* ANIMATION - Disable mobile transform */
transform: none;
/* SPACING - Indent dropdown items on mobile for hierarchy */
padding-left: var(--size-4);
/* Z-INDEX - Auto for mobile since we're not overlapping content */
z-index: auto;
/*margin: 0;
box-sizing: border-box;*/
}
/* If the dropdown has the hidden attribute, hide it */
.dropdown-menu[hidden] {
display: none !important;
opacity: 0;
visibility: hidden;
}
.dropdown-menu:popover-open {
position: absolute; /* Actually works with anchor API */
position-anchor: --dropdown-btn;
top: anchor(bottom);
left: anchor(start);
margin: 0; /* Reset native popover centering */
}
/* Because mobile doesn't support right aligned dropdowns turn into essentially noop */
.dropdown-menu.align-right {
left: 0;
right: auto;
transform-origin: top left;
}
/* -+- Dropdown Trigger Buttons (These can be used for any dropdown, not just nav) -+- */
.dropdown-trigger {
anchor-name: --dropdown-btn;
}
.dropdown-trigger,
.dropdown-trigger-icon {
appearance: none;
/* BACKGROUND */
background: transparent;
/* BORDER */
border: none;
/* WIDTH - Full width on mobile for easier tapping */
min-width: 100%;
/* FONT / TYPOGRAPHY */
font-family: inherit;
font-size: inherit;
/* COLOUR */
color: var(--text-2);
/* LAYOUT - Flex for icon + text */
display: flex;
align-items: stretch;
/* GAP - Space between icon and text */
gap: var(--size-2);
/*padding: 0;
margin: 0;*/
cursor: pointer;
}
/* Hover/Focus styles for dropdown triggers */
.dropdown-trigger:hover,
.dropdown-trigger:focus,
.dropdown-trigger[aria-expanded="true"],
::slotted([slot="dropdown-trigger"]:hover),
::slotted([slot="dropdown-trigger"]:focus),
::slotted([slot="dropdown-trigger"][aria-expanded="true"]) {
background: var(--surface-elevated);
color: var(--text-1);
}
/* Chevron Rotation when open */
.dropdown-trigger[aria-expanded="true"] .fa-chevron-down,
.dropdown-trigger-icon[aria-expanded="true"] .fa-chevron-down,
::slotted([slot="dropdown-trigger"][aria-expanded="true"] .fa-chevron-down) {
transform: rotate(180deg);
transition: transform 0.2s var(--ease-1);
transform-origin: center center;
}
/* For users who prefer reduced motion, disable the chevron rotation animation */
@media (prefers-reduced-motion: reduce) {
.dropdown-trigger[aria-expanded="true"] .fa-chevron-down,
.dropdown-trigger-icon[aria-expanded="true"] .fa-chevron-down,
::slotted([slot="dropdown-trigger"][aria-expanded="true"] .fa-chevron-down) {
transition: none;
}
}
/* --- Desktop Component Overrides --- */
@media (min-width: 820px) {
:host {
/* LAYOUT - Flex for trigger + dropdown */
flex-direction: row;
align-items: center;
}
.dropdown-menu {
/* POSITIONING - Absolute on desktop */
position: absolute;
top: 100%; /* Push below parent */
left: 0;
/* WIDTH - Fit to content */
width: max-content;
min-width: 14rem;
/* BACKGROUND */
background: var(--surface-filled);
/* SHADOW - Gives depth */
box-shadow: var(--shadow-3);
/* BORDER */
border: 1px solid var(--surface-3);
/* CURVES / RADIUS - for a more elegant look */
border-radius: var(--radius-2);
/* SPACING */
padding: var(--size-2);
/* ANIMATION - Dropdown slide down */
transform: translateY(-5px);
transition: all 0.2s var(--ease-2);
/* Z-INDEX - Ensure it floats above other content */
z-index: var(--layer-4);
}
/* Right-Aligned Dropdown Modifier */
.dropdown-menu.align-right {
left: auto; /* Unsets the default 'left: 0' */
right: 0; /* Anchors to the right edge of the parent */
transform-origin: top right; /* Makes animation start from the right corner */
}
.dropdown-trigger,
.dropdown-trigger-icon,
::slotted([slot="dropdown-trigger"]) {
/* WIDTH - Fit to content on desktop */
min-width: auto;
/* LAYOUT */
align-items: center;
}
}
/* Ensure slotted Light DOM content fills the container appropriately */
::slotted(ul) {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
width: 100%;
}
::slotted([slot="dropdown-trigger"]) {
display: flex;
gap: var(--size-2);
}
/* ==========================================================================
Light DOM Styles (Contents injected into the dropdown)
========================================================================== */
/* For empty dropdowns (where we want to put a message like "No notifications"), we can use this class to style the message nicely */
.dropdown-empty {
/* FONT / TYPOGRAPHY */
font-style: italic;
text-align: center;
/* SPACING */
padding: var(--size-2);
/* COLOUR - Use a muted color for the empty state message */
color: var(--text-3);
/* WIDTH - Full width to fill the dropdown */
min-width: 100%;
/* HEIGHT - Use native content height */
min-height: auto;
}
@media (min-width: 820px) {
.dropdown-empty {
/* WIDTH - Fit to content */
width: max-content;
/* OVERFLOW - Allow text to wrap if it's too long */
overflow: wrap;
/* HEIGHT - Ensure a minimum height for better aesthetics */
min-height: 2.5rem;
}
}
/* Targeting Light DOM lists slotted into the dropdown */
ba-dropdown ul {
margin: 0;
padding: 0;
list-style: none;
}
ba-dropdown li a,
ba-dropdown li button {
/* LAYOUT */
display: block;
/* WIDTH - Full width on mobile for easier tapping */
width: 100%;
/* FONT / TYPOGRAPHY */
text-decoration: none;
/* COLOUR */
color: var(--text-2);
/* SPACING */
padding: var(--size-2);
/* CURVES / RADIUS */
border-radius: var(--radius-1);
/*box-sizing: border-box;
appearance: none;
background: transparent;
border: none;
font: inherit;
text-align: left;*/
cursor: pointer;
}
ba-dropdown li a:hover,
ba-dropdown li a:focus,
ba-dropdown li button:hover,
ba-dropdown li button:focus {
background-color: var(--surface-elevated);
color: var(--text-1);
}
@media (min-width: 820px) {
ba-dropdown li a,
ba-dropdown li button {
/* WIDTH - Fit to content on desktop */
width: auto;
display: flex;
align-items: center;
}
ba-dropdown li a i,
ba-dropdown li button i {
/* GAP - Space between icon and text */
margin-right: var(--size-2);
}
}
/* Dividers */
ba-dropdown .divider {
background-color: light-dark(var(--color-9), var(--color-1));
block-size: var(--border-size-1);
margin-block: var(--size-2);
/*border: none;*/
}
/* Focus styling */
:is(.dropdown-trigger, .dropdown-trigger-icon):focus-visible {
outline: 2px solid var(--brand, var(--indigo-6));
outline-offset: 2px;
border-radius: var(--radius-1);
z-index: 10;
}
/* Utilities */
.desktop-only {
display: none;
visibility: hidden;
}
@media (min-width: 820px) {
.desktop-only {
display: inherit;
visibility: visible;
}
}
/* Ensure chevron wrappers remain perfectly centered inside the flex button */
.dropdown-trigger .desktop-only,
.dropdown-trigger .mobile-only {
display: flex;
align-items: center;
justify-content: center;
min-width: 1.5rem; /* Ensure a consistent size for the chevron wrapper */
}
.dropdown-trigger[aria-expanded="true"] .desktop-only .fa-chevron-down,
.dropdown-trigger[aria-expanded="true"] .mobile-only .fa-chevron-down,
::slotted([slot="dropdown-trigger"][aria-expanded="true"] .desktop-only .fa-chevron-down),
::slotted([slot="dropdown-trigger"][aria-expanded="true"] .mobile-only .fa-chevron-down) {
transform: rotate(180deg);
transition: transform 0.2s var(--ease-1);
transform-origin: center center;
}
/* Force the SVG to be a block/inline-block so the browser allows it to rotate */
svg.fa-chevron-down {
display: inline-block;
}

View file

@ -0,0 +1,123 @@
<ba-dropdown>
<%# Because of complexities with web components and specifically more legacy support and the `createTemplateInJS` method, we need to pass the tabs data available in EJS to the JavaScript for the web component (easiest way was via a script tag) %>
<!-- JSON config data so that `createTemplateInJS` can recreate the tabs DOM structure accurately if needed -->
<script type="application/json" data-dropdown-config>
<%- JSON.stringify({
dropdownExtraStyles: typeof dropdownExtraStyles !== 'undefined' ? (Array.isArray(dropdownExtraStyles) ? dropdownExtraStyles : [dropdownExtraStyles]) : [],
dropdownExtraScripts: typeof dropdownExtraScripts !== 'undefined' ? (Array.isArray(dropdownExtraScripts) ? dropdownExtraScripts : [dropdownExtraScripts]) : [],
desktopOnly: typeof desktopOnly !== 'undefined' ? desktopOnly : false,
mobileOnly: typeof mobileOnly !== 'undefined' ? mobileOnly : false,
triggerStyleClasses: typeof triggerStyleClasses !== 'undefined' ? (Array.isArray(triggerStyleClasses) ? triggerStyleClasses : [triggerStyleClasses]) : [],
triggerContent: typeof triggerContent !== 'undefined' ? triggerContent : '',
menuId: typeof menuId !== 'undefined' ? menuId : 'dropdown-menu',
menuStyleClasses: typeof menuStyleClasses !== 'undefined' ? (Array.isArray(menuStyleClasses) ? menuStyleClasses : [menuStyleClasses]) : [],
contents: typeof contents !== 'undefined' ? contents : false
}) %>
</script>
<template shadowrootmode="open">
<!-- Styles -->
<!-- Component Specific Styling -->
<!-- Component Styling (Part of Component Library CSS) -->
<link rel="stylesheet" type="text/css" href="<%= componentsStyleHref %>">
<!-- END: Component Specific Styling -->
<!-- Additional Styles -->
<% if (typeof dropdownExtraStyles !== 'undefined') { %>
<% const dropdownStyles = Array.isArray(dropdownExtraStyles) ? dropdownExtraStyles : [dropdownExtraStyles]; %>
<% for (let style of dropdownStyles) { %>
<link rel="stylesheet" type="text/css" href="/css/<%= style %>.css">
<% } %>
<% } %>
<!-- END: Additional Styles -->
<!-- END: Styles -->
<!-- Scripts -->
<!-- -- Fonts, Icons, and other assets -- -->
<!-- CSS Anchor Positioning Polyfill -->
<script type="module" src="https://unpkg.com/@oddbird/css-anchor-positioning/dist/css-anchor-positioning.js"></script>
<!-- Font Awesome -->
<%# <script src="https://kit.fontawesome.com/c754d91c7e.js" crossorigin="anonymous"></script> %>
<!-- -- End: Fonts, Icons, and other assets -- -->
<% if (typeof dropdownExtraScripts !== 'undefined') { %>
<% const dropdownScripts = Array.isArray(dropdownExtraScripts) ? dropdownExtraScripts : [dropdownExtraScripts]; %>
<% for (let script of dropdownScripts) { %>
<% if(typeof script === 'object') { %>
<script type="<%= typeof script.module === 'boolean' && script.module ? 'module' : 'application/javascript' %>" src="<%= script.script %>"></script>
<% } else if(typeof script === 'string' && (script.startsWith('http') || script.startsWith('https'))) { %>
<script type="<%= script.endsWith('.mjs') ? 'module' : 'application/javascript' %>" src="<%= script %>"></script>
<% } else if(typeof script === 'string') { %>
<script type="<%= script.endsWith('.mjs') ? 'module' : 'application/javascript' %>" src="/js/<%= script.endsWith('.mjs') ? script : script + '.js' %>"></script>
<% } %>
<% } %>
<% } %>
<!-- END: Scripts -->
<div role="button" class="dropdown-trigger<%= typeof triggerStyleClasses !== 'undefined' ? ' ' + triggerStyleClasses.join(' ') : '' %>" tabindex="0" aria-expanded="false" aria-controls="<%= typeof menuId !== 'undefined' ? menuId : 'dropdown-menu' %>" aria-haspopup="true" popovertarget="<%= typeof menuId !== 'undefined' ? menuId : 'dropdown-menu' %>">
<slot name="dropdown-trigger"></slot>
<%
// Define the SVG once to keep the HTML clean
const chevronSvg = `
<svg class="fa-chevron-down" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor">
<path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/>
</svg>`;
%>
<% if((typeof desktopOnly === 'boolean' && desktopOnly) || (typeof mobileOnly === 'boolean' && mobileOnly)) { %>
<div class="<%= typeof desktopOnly === 'boolean' && desktopOnly ? 'desktop-only' : '' %><%= typeof mobileOnly === 'boolean' && mobileOnly ? 'mobile-only' : '' %>">
<%# <i class="fas fa-chevron-down" aria-hidden="true"></i> %>
<%- chevronSvg %>
</div>
<% } else { %>
<%# <i class="fas fa-chevron-down" aria-hidden="true"></i> %>
<%- chevronSvg %>
<% } %>
</div>
<div id="<%= typeof menuId !== 'undefined' ? menuId : 'dropdown-menu' %>" class="dropdown-menu<%= typeof menuStyleClasses !== 'undefined' ? ' ' + menuStyleClasses.join(' ') : '' %>" hidden popover>
<slot name="dropdown-menu"></slot>
</div>
</template>
<% if(typeof slots !== 'undefined') { %>
<%- slots %>
<% } else { %>
<div slot="dropdown-trigger">
<%- triggerContent %>
</div>
<ul slot="dropdown-menu" role="menu">
<% if(typeof contents === 'object' && Array.isArray(contents)) { %>
<% contents.forEach((item, index) => { %>
<li role="menuitem">
<% if(typeof item === 'object' && item !== null) { %>
<a href="<%= item.link %>">
<% if(typeof item.icon !== 'undefined') { %>
<i class="<%= item.icon %>" aria-hidden="true"></i>
<% } %>
<span><%= item.text %></span>
</a>
<% } else if(typeof item === 'string') { %>
<span><%= item %></span>
<% } %>
</li>
<% }) %>
<% } else if(typeof contents === 'object') { %>
<li role="menuitem">
<a href="<%= contents.link %>">
<% if(typeof contents.icon !== 'undefined') { %>
<i class="<%= contents.icon %>" aria-hidden="true"></i>
<% } %>
<span><%= contents.text %></span>
</a>
</li>
<% } else { %>
<li role="menuitem">
<span>No content provided for dropdown.</span>
</li>
<% } %>
</ul>
<% } %>
</ba-dropdown>

View file

@ -0,0 +1,211 @@
import { ComposableElement } from '../ComposableElement.mjs';
/**
* Dropdown Web Component
*
* A simple dropdown component that toggles the visibility of its content when the "trigger" is clicked or activated with the keyboard.
*/
export class Dropdown extends ComposableElement {
constructor() {
super();
// Event handler bindings for toggle (popover) and keydown events
this._handleToggle = this._toggled.bind(this);
this._handleKeyDown = this._keyDown.bind(this)
}
// ----------------------
// Private Event Handlers
// ----------------------
/** Content toggled handler (popover content toggled) */
_toggled(event) {
//event.preventDefault();
this.currentElemIndex = -1;
const triggerElem = this.shadow.querySelector(`[aria-controls="${event.currentTarget.id}"]`);
const isOpen = event.newState === 'open';
triggerElem.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
event.currentTarget.toggleAttribute('hidden');
}
/** Keydown handler for the dropdown (arrow key navigation) */
_keyDown(event) {
if(event.key !== 'ArrowDown' && event.key !== 'ArrowUp') {
return;
}
event.preventDefault();
const focusableItems = Array.from(this.querySelectorAll('li'));
if (focusableItems.length === 0) {
return;
}
if (event.key === 'ArrowDown') {
if(this.currentElemIndex === undefined || this.currentElemIndex == null || this.currentElemIndex >= focusableItems.length - 1) {
this.currentElemIndex = -1;
}
this.currentElemIndex++;
focusableItems[this.currentElemIndex].children[0].focus();
}
else if(event.key === 'ArrowUp') {
if(this.currentElemIndex === undefined || this.currentElemIndex == null || this.currentElemIndex <= 0) {
this.currentElemIndex = focusableItems.length;
}
this.currentElemIndex--;
focusableItems[this.currentElemIndex].children[0].focus();
}
}
// -------------------------------
// Web Component Lifecycle Methods
// -------------------------------
/**
* Does initial setup and adds event listeners for interactivity
*
* `connectedCallback` is a lifecycle method in web components that runs when the custom element is inserted into the document's Document Object Model (DOM).
* It can be invoked multiple times if the element is removed and then re-inserted into the DOM.
*
* Timing: It is called after the element's constructor() but before the element's children are necessarily connected or fully rendered.
* Purpose: It is the ideal place to set up tasks that should only occur when the element is actually present in the live document. Common uses include:
*/
connectedCallback() {
const internals = this.attachInternals();
this.shadow = this.shadowRoot;
if (!this.shadow) {
this.shadow = this.attachShadow({ mode: 'open' });
// Defer execution until the browser finishes parsing the children
setTimeout(() => {
// Recreate the template using the shadow DOM that is only available through JavaScript
this.createTemplateInJS(this.shadow);
}, 0);
}
setTimeout(() => {
// Native Popover handles the click, enter, space, and dismiss logic.
// You only need event listeners here if you want to trigger
// custom analytics or highly specific behavior on open/close.
const dropdown = this.shadow.querySelector('[popover]');
if (dropdown) {
// The index of the currently focused element within the dropdown menu, used mainly for keyboard navigation
this.currentElemIndex = -1;
// Add event listeners to the dropdown trigger (button)
dropdown.addEventListener('toggle', this._handleToggle);
dropdown.addEventListener('keydown', this._handleKeyDown);
}
}, 0);
}
/**
* Cleans up event listeners when the component is removed from the DOM
*
* `disconnectedCallback` is a lifecycle method in web components that runs when the custom element is removed from the document's DOM.
* It can be invoked multiple times if the element is removed and then re-inserted into the DOM.
*
* Timing: It is called after the element is removed from the DOM but before it is garbage collected.
* Purpose: It is the ideal place to clean up any resources or event listeners that were set up in `connectedCallback`.
*
* Common uses include:
* - Removing event listeners to prevent memory leaks
* - Clearing timers or intervals
* - Disconnecting from external data sources or APIs
*/
disconnectedCallback() {
const dropdown = this.shadow.querySelector('[popover]');
if (dropdown) {
dropdown.removeEventListener('toggle', this._handleToggle);
dropdown.removeEventListener('keydown', this._handleKeyDown);
}
}
/**
* Recreate the template in the shadow DOM through JavaScript instead of relying on the `shadowrootmode` attribute
*
* @param {ShadowRoot} shadow The shadow DOM to attach the template to
*/
createTemplateInJS(shadow) {
const config = this.initializeComponent('dropdown', shadow);
const cssAnchorPolyfill = document.createElement('script');
cssAnchorPolyfill.type = 'module';
cssAnchorPolyfill.src = 'https://unpkg.com/@oddbird/css-anchor-positioning/dist/css-anchor-positioning.js';
shadow.appendChild(cssAnchorPolyfill);
// Create the trigger div for the dropdown component
const dropdownTriggerDiv = document.createElement('div');
dropdownTriggerDiv.role = 'button';
dropdownTriggerDiv.tabIndex = 0;
dropdownTriggerDiv.setAttribute('aria-expanded', 'false');
dropdownTriggerDiv.setAttribute('aria-controls', config.menuId);
dropdownTriggerDiv.setAttribute('aria-haspopup', 'true');
dropdownTriggerDiv.setAttribute('popovertarget', config.menuId);
dropdownTriggerDiv.classList.add('dropdown-trigger');
config.triggerStyleClasses.forEach(cls => dropdownTriggerDiv.classList.add(cls));
// Create the slot for the dropdown trigger content
const dropdownTriggerSlotElem = document.createElement('slot');
dropdownTriggerSlotElem.name = 'dropdown-trigger';
dropdownTriggerDiv.appendChild(dropdownTriggerSlotElem);
// Define the SVG once to keep the HTML clean
const dropdownTriggerChevronSvg = `
<svg class="fa-chevron-down" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" fill="currentColor">
<path d="M233.4 406.6c12.5 12.5 32.8 12.5 45.3 0l192-192c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L256 338.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l192 192z"/>
</svg>`;
if((typeof config.desktopOnly === 'boolean' && config.desktopOnly) || (typeof config.mobileOnly === 'boolean' && config.mobileOnly)) {
const dropdownTriggerChevronWrapperElem = document.createElement('div');
if(typeof config.desktopOnly === 'boolean' && config.desktopOnly) {
dropdownTriggerChevronWrapperElem.classList.add('desktop-only');
}
else if(typeof config.mobileOnly === 'boolean' && config.mobileOnly) {
dropdownTriggerChevronWrapperElem.classList.add('mobile-only');
}
dropdownTriggerChevronWrapperElem.innerHTML = dropdownTriggerChevronSvg;
dropdownTriggerDiv.appendChild(dropdownTriggerChevronWrapperElem);
}
else {
dropdownTriggerDiv.innerHTML = dropdownTriggerDiv.innerHTML + dropdownTriggerChevronSvg;
}
// Append the trigger div to the shadow DOM of the component
shadow.appendChild(dropdownTriggerDiv);
// Create the list (`ul`) that will contain the dropdown content
const dropdownContentsDiv = document.createElement('div');
dropdownContentsDiv.id = config.menuId;
dropdownContentsDiv.classList.add('dropdown-menu')
config.menuStyleClasses.forEach(cls => dropdownContentsDiv.classList.add(cls));
dropdownContentsDiv.hidden = true;
dropdownContentsDiv.popover = true;
const contentSlotElem = document.createElement('slot');
contentSlotElem.name = 'dropdown-menu';
dropdownContentsDiv.appendChild(contentSlotElem);
shadow.appendChild(dropdownContentsDiv);
}
}
document.addEventListener('DOMContentLoaded', () => {
if (!customElements.get('ba-dropdown')) {
customElements.define('ba-dropdown', Dropdown);
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

After

Width:  |  Height:  |  Size: 141 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 B

After

Width:  |  Height:  |  Size: 141 B

Before After
Before After