diff --git a/packages/webawesome/docs/docs/components/dropdown.md b/packages/webawesome/docs/docs/components/dropdown.md
deleted file mode 100644
index 647cfd36d..000000000
--- a/packages/webawesome/docs/docs/components/dropdown.md
+++ /dev/null
@@ -1,182 +0,0 @@
----
-title: Dropdown
-description: 'Dropdowns expose additional content that "drops down" in a panel.'
-tags: [actions, apps]
-icon: dropdown
----
-
-Dropdowns consist of a trigger and a panel. By default, activating the trigger will expose the panel and interacting outside of the panel will close it.
-
-Dropdowns are designed to work well with [menus](/docs/components/menu) to provide a list of options the user can select from. However, dropdowns can also be used in lower-level applications (e.g. [color picker](/docs/components/color-picker)). The API gives you complete control over showing, hiding, and positioning the panel.
-
-```html {.example}
-
- Dropdown
-
- Dropdown Item 1
- Dropdown Item 2
- Dropdown Item 3
-
- Checkbox
- Disabled
-
-
- Prefix
-
-
-
- Suffix Icon
-
-
-
-
-```
-
-## Examples
-
-### Getting the Selected Item
-
-When dropdowns are used with [menus](/docs/components/menu), you can listen for the [`wa-select`](/docs/components/menu#events) event to determine which menu item was selected. The menu item element will be exposed in `event.detail.item`. You can set `value` props to make it easier to identify commands.
-
-```html {.example}
-
-
- Edit
-
- Cut
- Copy
- Paste
-
-
-
-
-
-```
-
-Alternatively, you can listen for the `click` event on individual menu items. Note that, using this approach, disabled menu items will still emit a `click` event.
-
-```html {.example}
-
-
- Edit
-
- Cut
- Copy
- Paste
-
-
-
-
-
-```
-
-### Placement
-
-The preferred placement of the dropdown can be set with the `placement` attribute. Note that the actual position may vary to ensure the panel remains in the viewport.
-
-```html {.example}
-
- Edit
-
- Cut
- Copy
- Paste
-
- Find
- Replace
-
-
-```
-
-### Distance
-
-The distance from the panel to the trigger can be customized using the `distance` attribute. This value is specified in pixels.
-
-```html {.example}
-
- Edit
-
- Cut
- Copy
- Paste
-
- Find
- Replace
-
-
-```
-
-### Skidding
-
-The offset of the panel along the trigger can be customized using the `skidding` attribute. This value is specified in pixels.
-
-```html {.example}
-
- Edit
-
- Cut
- Copy
- Paste
-
- Find
- Replace
-
-
-```
-
-### Submenus
-
-To create a submenu, nest an `` element in a [menu item](/docs/components/menu-item).
-
-```html {.example}
-
- Edit
-
-
- Undo
- Redo
-
- Cut
- Copy
- Paste
-
-
- Find
-
- Find…
- Find Next
- Find Previous
-
-
-
- Transformations
-
- Make uppercase
- Make lowercase
- Capitalize
-
-
-
-
-```
-
-:::warning
-As a UX best practice, avoid using more than one level of submenu when possible.
-:::
diff --git a/packages/webawesome/src/components/dropdown/dropdown.css b/packages/webawesome/src/components/dropdown/dropdown.css
deleted file mode 100644
index ef8472162..000000000
--- a/packages/webawesome/src/components/dropdown/dropdown.css
+++ /dev/null
@@ -1,60 +0,0 @@
-:host {
- --box-shadow: var(--wa-shadow-m);
-
- display: inline-block;
-}
-
-.dropdown::part(popup) {
- z-index: 900;
-}
-
-.dropdown[data-current-placement^='top']::part(popup) {
- transform-origin: bottom;
-}
-
-.dropdown[data-current-placement^='bottom']::part(popup) {
- transform-origin: top;
-}
-
-.dropdown[data-current-placement^='left']::part(popup) {
- transform-origin: right;
-}
-
-.dropdown[data-current-placement^='right']::part(popup) {
- transform-origin: left;
-}
-
-#trigger {
- display: block; /* for boundingClientRect */
-}
-
-.panel {
- font: inherit;
- box-shadow: var(--box-shadow);
- border-radius: var(--wa-border-radius-m);
- pointer-events: none;
-}
-
-.dropdown-open .panel {
- display: block;
- pointer-events: all;
-}
-
-/* Sizes */
-:host([size='small']) ::slotted(wa-menu) {
- font-size: var(--wa-font-size-s);
-}
-
-:host([size='medium']) ::slotted(wa-menu) {
- font-size: var(--wa-font-size-m);
-}
-
-:host([size='large']) ::slotted(wa-menu) {
- font-size: var(--wa-font-size-l);
-}
-
-/* When users slot a menu, make sure it conforms to the popup's auto-size */
-::slotted(wa-menu) {
- max-width: var(--auto-size-available-width) !important;
- max-height: var(--auto-size-available-height) !important;
-}
diff --git a/packages/webawesome/src/components/dropdown/dropdown.ts b/packages/webawesome/src/components/dropdown/dropdown.ts
deleted file mode 100644
index 9fd7582b3..000000000
--- a/packages/webawesome/src/components/dropdown/dropdown.ts
+++ /dev/null
@@ -1,430 +0,0 @@
-import { html } from 'lit';
-import { customElement, property, query } from 'lit/decorators.js';
-import { classMap } from 'lit/directives/class-map.js';
-import { ifDefined } from 'lit/directives/if-defined.js';
-import { WaAfterHideEvent } from '../../events/after-hide.js';
-import { WaAfterShowEvent } from '../../events/after-show.js';
-import { WaHideEvent } from '../../events/hide.js';
-import { WaShowEvent } from '../../events/show.js';
-import { animateWithClass } from '../../internal/animate.js';
-import { waitForEvent } from '../../internal/event.js';
-import { watch } from '../../internal/watch.js';
-import WebAwesomeElement from '../../internal/webawesome-element.js';
-import sizeStyles from '../../styles/utilities/size.css';
-import type WaButton from '../button/button.js';
-import '../popup/popup.js';
-import type WaPopup from '../popup/popup.js';
-import styles from './dropdown.css';
-
-/**
- * @summary Dropdowns expose additional content that "drops down" in a panel.
- * @documentation https://backers.webawesome.com/docs/components/dropdown
- * @status stable
- * @since 2.0
- *
- * @dependency wa-popup
- *
- * @slot - The dropdown's main content.
- * @slot trigger - The dropdown's trigger, usually a `` element.
- *
- * @event wa-show - Emitted when the dropdown opens.
- * @event wa-after-show - Emitted after the dropdown opens and all animations are complete.
- * @event wa-hide - Emitted when the dropdown closes.
- * @event wa-after-hide - Emitted after the dropdown closes and all animations are complete.
- *
- * @cssproperty --box-shadow - The shadow effects around the dropdown's edges.
- *
- * @csspart base - The component's base wrapper, a `` element.
- * @csspart base__popup - The popup's exported `popup` part. Use this to target the tooltip's popup container.
- * @csspart trigger - The container that wraps the trigger.
- * @csspart panel - The panel that gets shown when the dropdown is open.
- */
-@customElement('wa-dropdown')
-export default class WaDropdown extends WebAwesomeElement {
- static css = [sizeStyles, styles];
-
- @query('.dropdown') popup: WaPopup;
- @query('#trigger') trigger: HTMLSlotElement;
- @query('.panel') panel: HTMLSlotElement;
-
- /**
- * Indicates whether or not the dropdown is open. You can toggle this attribute to show and hide the dropdown, or you
- * can use the `show()` and `hide()` methods and this attribute will reflect the dropdown's open state.
- */
- @property({ type: Boolean, reflect: true }) open = false;
-
- /**
- * The preferred placement of the dropdown panel. Note that the actual placement may vary as needed to keep the panel
- * inside of the viewport.
- */
- @property({ reflect: true }) placement:
- | 'top'
- | 'top-start'
- | 'top-end'
- | 'bottom'
- | 'bottom-start'
- | 'bottom-end'
- | 'right'
- | 'right-start'
- | 'right-end'
- | 'left'
- | 'left-start'
- | 'left-end' = 'bottom-start';
-
- /** Disables the dropdown so the panel will not open. */
- @property({ type: Boolean, reflect: true }) disabled = false;
-
- /**
- * By default, the dropdown is closed when an item is selected. This attribute will keep it open instead. Useful for
- * dropdowns that allow for multiple interactions.
- */
- @property({ attribute: 'stay-open-on-select', type: Boolean, reflect: true }) stayOpenOnSelect = false;
-
- /**
- * The dropdown will close when the user interacts outside of this element (e.g. clicking). Useful for composing other
- * components that use a dropdown internally.
- */
- @property({ attribute: false }) containingElement?: HTMLElement;
-
- /** The distance in pixels from which to offset the panel away from its trigger. */
- @property({ type: Number }) distance = 0;
-
- /** The distance in pixels from which to offset the panel along its trigger. */
- @property({ type: Number }) skidding = 0;
-
- /**
- * Syncs the popup width or height to that of the trigger element.
- */
- @property({ reflect: true }) sync: 'width' | 'height' | 'both' | undefined = undefined;
-
- connectedCallback() {
- super.connectedCallback();
-
- if (!this.containingElement) {
- this.containingElement = this;
- }
- }
-
- firstUpdated() {
- this.panel.hidden = !this.open;
-
- // If the dropdown is visible on init, update its position
- if (this.open) {
- this.addOpenListeners();
- this.popup.active = true;
- }
- }
-
- disconnectedCallback() {
- super.disconnectedCallback();
- this.removeOpenListeners();
- this.hide();
- }
-
- focusOnTrigger() {
- const trigger = this.trigger.assignedElements({ flatten: true })[0] as HTMLElement | undefined;
- if (typeof trigger?.focus === 'function') {
- trigger.focus();
- }
- }
-
- getMenu() {
- return this.panel.assignedElements({ flatten: true }).find(el => el.tagName.toLowerCase() === 'wa-menu') as
- | any
- | undefined;
- }
-
- private handleKeyDown = (event: KeyboardEvent) => {
- // Close when escape is pressed inside an open dropdown. We need to listen on the panel itself and stop propagation
- // in case any ancestors are also listening for this key.
- if (this.open && event.key === 'Escape') {
- event.stopPropagation();
- this.hide();
- this.focusOnTrigger();
- }
- };
-
- private handleDocumentKeyDown = (event: KeyboardEvent) => {
- // Close when escape or tab is pressed
- if (event.key === 'Escape' && this.open) {
- event.stopPropagation();
- this.focusOnTrigger();
- this.hide();
- return;
- }
-
- // Handle tabbing
- if (event.key === 'Tab') {
- // Tabbing within an open menu should close the dropdown and refocus the trigger
- if (this.open && document.activeElement?.tagName.toLowerCase() === 'wa-menu-item') {
- event.preventDefault();
- this.hide();
- this.focusOnTrigger();
- return;
- }
-
- // Tabbing outside of the containing element closes the panel
- //
- // If the dropdown is used within a shadow DOM, we need to obtain the activeElement within that shadowRoot,
- // otherwise `document.activeElement` will only return the name of the parent shadow DOM element.
- setTimeout(() => {
- const activeElement =
- this.containingElement?.getRootNode() instanceof ShadowRoot
- ? document.activeElement?.shadowRoot?.activeElement
- : document.activeElement;
-
- if (
- !this.containingElement ||
- activeElement?.closest(this.containingElement.tagName.toLowerCase()) !== this.containingElement
- ) {
- this.hide();
- }
- });
- }
- };
-
- private handleDocumentMouseDown = (event: MouseEvent) => {
- // Close when clicking outside of the containing element
- const path = event.composedPath();
- if (this.containingElement && !path.includes(this.containingElement)) {
- this.hide();
- }
- };
-
- private handlePanelSelect = (event: CustomEvent) => {
- const target = event.target as HTMLElement;
-
- // Hide the dropdown when a menu item is selected
- if (!this.stayOpenOnSelect && target.tagName.toLowerCase() === 'wa-menu') {
- this.hide();
- this.focusOnTrigger();
- }
- };
-
- handleTriggerClick() {
- if (this.open) {
- this.hide();
- } else {
- this.show();
- this.focusOnTrigger();
- }
- }
-
- async handleTriggerKeyDown(event: KeyboardEvent) {
- // When spacebar/enter is pressed, show the panel but don't focus on the menu. This let's the user press the same
- // key again to hide the menu in case they don't want to make a selection.
- if ([' ', 'Enter'].includes(event.key)) {
- event.preventDefault();
- this.handleTriggerClick();
- return;
- }
-
- const menu = this.getMenu();
-
- if (menu) {
- const menuItems = menu.getAllItems();
- const firstMenuItem = menuItems[0];
- const lastMenuItem = menuItems[menuItems.length - 1];
-
- // When up/down is pressed, we make the assumption that the user is familiar with the menu and plans to make a
- // selection. Rather than toggle the panel, we focus on the menu (if one exists) and activate the first item for
- // faster navigation.
- if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
- event.preventDefault();
-
- // Show the menu if it's not already open
- if (!this.open) {
- this.show();
-
- // Wait for the dropdown to open before focusing, but not the animation
- await this.updateComplete;
- }
-
- if (menuItems.length > 0) {
- // Focus on the first/last menu item after showing
- this.updateComplete.then(() => {
- if (event.key === 'ArrowDown' || event.key === 'Home') {
- menu.setCurrentItem(firstMenuItem);
- firstMenuItem.focus();
- }
-
- if (event.key === 'ArrowUp' || event.key === 'End') {
- menu.setCurrentItem(lastMenuItem);
- lastMenuItem.focus();
- }
- });
- }
- }
- }
- }
-
- handleTriggerKeyUp(event: KeyboardEvent) {
- // Prevent space from triggering a click event in Firefox
- if (event.key === ' ') {
- event.preventDefault();
- }
- }
-
- handleTriggerSlotChange() {
- this.updateAccessibleTrigger();
- }
-
- updateAccessibleTrigger() {
- const assignedElements = this.trigger.assignedElements({ flatten: true }) as HTMLElement[];
- const accessibleTrigger = assignedElements[0];
- let target: HTMLElement;
-
- if (accessibleTrigger) {
- const tagName = accessibleTrigger.tagName.toLowerCase();
- switch (tagName) {
- // Web Awesome buttons have to update the internal button so it's announced correctly by screen readers
- case 'wa-button':
- target = (accessibleTrigger as WaButton).button;
-
- // Either the tag hasn't registered, or it hasn't rendered.
- // So, wait for the tag to register, and then try again.
- if (target === undefined || target === null) {
- customElements.whenDefined(tagName).then(async () => {
- await (accessibleTrigger as WaButton).updateComplete;
- this.updateAccessibleTrigger();
- });
-
- return;
- }
- break;
-
- default:
- target = accessibleTrigger;
- }
-
- target.setAttribute('aria-haspopup', 'true');
- target.setAttribute('aria-expanded', this.open ? 'true' : 'false');
- }
- }
-
- /** Shows the dropdown panel. */
- async show() {
- if (this.open) {
- return undefined;
- }
-
- this.open = true;
- return waitForEvent(this, 'wa-after-show');
- }
-
- /** Hides the dropdown panel */
- async hide() {
- if (!this.open) {
- return undefined;
- }
-
- this.open = false;
- return waitForEvent(this, 'wa-after-hide');
- }
-
- /**
- * Instructs the dropdown menu to reposition. Useful when the position or size of the trigger changes when the menu
- * is activated.
- */
- reposition() {
- this.popup.reposition();
- }
-
- addOpenListeners() {
- this.panel.addEventListener('wa-select', this.handlePanelSelect);
- this.panel.addEventListener('keydown', this.handleKeyDown);
- document.addEventListener('keydown', this.handleDocumentKeyDown);
- document.addEventListener('mousedown', this.handleDocumentMouseDown);
- }
-
- removeOpenListeners() {
- if (this.panel) {
- this.panel.removeEventListener('wa-select', this.handlePanelSelect);
- this.panel.removeEventListener('keydown', this.handleKeyDown);
- }
- document.removeEventListener('keydown', this.handleDocumentKeyDown);
- document.removeEventListener('mousedown', this.handleDocumentMouseDown);
- }
-
- @watch('open', { waitUntilFirstUpdate: true })
- async handleOpenChange() {
- if (this.disabled) {
- this.open = false;
- return;
- }
-
- this.updateAccessibleTrigger();
-
- if (this.open) {
- // Show
- const waShowEvent = new WaShowEvent();
- this.dispatchEvent(waShowEvent);
- if (waShowEvent.defaultPrevented) {
- this.open = false;
- return;
- }
-
- this.addOpenListeners();
- this.panel.hidden = false;
- this.popup.active = true;
- await animateWithClass(this.popup.popup, 'show-with-scale');
- this.dispatchEvent(new WaAfterShowEvent());
- } else {
- // Hide
- const waHideEvent = new WaHideEvent();
- this.dispatchEvent(waHideEvent);
- if (waHideEvent.defaultPrevented) {
- this.open = true;
- return;
- }
-
- this.removeOpenListeners();
- await animateWithClass(this.popup.popup, 'hide-with-scale');
- this.panel.hidden = true;
- this.popup.active = false;
- this.dispatchEvent(new WaAfterHideEvent());
- }
- }
-
- render() {
- return html`
-
-
-
-
-
-
- `;
- }
-}
-
-declare global {
- interface HTMLElementTagNameMap {
- 'wa-dropdown': WaDropdown;
- }
-}