{"version":3,"file":"drawer-Zu6eBjxc.js","sources":["../../src/js/utils/focustrap.ts","../../src/js/components/drawer.ts"],"sourcesContent":["// Helper function to get all focusable elements within the modal\nfunction getFocusableElements(modal: HTMLElement): HTMLElement[] {\n return Array.from(\n modal.querySelectorAll(\n 'a, button, input, textarea, select, [tabindex]:not([tabindex=\"-1\"])',\n ),\n );\n}\n\nlet keydownListener: (event: KeyboardEvent) => void; // Declare outside to maintain reference\n\nexport function applyFocusTrap(modal: HTMLElement) {\n const focusableElements = getFocusableElements(modal);\n const lastElement = focusableElements[focusableElements.length - 1];\n const firstElement = focusableElements[0];\n let focusIndex: number = 0;\n let firstFocusableElement = focusableElements[focusIndex];\n\n // Get first focusable element in the container\n while (\n firstFocusableElement &&\n firstFocusableElement.hasAttribute('disabled')\n ) {\n focusIndex++;\n firstFocusableElement = focusableElements[focusIndex];\n }\n\n // Move focus to the first focusable element when modal opens\n if (firstFocusableElement) {\n firstFocusableElement.focus();\n }\n\n keydownListener = (event: KeyboardEvent) => {\n // Store the listener\n if (event.key === 'Tab') {\n if (event.shiftKey) {\n // Shift + Tab => focus goes backwards\n if (document.activeElement === firstElement) {\n event.preventDefault();\n lastElement.focus(); // Focus on the last element\n }\n } else {\n // Tab => focus goes forwards\n if (document.activeElement === lastElement) {\n event.preventDefault();\n if (firstElement.hasAttribute('disabled')) {\n firstFocusableElement.focus();\n } else {\n firstElement.focus();\n }\n }\n }\n }\n };\n\n modal.addEventListener('keydown', keydownListener); // Add the event listener\n}\n\nexport function removeFocusTrap(modal: HTMLElement) {\n modal.removeEventListener('keydown', keydownListener); // Remove the stored listener\n}\n","import { applyFocusTrap, removeFocusTrap } from '../utils/focustrap';\n\nexport function initAllDrawers() {\n const allDrawers = document.querySelectorAll(\n '[data-drawer-wrapper]',\n );\n\n allDrawers.forEach((drawerWrapper) => {\n initDrawer(drawerWrapper);\n });\n}\n\nexport function initDrawer(drawerWrapper: HTMLElement) {\n const drawer = drawerWrapper.querySelector('[data-drawer]');\n\n const backgroundBlur = drawerWrapper.querySelector(\n '[data-drawer-background]',\n );\n const addToCartButton = drawer?.querySelector(\n '[data-button-add-to-cart]',\n );\n const closeButton = drawer?.querySelector('[data-drawer-close]');\n\n attachTriggers(drawerWrapper);\n\n // Close drawer conditions\n backgroundBlur?.addEventListener('click', () => {\n closeDrawer(drawerWrapper);\n });\n\n addToCartButton?.addEventListener('click', () => {\n closeDrawer(drawerWrapper);\n });\n\n closeButton?.addEventListener('click', () => {\n closeDrawer(drawerWrapper);\n });\n\n // Reapply focus trap if contents of drawer are changed via HTMX\n drawerWrapper.addEventListener('htmx:afterSwap', (event) => {\n if (drawer) {\n removeFocusTrap(drawer);\n applyFocusTrap(drawer);\n }\n });\n\n drawerWrapper.addEventListener('htmx:oobAfterSwap', (event) => {\n if (drawer) {\n removeFocusTrap(drawer);\n applyFocusTrap(drawer);\n }\n });\n\n // Enable drag\n if (!drawer) return;\n\n const allowDrag = drawer.getAttribute('data-drawer-enable-drag') === 'True';\n if (allowDrag) {\n enableDrag(drawerWrapper);\n }\n}\n\nexport function attachTriggers(drawerWrapper: HTMLElement) {\n const drawerName = drawerWrapper.dataset.drawerWrapper;\n const allTriggers = document.querySelectorAll(\n `[data-drawer-trigger=\"${drawerName}\"]`,\n );\n\n const matchingTriggerList = Array.from(allTriggers);\n\n if (matchingTriggerList.length > 0) {\n matchingTriggerList.forEach((trigger) => {\n const timeout = Number(trigger.getAttribute('data-drawer-timeout')) || 0;\n trigger.addEventListener('click', () => {\n setTimeout(() => {\n openDrawer(drawerWrapper);\n }, timeout);\n });\n });\n } else {\n setTimeout(() => {\n openDrawer(drawerWrapper);\n }, 50);\n }\n}\n\nfunction enableDrag(drawerWrapper: HTMLElement) {\n const drawer = drawerWrapper.querySelector('[data-drawer]');\n const backgroundBlur = drawerWrapper.querySelector(\n '[data-drawer-background]',\n );\n\n if (!drawer || !backgroundBlur) return;\n\n let isDragging = false;\n let startPos = 0; // The cursor's initial X or Y position.\n let deltaPos = 0; // The difference between start and current X or Y positions.\n let percentageDiff = 0; // Percentage of the drawer height that has been dragged.\n const maxHeight = drawer.offsetHeight;\n const maxWidth = drawer.offsetWidth;\n const dragDirection = drawer.dataset.drawer; // The direction the drawer has come from\n\n const stopDrag = () => {\n isDragging = false;\n\n // Snap shut or open fully depending on how far dragged.\n if (percentageDiff >= 0 && percentageDiff < 25) {\n drawer.classList.remove('duration-0');\n drawer.style.transform = `translate3d(0, 0, 0)`; // Open drawer.\n backgroundBlur.style.opacity = '100%';\n } else {\n drawer.classList.remove('duration-0');\n closeDrawer(drawerWrapper);\n }\n\n // Remove global event listeners.\n document.removeEventListener(\n supportsTouchEvents() ? 'touchmove' : 'pointermove',\n onDrag,\n );\n document.removeEventListener(\n supportsTouchEvents() ? 'touchend' : 'pointerup',\n stopDrag,\n );\n };\n\n const onDrag = (event: PointerEvent | TouchEvent) => {\n if (!isDragging) return;\n\n let newPos;\n\n // Determine the current position based on touch or pointer event.\n if (supportsTouchEvents() && event instanceof TouchEvent) {\n newPos =\n dragDirection === 'top' || dragDirection === 'bottom'\n ? event.touches[0].clientY\n : event.touches[0].clientX;\n } else {\n newPos =\n dragDirection === 'top' || dragDirection === 'bottom'\n ? (event as PointerEvent).clientY\n : (event as PointerEvent).clientX;\n }\n\n switch (dragDirection) {\n case 'top':\n deltaPos = startPos - newPos > 0 ? startPos - newPos : 0;\n drawer.style.transform = `translate3d(0, ${-deltaPos}px, 0)`;\n break;\n\n case 'right':\n deltaPos = newPos - startPos > 0 ? newPos - startPos : 0;\n drawer.style.transform = `translate3d(${deltaPos}px, 0, 0)`;\n break;\n\n case 'left':\n deltaPos = startPos - newPos > 0 ? startPos - newPos : 0;\n drawer.style.transform = `translate3d(${-deltaPos}px, 0, 0)`;\n break;\n\n default:\n deltaPos = newPos - startPos > 0 ? newPos - startPos : 0;\n drawer.style.transform = `translate3d(0, ${deltaPos}px, 0)`;\n break;\n }\n\n // Calculate the percentage of the drawer that has been dragged.\n const dimension =\n dragDirection === 'left' || dragDirection === 'right'\n ? maxWidth\n : maxHeight;\n percentageDiff = Math.round((deltaPos / dimension) * 100);\n backgroundBlur.style.opacity = `${100 - percentageDiff}%`;\n };\n\n const startDrag = (event: PointerEvent | TouchEvent) => {\n // Prevent default behavior to avoid issues on mobile browsers.\n event.preventDefault();\n\n const isMobileView = window.innerWidth < 768;\n\n isDragging = true;\n drawer.classList.remove('duration-300');\n drawer.classList.add('duration-0');\n\n // Get the initial mouse X or Y position.\n switch (dragDirection) {\n case 'top':\n startPos =\n supportsTouchEvents() && event instanceof TouchEvent\n ? event.touches[0].clientY\n : (event as PointerEvent).clientY;\n break;\n\n case 'right':\n startPos =\n supportsTouchEvents() && event instanceof TouchEvent\n ? event.touches[0].clientX\n : (event as PointerEvent).clientX;\n break;\n\n case 'left':\n startPos =\n supportsTouchEvents() && event instanceof TouchEvent\n ? event.touches[0].clientX\n : (event as PointerEvent).clientX;\n break;\n\n default:\n startPos =\n supportsTouchEvents() && event instanceof TouchEvent\n ? event.touches[0].clientY\n : (event as PointerEvent).clientY;\n break;\n }\n\n // Attach global move and end listeners based on support for touch events.\n if (isMobileView) {\n document.addEventListener(\n supportsTouchEvents() ? 'touchmove' : 'pointermove',\n onDrag,\n );\n document.addEventListener(\n supportsTouchEvents() ? 'touchend' : 'pointerup',\n stopDrag,\n );\n } else {\n document.removeEventListener(\n supportsTouchEvents() ? 'touchmove' : 'pointermove',\n onDrag,\n );\n document.removeEventListener(\n supportsTouchEvents() ? 'touchend' : 'pointerup',\n stopDrag,\n );\n }\n };\n\n disableDragForClickableElements(drawer);\n reDisableDragAfterHTMXSwap(drawer);\n\n drawer.addEventListener(\n supportsTouchEvents() ? 'touchstart' : 'pointerdown',\n startDrag,\n );\n}\n\n// Helper function to check for touch event support.\nfunction supportsTouchEvents(): boolean {\n return 'ontouchstart' in window || navigator.maxTouchPoints > 0;\n}\n\nexport function closeDrawer(drawerWrapper: HTMLElement) {\n const drawer = drawerWrapper.querySelector('[data-drawer]');\n const backgroundBlur = drawerWrapper.querySelector(\n '[data-drawer-background]',\n );\n\n drawerWrapper.dataset.state = 'closed';\n\n if (!drawer || !backgroundBlur) {\n return;\n }\n\n drawer.style.transform = ''; // Remove transform applied by dragging.\n drawer.dataset.state = 'closed';\n drawer.classList.remove('duration-0');\n drawer.classList.add('duration-300');\n\n backgroundBlur.classList.remove('duration-0');\n backgroundBlur.dataset.state = 'closed';\n\n document.body.classList.remove('overflow-hidden'); // Enable scrolling\n document.body.style.userSelect = ''; // Re-enable text selection.\n\n removeFocusTrap(drawer);\n}\n\nexport function openDrawer(drawerWrapper: HTMLElement) {\n const drawer = drawerWrapper.querySelector('[data-drawer]');\n const backgroundBlur = drawerWrapper.querySelector(\n '[data-drawer-background]',\n );\n\n if (!drawer || !backgroundBlur) {\n return;\n }\n\n drawerWrapper.dataset.state = 'open';\n drawer.dataset.state = 'open';\n drawer.classList.remove('duration-0');\n backgroundBlur.dataset.state = 'open';\n document.body.classList.add('overflow-hidden'); // Disable scrolling\n document.body.style.userSelect = 'none'; // Disable text selection.\n\n setTimeout(() => {\n applyFocusTrap(drawer);\n }, 50);\n}\n\nfunction disableDragForClickableElements(drawer: HTMLElement) {\n // Function to prevent dragging on button or link click\n const preventDragOnClick = (event: MouseEvent) => {\n event.stopPropagation();\n };\n\n // Attach the prevent drag listener to all buttons and links inside the drawer\n const buttonsAndLinks = drawer.querySelectorAll('button, a, label, input');\n buttonsAndLinks.forEach((element) => {\n element.addEventListener(\n 'pointerdown',\n preventDragOnClick as EventListener,\n );\n element.addEventListener('touchstart', preventDragOnClick as EventListener);\n });\n}\n\nfunction reDisableDragAfterHTMXSwap(drawer: HTMLElement) {\n drawer.addEventListener('htmx:afterSwap', () => {\n disableDragForClickableElements(drawer);\n });\n}\n"],"names":["getFocusableElements","modal","keydownListener","applyFocusTrap","focusableElements","lastElement","firstElement","focusIndex","firstFocusableElement","event","removeFocusTrap","initAllDrawers","drawerWrapper","initDrawer","drawer","backgroundBlur","addToCartButton","closeButton","attachTriggers","closeDrawer","enableDrag","drawerName","allTriggers","matchingTriggerList","trigger","timeout","openDrawer","isDragging","startPos","deltaPos","percentageDiff","maxHeight","maxWidth","dragDirection","stopDrag","supportsTouchEvents","onDrag","newPos","startDrag","isMobileView","disableDragForClickableElements","reDisableDragAfterHTMXSwap","preventDragOnClick","element"],"mappings":"AACA,SAASA,EAAqBC,EAAmC,CAC/D,OAAO,MAAM,KACXA,EAAM,iBACJ,qEAAA,CAEJ,CACF,CAEA,IAAIC,EAEG,SAASC,EAAeF,EAAoB,CAC3C,MAAAG,EAAoBJ,EAAqBC,CAAK,EAC9CI,EAAcD,EAAkBA,EAAkB,OAAS,CAAC,EAC5DE,EAAeF,EAAkB,CAAC,EACxC,IAAIG,EAAqB,EACrBC,EAAwBJ,EAAkBG,CAAU,EAGxD,KACEC,GACAA,EAAsB,aAAa,UAAU,GAE7CD,IACAC,EAAwBJ,EAAkBG,CAAU,EAIlDC,GACFA,EAAsB,MAAM,EAG9BN,EAAmBO,GAAyB,CAEtCA,EAAM,MAAQ,QACZA,EAAM,SAEJ,SAAS,gBAAkBH,IAC7BG,EAAM,eAAe,EACrBJ,EAAY,MAAM,GAIhB,SAAS,gBAAkBA,IAC7BI,EAAM,eAAe,EACjBH,EAAa,aAAa,UAAU,EACtCE,EAAsB,MAAM,EAE5BF,EAAa,MAAM,GAK7B,EAEML,EAAA,iBAAiB,UAAWC,CAAe,CACnD,CAEO,SAASQ,EAAgBT,EAAoB,CAC5CA,EAAA,oBAAoB,UAAWC,CAAe,CACtD,CC1DO,SAASS,GAAiB,CACZ,SAAS,iBAC1B,uBACF,EAEW,QAASC,GAAkB,CACpCC,EAAWD,CAAa,CAAA,CACzB,CACH,CAEO,SAASC,EAAWD,EAA4B,CAC/C,MAAAE,EAASF,EAAc,cAA2B,eAAe,EAEjEG,EAAiBH,EAAc,cACnC,0BACF,EACMI,EAAkBF,GAAA,YAAAA,EAAQ,cAC9B,6BAEIG,EAAcH,GAAA,YAAAA,EAAQ,cAA2B,uBAiCvD,GA/BAI,EAAeN,CAAa,EAGZG,GAAA,MAAAA,EAAA,iBAAiB,QAAS,IAAM,CAC9CI,EAAYP,CAAa,CAAA,GAGVI,GAAA,MAAAA,EAAA,iBAAiB,QAAS,IAAM,CAC/CG,EAAYP,CAAa,CAAA,GAGdK,GAAA,MAAAA,EAAA,iBAAiB,QAAS,IAAM,CAC3CE,EAAYP,CAAa,CAAA,GAIbA,EAAA,iBAAiB,iBAAmBH,GAAU,CACtDK,IACFJ,EAAgBI,CAAM,EACtBX,EAAeW,CAAM,EACvB,CACD,EAEaF,EAAA,iBAAiB,oBAAsBH,GAAU,CACzDK,IACFJ,EAAgBI,CAAM,EACtBX,EAAeW,CAAM,EACvB,CACD,EAGG,CAACA,EAAQ,OAEKA,EAAO,aAAa,yBAAyB,IAAM,QAEnEM,EAAWR,CAAa,CAE5B,CAEO,SAASM,EAAeN,EAA4B,CACnD,MAAAS,EAAaT,EAAc,QAAQ,cACnCU,EAAc,SAAS,iBAC3B,yBAAyBD,CAAU,IACrC,EAEME,EAAsB,MAAM,KAAKD,CAAW,EAE9CC,EAAoB,OAAS,EACXA,EAAA,QAASC,GAAY,CACvC,MAAMC,EAAU,OAAOD,EAAQ,aAAa,qBAAqB,CAAC,GAAK,EAC/DA,EAAA,iBAAiB,QAAS,IAAM,CACtC,WAAW,IAAM,CACfE,EAAWd,CAAa,GACvBa,CAAO,CAAA,CACX,CAAA,CACF,EAED,WAAW,IAAM,CACfC,EAAWd,CAAa,GACvB,EAAE,CAET,CAEA,SAASQ,EAAWR,EAA4B,CACxC,MAAAE,EAASF,EAAc,cAA2B,eAAe,EACjEG,EAAiBH,EAAc,cACnC,0BACF,EAEI,GAAA,CAACE,GAAU,CAACC,EAAgB,OAEhC,IAAIY,EAAa,GACbC,EAAW,EACXC,EAAW,EACXC,EAAiB,EACrB,MAAMC,EAAYjB,EAAO,aACnBkB,EAAWlB,EAAO,YAClBmB,EAAgBnB,EAAO,QAAQ,OAE/BoB,EAAW,IAAM,CACRP,EAAA,GAGTG,GAAkB,GAAKA,EAAiB,IACnChB,EAAA,UAAU,OAAO,YAAY,EACpCA,EAAO,MAAM,UAAY,uBACzBC,EAAe,MAAM,QAAU,SAExBD,EAAA,UAAU,OAAO,YAAY,EACpCK,EAAYP,CAAa,GAIlB,SAAA,oBACPuB,EAAA,EAAwB,YAAc,cACtCC,CACF,EACS,SAAA,oBACPD,EAAA,EAAwB,WAAa,YACrCD,CACF,CACF,EAEME,EAAU3B,GAAqC,CACnD,GAAI,CAACkB,EAAY,OAEb,IAAAU,EAeJ,OAZIF,EAAA,GAAyB1B,aAAiB,WAC5C4B,EACEJ,IAAkB,OAASA,IAAkB,SACzCxB,EAAM,QAAQ,CAAC,EAAE,QACjBA,EAAM,QAAQ,CAAC,EAAE,QAEvB4B,EACEJ,IAAkB,OAASA,IAAkB,SACxCxB,EAAuB,QACvBA,EAAuB,QAGxBwB,EAAe,CACrB,IAAK,MACHJ,EAAWD,EAAWS,EAAS,EAAIT,EAAWS,EAAS,EACvDvB,EAAO,MAAM,UAAY,kBAAkB,CAACe,CAAQ,SACpD,MAEF,IAAK,QACHA,EAAWQ,EAAST,EAAW,EAAIS,EAAST,EAAW,EAChDd,EAAA,MAAM,UAAY,eAAee,CAAQ,YAChD,MAEF,IAAK,OACHA,EAAWD,EAAWS,EAAS,EAAIT,EAAWS,EAAS,EACvDvB,EAAO,MAAM,UAAY,eAAe,CAACe,CAAQ,YACjD,MAEF,QACEA,EAAWQ,EAAST,EAAW,EAAIS,EAAST,EAAW,EAChDd,EAAA,MAAM,UAAY,kBAAkBe,CAAQ,SACnD,KAAA,CAQJC,EAAiB,KAAK,MAAOD,GAH3BI,IAAkB,QAAUA,IAAkB,QAC1CD,EACAD,GAC+C,GAAG,EACxDhB,EAAe,MAAM,QAAU,GAAG,IAAMe,CAAc,GACxD,EAEMQ,EAAa7B,GAAqC,CAEtDA,EAAM,eAAe,EAEf,MAAA8B,EAAe,OAAO,WAAa,IAOzC,OALaZ,EAAA,GACNb,EAAA,UAAU,OAAO,cAAc,EAC/BA,EAAA,UAAU,IAAI,YAAY,EAGzBmB,EAAe,CACrB,IAAK,MAEDL,EAAAO,EAAA,GAAyB1B,aAAiB,WACtCA,EAAM,QAAQ,CAAC,EAAE,QAChBA,EAAuB,QAC9B,MAEF,IAAK,QAEDmB,EAAAO,EAAA,GAAyB1B,aAAiB,WACtCA,EAAM,QAAQ,CAAC,EAAE,QAChBA,EAAuB,QAC9B,MAEF,IAAK,OAEDmB,EAAAO,EAAA,GAAyB1B,aAAiB,WACtCA,EAAM,QAAQ,CAAC,EAAE,QAChBA,EAAuB,QAC9B,MAEF,QAEImB,EAAAO,EAAA,GAAyB1B,aAAiB,WACtCA,EAAM,QAAQ,CAAC,EAAE,QAChBA,EAAuB,QAC9B,KAAA,CAIA8B,GACO,SAAA,iBACPJ,EAAA,EAAwB,YAAc,cACtCC,CACF,EACS,SAAA,iBACPD,EAAA,EAAwB,WAAa,YACrCD,CACF,IAES,SAAA,oBACPC,EAAA,EAAwB,YAAc,cACtCC,CACF,EACS,SAAA,oBACPD,EAAA,EAAwB,WAAa,YACrCD,CACF,EAEJ,EAEAM,EAAgC1B,CAAM,EACtC2B,EAA2B3B,CAAM,EAE1BA,EAAA,iBACLqB,EAAA,EAAwB,aAAe,cACvCG,CACF,CACF,CAGA,SAASH,GAA+B,CAC/B,MAAA,iBAAkB,QAAU,UAAU,eAAiB,CAChE,CAEO,SAAShB,EAAYP,EAA4B,CAChD,MAAAE,EAASF,EAAc,cAA2B,eAAe,EACjEG,EAAiBH,EAAc,cACnC,0BACF,EAEAA,EAAc,QAAQ,MAAQ,SAE1B,GAACE,GAAU,CAACC,KAIhBD,EAAO,MAAM,UAAY,GACzBA,EAAO,QAAQ,MAAQ,SAChBA,EAAA,UAAU,OAAO,YAAY,EAC7BA,EAAA,UAAU,IAAI,cAAc,EAEpBC,EAAA,UAAU,OAAO,YAAY,EAC5CA,EAAe,QAAQ,MAAQ,SAEtB,SAAA,KAAK,UAAU,OAAO,iBAAiB,EACvC,SAAA,KAAK,MAAM,WAAa,GAEjCL,EAAgBI,CAAM,EACxB,CAEO,SAASY,EAAWd,EAA4B,CAC/C,MAAAE,EAASF,EAAc,cAA2B,eAAe,EACjEG,EAAiBH,EAAc,cACnC,0BACF,EAEI,CAACE,GAAU,CAACC,IAIhBH,EAAc,QAAQ,MAAQ,OAC9BE,EAAO,QAAQ,MAAQ,OAChBA,EAAA,UAAU,OAAO,YAAY,EACpCC,EAAe,QAAQ,MAAQ,OACtB,SAAA,KAAK,UAAU,IAAI,iBAAiB,EACpC,SAAA,KAAK,MAAM,WAAa,OAEjC,WAAW,IAAM,CACfZ,EAAeW,CAAM,GACpB,EAAE,EACP,CAEA,SAAS0B,EAAgC1B,EAAqB,CAEtD,MAAA4B,EAAsBjC,GAAsB,CAChDA,EAAM,gBAAgB,CACxB,EAGwBK,EAAO,iBAAiB,yBAAyB,EACzD,QAAS6B,GAAY,CAC3BA,EAAA,iBACN,cACAD,CACF,EACQC,EAAA,iBAAiB,aAAcD,CAAmC,CAAA,CAC3E,CACH,CAEA,SAASD,EAA2B3B,EAAqB,CAChDA,EAAA,iBAAiB,iBAAkB,IAAM,CAC9C0B,EAAgC1B,CAAM,CAAA,CACvC,CACH"}