{ "version": 3, "sources": ["../../Features/Shared/Dropdown/dropdown-handlers.ts", "../../Features/Shared/Dropdown/dropdown.ts"], "sourcesContent": ["const MAX_HEIGHT = 300;\r\n\r\nconst handleKeyDown = (e: KeyboardEvent) => {\r\n if (e.key === \"Escape\" || e.key === \" \") {\r\n e.stopPropagation();\r\n e.preventDefault();\r\n const panel = e.currentTarget as HTMLElement;\r\n closePanel(panel);\r\n }\r\n};\r\n\r\nconst handleFocusOut = (e: FocusEvent) => {\r\n const panel = e.currentTarget as HTMLElement;\r\n const focusedElement = e.relatedTarget as HTMLElement | null;\r\n if (!focusedElement || (panel && !panel.contains(focusedElement))) {\r\n closePanel(panel);\r\n }\r\n};\r\n\r\nconst handleButtonClick = (e: Event) => {\r\n const toggleButton = e.currentTarget as HTMLElement;\r\n const dropdownPanel = findPanel(toggleButton);\r\n if (!dropdownPanel) {\r\n return;\r\n }\r\n\r\n const isOpen = toggleButton.getAttribute(\"aria-expanded\") === \"true\";\r\n if (isOpen) {\r\n closeDropdown(toggleButton, dropdownPanel);\r\n } else {\r\n openDropdown(toggleButton, dropdownPanel);\r\n }\r\n};\r\n\r\nconst findPanel = (toggleButton: Element) => {\r\n const panelId = toggleButton.getAttribute(\"aria-controls\");\r\n if (!panelId) {\r\n console.warn(\"Dropdown toggle button is missing a panel ID\");\r\n return null;\r\n }\r\n\r\n const dropdownPanel = document.getElementById(panelId);\r\n if (!dropdownPanel) {\r\n console.warn(`Dropdown panel with ID ${panelId} does not exist`);\r\n return null;\r\n }\r\n\r\n return dropdownPanel as HTMLElement | null;\r\n};\r\n\r\nconst findToggleButton = (panel: Element) => {\r\n const button = document.querySelector(`[aria-controls=\"${panel.id}\"]`);\r\n if (!button) {\r\n console.warn(`Dropdown panel \"${panel.id}\" is missing a toggle button`);\r\n }\r\n\r\n return button as HTMLElement | null;\r\n};\r\n\r\nconst closePanel = (panel: HTMLElement) => {\r\n const button = findToggleButton(panel);\r\n if (button) {\r\n closeDropdown(button, panel);\r\n }\r\n};\r\n\r\nconst closeDropdown = (button: HTMLElement, panel: HTMLElement) => {\r\n button.setAttribute(\"aria-expanded\", \"false\");\r\n button.setAttribute(\"data-expanded\", \"false\");\r\n\r\n panel.style.height = panel.children[0].clientHeight + \"px\";\r\n\r\n // After browser has updated the fixed height (a few milliseconds to be sure), set to 0\r\n setTimeout(() => {\r\n panel.style.height = \"0\";\r\n\r\n // Clear specific height after duration\r\n setTimeout(() => panel.style.removeProperty(\"height\"), 200);\r\n }, 10);\r\n\r\n toggleAllClasses(button, false);\r\n};\r\n\r\nconst openDropdown = (button: HTMLElement, panel: HTMLElement) => {\r\n button.setAttribute(\"aria-expanded\", \"true\");\r\n button.setAttribute(\"data-expanded\", \"true\");\r\n\r\n panel.dataset.dropdownDirection = isDirectionUp(button) ? \"up\" : \"down\";\r\n\r\n toggleAllClasses(button, true);\r\n\r\n panel.style.height = \"0\";\r\n\r\n // After browser has updated the fixed height (a few milliseconds to be sure), set to 0\r\n setTimeout(() => {\r\n panel.style.height = panel.children[0].clientHeight + \"px\";\r\n\r\n // Clear specific height after duration\r\n setTimeout(() => panel.style.removeProperty(\"height\"), 200);\r\n }, 10);\r\n\r\n panel.focus(); // can't set focus until visible, will close on focusout\r\n};\r\n\r\nconst isDirectionUp = (button: HTMLElement) => {\r\n const specifiedMaxHeight = Number(button.dataset.maxHeight);\r\n const maxHeight = specifiedMaxHeight > 0 ? specifiedMaxHeight : MAX_HEIGHT;\r\n\r\n const btnRect = button?.getBoundingClientRect();\r\n if (btnRect) {\r\n const spaceAbove = btnRect.top;\r\n const spaceBelow = window.innerHeight - btnRect.bottom;\r\n return spaceAbove > spaceBelow && spaceBelow < maxHeight + 16;\r\n }\r\n\r\n return false;\r\n};\r\n\r\nconst onItemSelected = (dropdownToggleButton: HTMLElement, input: HTMLInputElement) => {\r\n // We expect input to be wrapped with label\r\n const parentLabel = input.parentElement as HTMLLabelElement;\r\n\r\n if (!parentLabel) {\r\n return;\r\n }\r\n\r\n const buttonTextElement = dropdownToggleButton.querySelector(\r\n \"[data-js-dropdown-text]\"\r\n ) as HTMLElement;\r\n\r\n if (buttonTextElement) {\r\n buttonTextElement.innerText = parentLabel.innerText;\r\n }\r\n};\r\n\r\nconst replaceClasses = (element: HTMLElement, from: string, to: string) => {\r\n element.classList.remove(...from.split(\" \").filter(str => !!str));\r\n element.classList.add(...to.split(\" \").filter(str => !!str));\r\n};\r\n\r\nconst toggleAllClasses = (dropdownToggleButton: Element, open: boolean) => {\r\n const outerWrapper = dropdownToggleButton.closest(\"[data-js-dropdown-outer]\");\r\n\r\n const toggleElements = [\r\n outerWrapper,\r\n ...(outerWrapper?.querySelectorAll(\"[data-open-classes], [data-closed-classes]\") ??\r\n []),\r\n ];\r\n\r\n toggleElements.forEach(element => {\r\n const htmlElement = element as HTMLElement;\r\n const openClasses = htmlElement.dataset.openClasses ?? \"\";\r\n const closedClasses = htmlElement.dataset.closedClasses ?? \"\";\r\n\r\n if (open) {\r\n replaceClasses(htmlElement, closedClasses, openClasses);\r\n } else {\r\n replaceClasses(htmlElement, openClasses, closedClasses);\r\n }\r\n });\r\n};\r\n\r\nexport const initializeDropdown = (dropdownToggleButton: Element) => {\r\n const panelId = dropdownToggleButton.getAttribute(\"aria-controls\");\r\n\r\n if (!panelId) {\r\n console.warn(\"Dropdown toggle button is missing an aria-controls attribute\");\r\n return;\r\n }\r\n\r\n const dropdownPanel = findPanel(dropdownToggleButton);\r\n\r\n if (!dropdownPanel) {\r\n console.warn(\r\n `Dropdown toggle button expects a panel with ID ${panelId} but it does not exist`\r\n );\r\n return;\r\n }\r\n\r\n dropdownToggleButton.addEventListener(\"click\", handleButtonClick);\r\n // prevent taking focus on mousedown (which interferes with focusout)\r\n dropdownToggleButton.addEventListener(\"mousedown\", e => e.preventDefault());\r\n\r\n dropdownPanel.tabIndex = -1;\r\n dropdownPanel.addEventListener(\"focusout\", e => handleFocusOut(e as FocusEvent));\r\n dropdownPanel.addEventListener(\"keydown\", e => handleKeyDown(e as KeyboardEvent));\r\n\r\n // If the dropdown contains form element items (i.e. radio buttons), update the placeholder text\r\n dropdownPanel.querySelectorAll(\"input[type='radio']\").forEach(input => {\r\n input.addEventListener(\"change\", () =>\r\n onItemSelected(dropdownToggleButton as HTMLElement, input as HTMLInputElement)\r\n );\r\n });\r\n};\r\n", "\uFEFF/**\r\n * A dropdown consists of a toggle button and a panel.\r\n *
\r\n *