367 lines
11 KiB
JavaScript
367 lines
11 KiB
JavaScript
import {
|
|
dropdown_styles_default
|
|
} from "./chunk.DY6ZNNWZ.js";
|
|
import {
|
|
getTabbableBoundary
|
|
} from "./chunk.LXDTFLWU.js";
|
|
import {
|
|
SlPopup
|
|
} from "./chunk.SJLA5ROP.js";
|
|
import {
|
|
getAnimation,
|
|
setDefaultAnimation
|
|
} from "./chunk.RCZVQXWP.js";
|
|
import {
|
|
waitForEvent
|
|
} from "./chunk.B4BZKR24.js";
|
|
import {
|
|
animateTo,
|
|
stopAnimations
|
|
} from "./chunk.S7GYYU7Z.js";
|
|
import {
|
|
LocalizeController
|
|
} from "./chunk.NH3SRVOC.js";
|
|
import {
|
|
e
|
|
} from "./chunk.UZVKBFXH.js";
|
|
import {
|
|
watch
|
|
} from "./chunk.FA5RT4K4.js";
|
|
import {
|
|
ShoelaceElement,
|
|
e as e2,
|
|
n
|
|
} from "./chunk.SEXBCYCU.js";
|
|
import {
|
|
x
|
|
} from "./chunk.CXZZ2LVK.js";
|
|
import {
|
|
__decorateClass
|
|
} from "./chunk.KIILAQWQ.js";
|
|
|
|
// src/components/dropdown/dropdown.component.ts
|
|
var SlDropdown = class extends ShoelaceElement {
|
|
constructor() {
|
|
super(...arguments);
|
|
this.localize = new LocalizeController(this);
|
|
this.open = false;
|
|
this.placement = "bottom-start";
|
|
this.disabled = false;
|
|
this.stayOpenOnSelect = false;
|
|
this.distance = 0;
|
|
this.skidding = 0;
|
|
this.hoist = false;
|
|
this.handleKeyDown = (event) => {
|
|
if (this.open && event.key === "Escape") {
|
|
event.stopPropagation();
|
|
this.hide();
|
|
this.focusOnTrigger();
|
|
}
|
|
};
|
|
this.handleDocumentKeyDown = (event) => {
|
|
var _a;
|
|
if (event.key === "Escape" && this.open && !this.closeWatcher) {
|
|
event.stopPropagation();
|
|
this.focusOnTrigger();
|
|
this.hide();
|
|
return;
|
|
}
|
|
if (event.key === "Tab") {
|
|
if (this.open && ((_a = document.activeElement) == null ? void 0 : _a.tagName.toLowerCase()) === "sl-menu-item") {
|
|
event.preventDefault();
|
|
this.hide();
|
|
this.focusOnTrigger();
|
|
return;
|
|
}
|
|
setTimeout(() => {
|
|
var _a2, _b, _c;
|
|
const activeElement = ((_a2 = this.containingElement) == null ? void 0 : _a2.getRootNode()) instanceof ShadowRoot ? (_c = (_b = document.activeElement) == null ? void 0 : _b.shadowRoot) == null ? void 0 : _c.activeElement : document.activeElement;
|
|
if (!this.containingElement || (activeElement == null ? void 0 : activeElement.closest(this.containingElement.tagName.toLowerCase())) !== this.containingElement) {
|
|
this.hide();
|
|
}
|
|
});
|
|
}
|
|
};
|
|
this.handleDocumentMouseDown = (event) => {
|
|
const path = event.composedPath();
|
|
if (this.containingElement && !path.includes(this.containingElement)) {
|
|
this.hide();
|
|
}
|
|
};
|
|
this.handlePanelSelect = (event) => {
|
|
const target = event.target;
|
|
if (!this.stayOpenOnSelect && target.tagName.toLowerCase() === "sl-menu") {
|
|
this.hide();
|
|
this.focusOnTrigger();
|
|
}
|
|
};
|
|
}
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
if (!this.containingElement) {
|
|
this.containingElement = this;
|
|
}
|
|
}
|
|
firstUpdated() {
|
|
this.panel.hidden = !this.open;
|
|
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];
|
|
if (typeof (trigger == null ? void 0 : trigger.focus) === "function") {
|
|
trigger.focus();
|
|
}
|
|
}
|
|
getMenu() {
|
|
return this.panel.assignedElements({ flatten: true }).find((el) => el.tagName.toLowerCase() === "sl-menu");
|
|
}
|
|
handleTriggerClick() {
|
|
if (this.open) {
|
|
this.hide();
|
|
} else {
|
|
this.show();
|
|
this.focusOnTrigger();
|
|
}
|
|
}
|
|
async handleTriggerKeyDown(event) {
|
|
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];
|
|
if (["ArrowDown", "ArrowUp", "Home", "End"].includes(event.key)) {
|
|
event.preventDefault();
|
|
if (!this.open) {
|
|
this.show();
|
|
await this.updateComplete;
|
|
}
|
|
if (menuItems.length > 0) {
|
|
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) {
|
|
if (event.key === " ") {
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
handleTriggerSlotChange() {
|
|
this.updateAccessibleTrigger();
|
|
}
|
|
//
|
|
// Slotted triggers can be arbitrary content, but we need to link them to the dropdown panel with `aria-haspopup` and
|
|
// `aria-expanded`. These must be applied to the "accessible trigger" (the tabbable portion of the trigger element
|
|
// that gets slotted in) so screen readers will understand them. The accessible trigger could be the slotted element,
|
|
// a child of the slotted element, or an element in the slotted element's shadow root.
|
|
//
|
|
// For example, the accessible trigger of an <sl-button> is a <button> located inside its shadow root.
|
|
//
|
|
// To determine this, we assume the first tabbable element in the trigger slot is the "accessible trigger."
|
|
//
|
|
updateAccessibleTrigger() {
|
|
const assignedElements = this.trigger.assignedElements({ flatten: true });
|
|
const accessibleTrigger = assignedElements.find((el) => getTabbableBoundary(el).start);
|
|
let target;
|
|
if (accessibleTrigger) {
|
|
switch (accessibleTrigger.tagName.toLowerCase()) {
|
|
case "sl-button":
|
|
case "sl-icon-button":
|
|
target = accessibleTrigger.button;
|
|
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 void 0;
|
|
}
|
|
this.open = true;
|
|
return waitForEvent(this, "sl-after-show");
|
|
}
|
|
/** Hides the dropdown panel */
|
|
async hide() {
|
|
if (!this.open) {
|
|
return void 0;
|
|
}
|
|
this.open = false;
|
|
return waitForEvent(this, "sl-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() {
|
|
var _a;
|
|
this.panel.addEventListener("sl-select", this.handlePanelSelect);
|
|
if ("CloseWatcher" in window) {
|
|
(_a = this.closeWatcher) == null ? void 0 : _a.destroy();
|
|
this.closeWatcher = new CloseWatcher();
|
|
this.closeWatcher.onclose = () => {
|
|
this.hide();
|
|
this.focusOnTrigger();
|
|
};
|
|
} else {
|
|
this.panel.addEventListener("keydown", this.handleKeyDown);
|
|
}
|
|
document.addEventListener("keydown", this.handleDocumentKeyDown);
|
|
document.addEventListener("mousedown", this.handleDocumentMouseDown);
|
|
}
|
|
removeOpenListeners() {
|
|
var _a;
|
|
if (this.panel) {
|
|
this.panel.removeEventListener("sl-select", this.handlePanelSelect);
|
|
this.panel.removeEventListener("keydown", this.handleKeyDown);
|
|
}
|
|
document.removeEventListener("keydown", this.handleDocumentKeyDown);
|
|
document.removeEventListener("mousedown", this.handleDocumentMouseDown);
|
|
(_a = this.closeWatcher) == null ? void 0 : _a.destroy();
|
|
}
|
|
async handleOpenChange() {
|
|
if (this.disabled) {
|
|
this.open = false;
|
|
return;
|
|
}
|
|
this.updateAccessibleTrigger();
|
|
if (this.open) {
|
|
this.emit("sl-show");
|
|
this.addOpenListeners();
|
|
await stopAnimations(this);
|
|
this.panel.hidden = false;
|
|
this.popup.active = true;
|
|
const { keyframes, options } = getAnimation(this, "dropdown.show", { dir: this.localize.dir() });
|
|
await animateTo(this.popup.popup, keyframes, options);
|
|
this.emit("sl-after-show");
|
|
} else {
|
|
this.emit("sl-hide");
|
|
this.removeOpenListeners();
|
|
await stopAnimations(this);
|
|
const { keyframes, options } = getAnimation(this, "dropdown.hide", { dir: this.localize.dir() });
|
|
await animateTo(this.popup.popup, keyframes, options);
|
|
this.panel.hidden = true;
|
|
this.popup.active = false;
|
|
this.emit("sl-after-hide");
|
|
}
|
|
}
|
|
render() {
|
|
return x`
|
|
<sl-popup
|
|
part="base"
|
|
id="dropdown"
|
|
placement=${this.placement}
|
|
distance=${this.distance}
|
|
skidding=${this.skidding}
|
|
strategy=${this.hoist ? "fixed" : "absolute"}
|
|
flip
|
|
shift
|
|
auto-size="vertical"
|
|
auto-size-padding="10"
|
|
class=${e({
|
|
dropdown: true,
|
|
"dropdown--open": this.open
|
|
})}
|
|
>
|
|
<slot
|
|
name="trigger"
|
|
slot="anchor"
|
|
part="trigger"
|
|
class="dropdown__trigger"
|
|
@click=${this.handleTriggerClick}
|
|
@keydown=${this.handleTriggerKeyDown}
|
|
@keyup=${this.handleTriggerKeyUp}
|
|
@slotchange=${this.handleTriggerSlotChange}
|
|
></slot>
|
|
|
|
<div aria-hidden=${this.open ? "false" : "true"} aria-labelledby="dropdown">
|
|
<slot part="panel" class="dropdown__panel"></slot>
|
|
</div>
|
|
</sl-popup>
|
|
`;
|
|
}
|
|
};
|
|
SlDropdown.styles = dropdown_styles_default;
|
|
SlDropdown.dependencies = { "sl-popup": SlPopup };
|
|
__decorateClass([
|
|
e2(".dropdown")
|
|
], SlDropdown.prototype, "popup", 2);
|
|
__decorateClass([
|
|
e2(".dropdown__trigger")
|
|
], SlDropdown.prototype, "trigger", 2);
|
|
__decorateClass([
|
|
e2(".dropdown__panel")
|
|
], SlDropdown.prototype, "panel", 2);
|
|
__decorateClass([
|
|
n({ type: Boolean, reflect: true })
|
|
], SlDropdown.prototype, "open", 2);
|
|
__decorateClass([
|
|
n({ reflect: true })
|
|
], SlDropdown.prototype, "placement", 2);
|
|
__decorateClass([
|
|
n({ type: Boolean, reflect: true })
|
|
], SlDropdown.prototype, "disabled", 2);
|
|
__decorateClass([
|
|
n({ attribute: "stay-open-on-select", type: Boolean, reflect: true })
|
|
], SlDropdown.prototype, "stayOpenOnSelect", 2);
|
|
__decorateClass([
|
|
n({ attribute: false })
|
|
], SlDropdown.prototype, "containingElement", 2);
|
|
__decorateClass([
|
|
n({ type: Number })
|
|
], SlDropdown.prototype, "distance", 2);
|
|
__decorateClass([
|
|
n({ type: Number })
|
|
], SlDropdown.prototype, "skidding", 2);
|
|
__decorateClass([
|
|
n({ type: Boolean })
|
|
], SlDropdown.prototype, "hoist", 2);
|
|
__decorateClass([
|
|
watch("open", { waitUntilFirstUpdate: true })
|
|
], SlDropdown.prototype, "handleOpenChange", 1);
|
|
setDefaultAnimation("dropdown.show", {
|
|
keyframes: [
|
|
{ opacity: 0, scale: 0.9 },
|
|
{ opacity: 1, scale: 1 }
|
|
],
|
|
options: { duration: 100, easing: "ease" }
|
|
});
|
|
setDefaultAnimation("dropdown.hide", {
|
|
keyframes: [
|
|
{ opacity: 1, scale: 1 },
|
|
{ opacity: 0, scale: 0.9 }
|
|
],
|
|
options: { duration: 100, easing: "ease" }
|
|
});
|
|
|
|
export {
|
|
SlDropdown
|
|
};
|