+ "content": "import { Controller } from \"@hotwired/stimulus\";\nimport { animate } from \"motion\";\n\n// Connects to data-controller=\"ruby-ui--accordion\"\nexport default class extends Controller {\n static targets = [\"icon\", \"content\"];\n static values = {\n open: {\n type: Boolean,\n default: false,\n },\n animationDuration: {\n type: Number,\n default: 0.15, // Default animation duration (in seconds)\n },\n animationEasing: {\n type: String,\n default: \"ease-in-out\", // Default animation easing\n },\n rotateIcon: {\n type: Number,\n default: 180, // Default icon rotation (in degrees)\n },\n };\n\n connect() {\n // Set the initial state of the accordion\n let originalAnimationDuration = this.animationDurationValue;\n this.animationDurationValue = 0;\n this.openValue ? this.open() : this.close();\n this.animationDurationValue = originalAnimationDuration;\n }\n\n // Toggle the 'open' value\n toggle() {\n this.openValue = !this.openValue;\n }\n\n // Handle changes in the 'open' value\n openValueChanged(isOpen, wasOpen) {\n if (isOpen) {\n this.open();\n } else {\n this.close();\n }\n }\n\n // Open the accordion content\n open() {\n if (this.hasContentTarget) {\n this.revealContent();\n this.hasIconTarget && this.rotateIcon();\n this.openValue = true;\n }\n }\n\n // Close the accordion content\n close() {\n if (this.hasContentTarget) {\n this.hideContent();\n this.hasIconTarget && this.rotateIcon();\n this.openValue = false;\n }\n }\n\n // Reveal the accordion content with animation\n revealContent() {\n const content = this.contentTarget;\n\n // Remove hidden so the element participates in layout before measuring\n content.removeAttribute(\"hidden\");\n content.dataset.state = \"open\";\n\n const contentHeight = content.scrollHeight;\n animate(\n content,\n { height: `${contentHeight}px` },\n {\n duration: this.animationDurationValue,\n easing: this.animationEasingValue,\n },\n );\n }\n\n // Hide the accordion content with animation\n hideContent() {\n const content = this.contentTarget;\n content.dataset.state = \"closed\";\n\n animate(\n content,\n { height: 0 },\n {\n duration: this.animationDurationValue,\n easing: this.animationEasingValue,\n },\n ).finished.then(() => {\n // After animation completes, truly hide the element so it is removed\n // from layout and form focus — prevents trapped validation errors\n if (content.dataset.state === \"closed\") {\n content.setAttribute(\"hidden\", \"\");\n }\n });\n }\n\n // Rotate the accordion icon 180deg using animate function\n rotateIcon() {\n animate(this.iconTarget, {\n rotate: `${this.openValue ? this.rotateIconValue : 0}deg`,\n });\n }\n}\n"
0 commit comments