UNPKG

@synergy-design-system/components

Version:

This package provides the base of the Synergy Design System as native web components. It uses [lit](https://www.lit.dev) and parts of [shoelace](https://shoelace.style/). Synergy officially supports the latest two versions of all major browsers (as define

389 lines (385 loc) 12.2 kB
import { dropdown_custom_styles_default } from "./chunk.2KYFCKCK.js"; import { dropdown_styles_default } from "./chunk.D6IIQDWJ.js"; import { getDeepestActiveElement, getTabbableBoundary } from "./chunk.G76BKD7B.js"; import { SynPopup } from "./chunk.6CC5CH2P.js"; import { waitForEvent } from "./chunk.C2ENQBPM.js"; import { animateTo, stopAnimations } from "./chunk.G6ITZTTW.js"; import { getAnimation, setDefaultAnimation } from "./chunk.7JGKUB4A.js"; import { LocalizeController } from "./chunk.OAQRCZOO.js"; import { watch } from "./chunk.BVZQ6QSY.js"; import { component_styles_default } from "./chunk.NLYVOJGK.js"; import { SynergyElement } from "./chunk.3AZFEB6D.js"; import { __decorateClass } from "./chunk.Z4XV3SMG.js"; // src/components/dropdown/dropdown.component.ts import { classMap } from "lit/directives/class-map.js"; import { html } from "lit"; import { ifDefined } from "lit/directives/if-defined.js"; import { property, query } from "lit/decorators.js"; var SynDropdown = class extends SynergyElement { 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.sync = void 0; 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()) === "syn-menu-item") { event.preventDefault(); this.hide(); this.focusOnTrigger(); return; } const computeClosestContaining = (element, tagName) => { if (!element) return null; const closest = element.closest(tagName); if (closest) return closest; const rootNode = element.getRootNode(); if (rootNode instanceof ShadowRoot) { return computeClosestContaining(rootNode.host, tagName); } return null; }; setTimeout(() => { var _a2; const activeElement = ((_a2 = this.containingElement) == null ? void 0 : _a2.getRootNode()) instanceof ShadowRoot ? getDeepestActiveElement() : document.activeElement; if (!this.containingElement || computeClosestContaining(activeElement, 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() === "syn-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() === "syn-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 <syn-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()) { // Synergy buttons have to update the internal button so it's announced correctly by screen readers case "syn-button": case "syn-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, "syn-after-show"); } /** Hides the dropdown panel */ async hide() { if (!this.open) { return void 0; } this.open = false; return waitForEvent(this, "syn-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("syn-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("syn-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("syn-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("syn-after-show"); } else { this.emit("syn-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("syn-after-hide"); } } render() { return html` <syn-popup part="base" exportparts="popup:base__popup" 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" sync=${ifDefined(this.sync ? this.sync : void 0)} class=${classMap({ 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> </syn-popup> `; } }; SynDropdown.styles = [component_styles_default, dropdown_styles_default, dropdown_custom_styles_default]; SynDropdown.dependencies = { "syn-popup": SynPopup }; __decorateClass([ query(".dropdown") ], SynDropdown.prototype, "popup", 2); __decorateClass([ query(".dropdown__trigger") ], SynDropdown.prototype, "trigger", 2); __decorateClass([ query(".dropdown__panel") ], SynDropdown.prototype, "panel", 2); __decorateClass([ property({ type: Boolean, reflect: true }) ], SynDropdown.prototype, "open", 2); __decorateClass([ property({ reflect: true }) ], SynDropdown.prototype, "placement", 2); __decorateClass([ property({ type: Boolean, reflect: true }) ], SynDropdown.prototype, "disabled", 2); __decorateClass([ property({ attribute: "stay-open-on-select", type: Boolean, reflect: true }) ], SynDropdown.prototype, "stayOpenOnSelect", 2); __decorateClass([ property({ attribute: false }) ], SynDropdown.prototype, "containingElement", 2); __decorateClass([ property({ type: Number }) ], SynDropdown.prototype, "distance", 2); __decorateClass([ property({ type: Number }) ], SynDropdown.prototype, "skidding", 2); __decorateClass([ property({ type: Boolean }) ], SynDropdown.prototype, "hoist", 2); __decorateClass([ property({ reflect: true }) ], SynDropdown.prototype, "sync", 2); __decorateClass([ watch("open", { waitUntilFirstUpdate: true }) ], SynDropdown.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 { SynDropdown }; //# sourceMappingURL=chunk.2JOI3AFS.js.map