+ "content": "import { Controller } from \"@hotwired/stimulus\";\nimport {\n computePosition,\n flip,\n shift,\n offset,\n autoUpdate,\n} from \"@floating-ui/dom\";\n\nexport default class extends Controller {\n static targets = [\"trigger\", \"content\"];\n static values = {\n open: { type: Boolean, default: false },\n options: { type: Object, default: {} },\n trigger: { type: String, default: \"hover\" },\n };\n\n connect() {\n this.closeTimeout = null;\n this.cleanup = null;\n this.addEventListeners();\n }\n\n disconnect() {\n this.removeEventListeners();\n clearTimeout(this.closeTimeout);\n document.removeEventListener(\"keydown\", this.handleKeydown);\n if (this.cleanup) {\n this.cleanup();\n this.cleanup = null;\n }\n }\n\n addEventListeners() {\n if (this.triggerValue === \"hover\") {\n this.triggerTarget.addEventListener(\"mouseenter\", this.handleMouseEnter);\n this.triggerTarget.addEventListener(\"mouseleave\", this.handleMouseLeave);\n this.contentTarget.addEventListener(\"mouseenter\", this.handleMouseEnter);\n this.contentTarget.addEventListener(\"mouseleave\", this.handleMouseLeave);\n } else if (this.triggerValue === \"click\") {\n this.triggerTarget.addEventListener(\"click\", this.handleClick);\n document.addEventListener(\"click\", this.handleOutsideClick);\n }\n }\n\n removeEventListeners() {\n this.triggerTarget.removeEventListener(\"mouseenter\", this.handleMouseEnter);\n this.triggerTarget.removeEventListener(\"mouseleave\", this.handleMouseLeave);\n this.contentTarget.removeEventListener(\"mouseenter\", this.handleMouseEnter);\n this.contentTarget.removeEventListener(\"mouseleave\", this.handleMouseLeave);\n this.triggerTarget.removeEventListener(\"click\", this.handleClick);\n document.removeEventListener(\"click\", this.handleOutsideClick);\n }\n\n handleMouseEnter = () => {\n clearTimeout(this.closeTimeout);\n this.openValue = true;\n this.showPopover();\n };\n\n handleMouseLeave = () => {\n this.closeTimeout = setTimeout(() => {\n this.openValue = false;\n this.hidePopover();\n }, 100);\n };\n\n handleClick = (event) => {\n event.stopPropagation();\n this.openValue = !this.openValue;\n this.openValue ? this.showPopover() : this.hidePopover();\n };\n\n handleOutsideClick = (event) => {\n if (!this.element.contains(event.target) && this.openValue) {\n this.openValue = false;\n this.hidePopover();\n }\n };\n\n handleKeydown = (event) => {\n if (event.key !== \"Escape\") return;\n if (!this.openValue) return;\n\n clearTimeout(this.closeTimeout);\n this.openValue = false;\n this.hidePopover();\n };\n\n showPopover() {\n this.contentTarget.classList.remove(\"hidden\");\n this.contentTarget.dataset.state = \"open\";\n document.addEventListener(\"keydown\", this.handleKeydown);\n this.updatePosition();\n }\n\n hidePopover() {\n this.contentTarget.classList.add(\"hidden\");\n this.contentTarget.dataset.state = \"closed\";\n document.removeEventListener(\"keydown\", this.handleKeydown);\n if (this.cleanup) {\n this.cleanup();\n this.cleanup = null;\n }\n }\n\n updatePosition() {\n if (this.cleanup) {\n this.cleanup();\n }\n\n this.cleanup = autoUpdate(this.triggerTarget, this.contentTarget, () => {\n computePosition(this.triggerTarget, this.contentTarget, {\n placement: this.optionsValue.placement || \"bottom\",\n middleware: [flip(), shift(), offset(8)],\n }).then(({ x, y, placement }) => {\n Object.assign(this.contentTarget.style, {\n left: `${x}px`,\n top: `${y}px`,\n });\n // flip() can resolve to the opposite side of the requested placement,\n // so the directional slide-in classes must follow the resolved value.\n this.contentTarget.dataset.side = placement.split(\"-\")[0];\n });\n });\n }\n}\n"
0 commit comments