@alto-avios/alto-ui 5.6.0 → 5.7.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/Popover.css +1 -1
- package/dist/components/Carousel/Carousel.js +7 -7
- package/dist/components/Carousel/Carousel.js.map +1 -1
- package/dist/components/CategoryTileGroup/CategoryTileGroup.js +7 -7
- package/dist/components/CategoryTileGroup/CategoryTileGroup.js.map +1 -1
- package/dist/components/Popover/Popover.d.ts +4 -3
- package/dist/components/Popover/Popover.js +3 -3
- package/dist/components/Popover/Popover.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/utils/carousel/hooks/_tests/useCarousel.test.d.ts +1 -0
- package/dist/utils/carousel/hooks/useCarousel.d.ts +99 -0
- package/dist/utils/carousel/hooks/useCarousel.js +2 -0
- package/dist/utils/carousel/hooks/useCarousel.js.map +1 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +1 -1
- package/dist/utils/layout/hooks/useFitCount.js.map +1 -1
- package/package.json +1 -1
package/dist/assets/Popover.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.
|
|
1
|
+
._popover_1ntxl_1{background:var(--alto-sem-color-bg-layer2-default);border:1px solid var(--alto-sem-color-border-tertiary);border-radius:var(--alto-card-radius,12px);box-shadow:0 4px 6px -1px var(--alto-sem-color-overlay-state-darken-active),0 2px 4px -2px var(--alto-sem-color-overlay-state-darken-active);position:absolute}._arrow_1ntxl_11{background:url('data:image/svg+xml;utf8,<svg width="17" height="18" viewBox="0 0 17 18" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="path-1-inside-1_15337_1997" fill="white"><path d="M17 9.29999L8.51472 17.7853L0.0294347 9.29999L8.51472 0.814707L17 9.29999Z"/></mask><path d="M17 9.29999L8.51472 17.7853L0.0294347 9.29999L8.51472 0.814707L17 9.29999Z" fill="%23FEFEFE"/><path d="M8.51472 0.814707L9.22182 0.107601L8.51472 -0.599506L7.80761 0.107601L8.51472 0.814707ZM0.736542 10.0071L9.22182 1.52181L7.80761 0.107601L-0.677672 8.59288L0.736542 10.0071ZM7.80761 1.52181L16.2929 10.0071L17.7071 8.59288L9.22182 0.107601L7.80761 1.52181Z" fill="%239593A0" fill-opacity="0.25" mask="url(%23path-1-inside-1_15337_1997)"/></svg>') 50% no-repeat;height:16px;position:absolute;width:16px}._arrow_1ntxl_11[data-placement=top]{bottom:-9px;left:50%;margin-left:-8.5px;transform:rotate(180deg)}._arrow_1ntxl_11[data-placement="top start"]{bottom:-9px;left:15px;margin-left:-8.5px;transform:rotate(180deg)}._arrow_1ntxl_11[data-placement="top end"]{bottom:-9px;margin-right:-8.5px;right:15px;transform:rotate(180deg)}._arrow_1ntxl_11[data-placement=bottom]{left:50%;margin-left:-8.5px;top:-9px}._arrow_1ntxl_11[data-placement=left]{margin-top:-9px;right:-9px;top:50%;transform:rotate(90deg)}._arrow_1ntxl_11[data-placement=right]{left:-9px;margin-top:-9px;top:50%;transform:rotate(-90deg)}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import{jsx as e,jsxs as t}from"react/jsx-runtime";import r,{useRef as
|
|
2
|
-
return e("div",{className:(()=>{const e=[w.carouselWrapper];return Number.isInteger(me)||e.push(w.showPartialItems),ue&&e.push(w.hasScrollPadding),F&&F.trim()?e.push(F):e.push(w.defaultCarouselWrapper),e.join(" ")})(),ref:pe,style:et,"data-dots-size":$,...Ue,"data-arrows-visible":Ze?"true":"false","data-transitioning":je?"true":void 0,"data-scroll-padding":ue,role:"region","aria-label":(()=>{if(C)return C;return`Carousel with ${oe} items, currently showing slide ${_e+1} of ${He}${
|
|
3
|
-
/* @__PURE__ */e(u,{className:`${w.scroller} ${v({focusStyle:D})} ${Q}`.trim(),ref:be,...tt,"data-focused":!!rt||void 0,"data-focus-visible":!!rt||void 0,"data-transitioning":je?"true":void 0,style:(()=>{const e={};if(le>=0&&(e.gap=`${le}px`),se>1&&le>0){const t=le*(me-1)/me;e.gridAutoColumns=`calc(${100/me}% - ${t}px)`}else se>1&&(e.gridAutoColumns=100/me+"%");return e})(),tabIndex:0,role:"region","aria-label":"Carousel content",onKeyDown:e=>{if("ArrowLeft"===e.key){e.preventDefault();const t=pe.current?.querySelector('[dir="prev"]');t&&!t.disabled&&t.click()}else if("ArrowRight"===e.key){e.preventDefault();const t=pe.current?.querySelector('[dir="next"]');t&&!t.disabled&&t.click()}else"Home"===e.key?(e.preventDefault(),Ge(0)):"End"===e.key&&(e.preventDefault(),Ge(He-1))},children:
|
|
4
|
-
return e(
|
|
1
|
+
import{jsx as e,jsxs as t}from"react/jsx-runtime";import r,{useRef as a,useState as n,useCallback as o,useEffect as i}from"react";import{c as s}from"../../index-CCUt_dAN.js";import{C as l,a as u,b as d,c,d as m}from"../../IconButton.module-C7YCy-MU.js";import{CarouselButton as f}from"./CarouselButton/CarouselButton.js";import{CarouselDots as h}from"./CarouselDots/CarouselDots.js";import{AutoplayControl as p}from"./AutoplayControl/AutoplayControl.js";import{focusStyleVariants as v}from"../../utils/focus/focusStyles.js";import{useFocusRing as y}from"@react-aria/focus";import{resolveResponsiveProp as g}from"../../utils/breakpoint/responsive.js";import{useBreakpoint as b}from"../../utils/breakpoint/hooks/useBreakpoint.js";import '../../assets/Carousel.css';const w={carouselWrapper:"_carouselWrapper_1uhyq_2",showPartialItems:"_showPartialItems_1uhyq_14",carousel:"_carousel_1uhyq_2",hasScrollPadding:"_hasScrollPadding_1uhyq_25",scroller:"_scroller_1uhyq_43",item:"_item_1uhyq_78",slideLeft:"_slideLeft_1uhyq_127",slideRight:"_slideRight_1uhyq_131",controls:"_controls_1uhyq_154",defaultPrevButton:"_defaultPrevButton_1uhyq_165",defaultNextButton:"_defaultNextButton_1uhyq_173",defaultDotsContainer:"_defaultDotsContainer_1uhyq_182",defaultCarouselWrapper:"_defaultCarouselWrapper_1uhyq_193",defaultAutoplayControl:"_defaultAutoplayControl_1uhyq_200",showOnHover:"_showOnHover_1uhyq_209",hidden:"_hidden_1uhyq_219",hiddenTabs:"_hiddenTabs_1uhyq_250",autoplayControlWrapper:"_autoplayControlWrapper_1uhyq_268"},_=s(w.carousel,{variants:{looping:{infinite:w.infinite,backToStart:w.native,off:void 0}}}),C=({children:s=[],"aria-label":C,"aria-describedby":x,looping:N="off",itemsPerPage:A=1,spaceBetweenItems:S=0,scrollPadding:P,iconType:q="chevron",arrowStyleVariant:L="neutral",arrowSize:M="md",hideDisabledArrow:E=!1,showArrowsOnHover:T=!1,focusStyle:D="default",dotsSize:$="md",dotsVariant:I="standard",hideDots:W=!1,autoPlay:k=!1,autoPlayInterval:B=2e3,autoPlayStyleVariant:j="neutralVibrant",autoPlayControlSize:z="md",mouseDragging:R=!0,carouselWrapperClassName:F="",prevArrowClassName:H="",nextArrowClassName:O="",autoplayControlClassName:V="",dotsContainerClassName:Y="",dotsWrapperClassName:X="",dotClassName:G="",activeDotClassName:K="",itemWrapperClassName:J="",scrollerClassName:Q="",activeItemClassName:U="",onArrowPress:Z,onDotPress:ee,onAutoplayPress:te,controlsRef:re,onNavigationStateChange:ae})=>{const ne=r.Children.toArray(s),oe=ne.length,ie=b(),se=g(A,ie)??1,le=g(S,ie)??0,ue=g(P,ie)??void 0,de=g(M,ie)??"md",ce=g(W,ie)??!1,me=Math.max(1,se),fe=Math.max(1,Math.floor(me)),he=r.useMemo(()=>{const e=Array.from({length:oe},(e,t)=>t).reduce((e,t)=>{const r=e.at(-1);return r&&r.length<fe?r.push(t):e.push([t]),e},[]);if(e.length>=2){const t=fe-e.at(-1).length;if(t>0){const r=[...e.at(-2)].splice(fe-t);e.at(-1).unshift(...r)}}return e},[oe,fe]),pe=a(null),ve=a(null),ye=a(null),ge=a(null),be=a(null),we=a([]),[_e,Ce]=n(0),[xe,Ne]=n(0),[Ae,Se]=n(!1),[Pe,qe]=n(he.length>1),[Le,Me]=n(0),[Ee,Te]=n(0),[De,$e]=n(!1),[Ie,We]=n(!1),[ke,Be]=n(!1),[je]=n(!1),[ze,Re]=n(null),Fe="infinite"===N?"infinite":"backToStart"===N?"native":"off"!==N?N:void 0,He=Le>0?Le:he.length,Oe=!ce&&He>1,Ve=(e,t)=>e||t,Ye=o(()=>pe.current?.querySelector('[data-carousel-scroller="true"]'),[]),Xe=o((e,t)=>{const r=t*Math.max(1,Math.floor(me)),a=e.querySelector(`[data-item-index="${r}"]`);if(!a)return!1;const n=e.getBoundingClientRect(),o=a.getBoundingClientRect(),i=parseFloat(getComputedStyle(e).paddingLeft)||0,s=Math.max(0,e.scrollWidth-e.clientWidth),l=e.scrollLeft+(o.left-n.left)-i,u=Math.min(Math.max(0,l),s);return e.scrollTo({left:u,behavior:"smooth"}),!0},[me]),Ge=o(e=>{if(!(e===_e||e<0||e>=He)&&pe.current){const t=pe.current.querySelector(`[data-slide-tab="true"][data-index="${e}"]`);t&&(t.click(),Ne(_e),Ce(e))}},[_e,He]),Ke=o(()=>{if(!pe.current||He<=1)return;const e=pe.current.querySelector('[data-carousel-scroller="true"]');if(!e)return;const t=Array.from(e.querySelectorAll("[data-item-index]"));if(!t.length)return;const r=e.getBoundingClientRect().left;let a=0,n=Number.POSITIVE_INFINITY;t.forEach(e=>{const t=parseInt(e.dataset.itemIndex??"",10);if(Number.isNaN(t))return;const o=Math.abs(e.getBoundingClientRect().left-r);o<n&&(n=o,a=t)});const o=Math.max(1,Math.floor(me)),i=Math.min(He-1,Math.floor(a/o));i!==_e&&(Ne(_e),Ce(i))},[_e,me,He]),Je=o(()=>{const e=Ye();if(e){const t=Math.max(0,_e-1);return Xe(e,t)||e.scrollBy({left:-e.clientWidth,behavior:"smooth"}),Ne(_e),Ce(t),void window.setTimeout(()=>{Ke()},200)}_e<=0||Ge(_e-1)},[_e,Ye,Ge,Xe,Ke]),Qe=o(()=>{const e=Ye();if(e){const t=Math.min(He-1,_e+1);return Xe(e,t)||e.scrollBy({left:e.clientWidth,behavior:"smooth"}),Ne(_e),Ce(t),void window.setTimeout(()=>{Ke()},200)}_e>=He-1||Ge(_e+1)},[_e,Ye,Ge,Xe,Ke,He]);i(()=>{const e=((e,t,r,a)=>({canGoPrev:e,canGoNext:t,currentPage:r,totalPages:a}))(Ae||_e>0,Pe||_e<He-1,_e,He);if(re)return re.current={prev:Je,next:Qe,goToSlide:Ge,...e},ae?.(e),()=>{re.current&&(re.current=null)};ae?.(e)},[re,Pe,Ae,_e,Qe,Je,Ge,ae,He]),i(()=>()=>{null!==ye.current&&window.clearTimeout(ye.current),null!==ge.current&&window.clearTimeout(ge.current)},[]),i(()=>{if(!pe.current||!R)return;const e=e=>{if(!De)return;const t=e.target;if(!(t instanceof Element))return;t.closest("a[href]")&&(e.preventDefault(),e.stopPropagation(),setTimeout(()=>{$e(!1)},0))},t=e=>{Re({x:e.clientX,y:e.clientY})},r=e=>{if(null===ze)return;const t=Math.abs(e.clientX-ze.x),r=Math.abs(e.clientY-ze.y);Math.sqrt(t*t+r*r)>5&&$e(!0)},a=()=>{De?setTimeout(()=>{$e(!1),Re(null)},50):Re(null)},n=pe.current;return n.addEventListener("mousedown",t,!0),n.addEventListener("mousemove",r,!0),n.addEventListener("mouseup",a,!0),n.addEventListener("click",e,!0),()=>{n.removeEventListener("mousedown",t,!0),n.removeEventListener("mousemove",r,!0),n.removeEventListener("mouseup",a,!0),n.removeEventListener("click",e,!0)}},[R,De,ze,5]),i(()=>{if(pe.current){const e=pe.current.querySelector(`.${w.scroller}`);e&&e instanceof HTMLElement&&(xe<_e?(e.classList.remove(w.slideLeft),e.classList.add(w.slideRight)):xe>_e&&(e.classList.remove(w.slideRight),e.classList.add(w.slideLeft)))}},[_e,xe]),i(()=>{const e=pe.current;if(!e)return;let t=null;const r=()=>{null===t&&(t=window.requestAnimationFrame(()=>{t=null,(()=>{const t=e.querySelectorAll('[data-slide-tab="true"]').length;Me(e=>e===t?e:t)})()}))},a=new MutationObserver(()=>{r()});return a.observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-slide-tab"]}),r(),()=>{null!==t&&window.cancelAnimationFrame(t),a.disconnect()}},[oe,me]),i(()=>{const e=Ye()??be.current;if(!e)return;let t=null;const r=()=>{null===t&&(t=window.requestAnimationFrame(()=>{t=null,a()}))},a=()=>{const t=Math.max(0,e.scrollWidth-e.clientWidth),r=e.scrollLeft,a=.01,n=t>a,o=n?r>a:_e>0,i=n?r<t-a:_e<He-1,s=we.current.length>0?we.current:Array.from(e.querySelectorAll("[data-item-index]"));0===we.current.length&&(we.current=s);const l=r+(parseFloat(getComputedStyle(e).paddingLeft)||0)+a,u=s.find(e=>e.offsetLeft+e.offsetWidth>l)?.dataset.itemIndex??"0",d=parseInt(u,10),c=Number.isNaN(d)?0:d,m=Math.max(1,Math.ceil(me)),f=c+m-1;s.forEach(e=>{const t=e.dataset.itemIndex;if("string"!=typeof t)return;const r=parseInt(t,10);if(Number.isNaN(r))return;r>=c&&r<=f?(e.removeAttribute("inert"),e.removeAttribute("aria-hidden")):(e.setAttribute("inert","true"),e.setAttribute("aria-hidden","true"))});const h=he.map(e=>e[0]).reduce((e,t,r)=>t<=c?r:e,0),p=n?h:_e;Se(e=>e===o?e:o),qe(e=>e===i?e:i),Te(e=>e===c?e:c),Ce(e=>(e!==p&&Ne(e),e===p?e:p))},n=new ResizeObserver(()=>{r()});n.observe(e);(()=>{const t=Array.from(e.querySelectorAll("[data-item-index]"));return we.current=t,t})().forEach(e=>{n.observe(e)});const o=()=>{r()};return e.addEventListener("load",o,!0),a(),e.addEventListener("scroll",a,{passive:!0}),window.addEventListener("resize",r),()=>{null!==t&&window.cancelAnimationFrame(t),n.disconnect(),e.removeEventListener("load",o,!0),e.removeEventListener("scroll",a),window.removeEventListener("resize",r),we.current=[]}},[_e,Ye,he,me,He,oe]);const Ue=T?{onMouseEnter:()=>We(!0),onMouseLeave:()=>We(!1),onFocus:()=>Be(!0),onBlur:e=>{e.currentTarget.contains(e.relatedTarget)||Be(!1)}}:{},Ze=He>1&&(!T||Ie||ke),et=(()=>{const e={};return 1!==se&&(e["--items-per-page"]=String(me)),le>0&&(e["--space-between-items"]=`${le}px`),ue&&(e["--scroll-padding"]=ue),Object.keys(e).length>0?e:void 0})(),{focusProps:tt,isFocusVisible:rt}=y(),at=he[_e]??[];/* @__PURE__ */
|
|
2
|
+
return e("div",{className:(()=>{const e=[w.carouselWrapper];return Number.isInteger(me)||e.push(w.showPartialItems),ue&&e.push(w.hasScrollPadding),F&&F.trim()?e.push(F):e.push(w.defaultCarouselWrapper),e.join(" ")})(),ref:pe,style:et,"data-dots-size":$,...Ue,"data-arrows-visible":Ze?"true":"false","data-transitioning":je?"true":void 0,"data-scroll-padding":ue,role:"region","aria-label":(()=>{if(C)return C;return`Carousel with ${oe} items, currently showing slide ${_e+1} of ${He}${k?", autoplay enabled":""}`})(),"aria-describedby":x,"aria-roledescription":"carousel",children:/* @__PURE__ */t(l,{className:`${_({looping:N})}`,loop:Fe,ref:ve,autoplay:k,autoplayInterval:B,mouseDragging:R&&!je,onActivePageIndexChange:({index:e})=>{Ce(t=>(t!==e&&Ne(t),e))},onDragStart:e=>{$e(!0),e&&e.nativeEvent&&Re({x:e.nativeEvent.clientX,y:e.nativeEvent.clientY})},onDragEnd:()=>{null!==ye.current&&window.clearTimeout(ye.current),null!==ge.current&&window.clearTimeout(ge.current),ye.current=window.setTimeout(()=>{$e(!1),Ke()},50),ge.current=window.setTimeout(()=>{Ke()},150)},"data-dragging":De?"true":"false",itemsPerPage:me,scrollPadding:ue,"data-has-space-between":le>0?"true":void 0,"data-items-per-page":1!==se?me.toString():void 0,children:[
|
|
3
|
+
/* @__PURE__ */e(u,{className:`${w.scroller} ${v({focusStyle:D})} ${Q}`.trim(),ref:be,"data-carousel-scroller":"true",...tt,"data-focused":!!rt||void 0,"data-focus-visible":!!rt||void 0,"data-transitioning":je?"true":void 0,style:(()=>{const e={};if(le>=0&&(e.gap=`${le}px`),se>1&&le>0){const t=le*(me-1)/me;e.gridAutoColumns=`calc(${100/me}% - ${t}px)`}else se>1&&(e.gridAutoColumns=100/me+"%");return e})(),tabIndex:0,role:"region","aria-label":"Carousel content",onKeyDown:e=>{if("ArrowLeft"===e.key){e.preventDefault();const t=pe.current?.querySelector('[dir="prev"]');t&&!t.disabled&&t.click()}else if("ArrowRight"===e.key){e.preventDefault();const t=pe.current?.querySelector('[dir="next"]');t&&!t.disabled&&t.click()}else"Home"===e.key?(e.preventDefault(),Ge(0)):"End"===e.key&&(e.preventDefault(),Ge(He-1))},children:ne.map((t,r)=>{const a=at.includes(r),n=[J||"",w.item,a&&U?U:""].filter(Boolean).join(" ");/* @__PURE__ */
|
|
4
|
+
return e(d,{className:n,"data-is-active":a?"true":void 0,"data-item-index":r,"aria-label":`Slide ${r+1} of ${oe}`,children:t},r)})}),
|
|
5
5
|
/* @__PURE__ */t("div",{className:`${w.controls} ${T?w.showOnHover:""}`,children:[
|
|
6
|
-
/* @__PURE__ */e(f,{dir:"prev",variant:L,size:
|
|
7
|
-
/* @__PURE__ */e(f,{dir:"next",variant:L,size:
|
|
8
|
-
/* @__PURE__ */e("div",{className:w.hiddenTabs,children:/* @__PURE__ */e(
|
|
6
|
+
/* @__PURE__ */e(f,{dir:"prev",variant:L,size:de,focusStyle:D,iconType:q,hideDisabledArrow:E,overrideHideDisabledArrow:Fe?void 0:!Ae,onClick:Fe?void 0:()=>Ge(Ae?Ee<=0?0:Math.max(0,Math.ceil(Ee/fe)-1):_e),onPress:()=>Z?.("prev"),className:`${Ve(H,w.defaultPrevButton)} ${Ze?"":w.hidden}`}),
|
|
7
|
+
/* @__PURE__ */e(f,{dir:"next",variant:L,size:de,focusStyle:D,iconType:q,hideDisabledArrow:E,overrideHideDisabledArrow:Fe?void 0:!Pe,onClick:Fe?void 0:()=>Ge(Pe?Math.min(He-1,Math.floor(Ee/fe)+1):_e),onPress:()=>Z?.("next"),className:`${Ve(O,w.defaultNextButton)} ${Ze?"":w.hidden}`})]}),
|
|
8
|
+
/* @__PURE__ */e("div",{className:w.hiddenTabs,children:/* @__PURE__ */e(c,{children:t=>/* @__PURE__ */e(m,{index:t.index,"data-index":t.index,"data-slide-tab":"true",tabIndex:-1},t.index)})}),k&&/* @__PURE__ */e("div",{className:`${w.autoplayControlWrapper} ${Ve(V,w.defaultAutoplayControl)}`,children:/* @__PURE__ */e(p,{variant:j,size:z,className:"",onPress:te})}),Oe&&/* @__PURE__ */e(h,{totalItems:He,currentPage:_e,onDotClick:e=>Ge(e),onPress:ee,focusStyle:D,dotsSize:$,variant:I,className:Ve(Y,w.defaultDotsContainer),dotsWrapperClassName:X,dotClassName:G,activeDotClassName:K,isTransitioning:je})]})})};export{C as Carousel,C as default};
|
|
9
9
|
//# sourceMappingURL=Carousel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Carousel.js","sources":["../../../src/components/Carousel/Carousel.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, useCallback } from 'react';\nimport { cva } from 'class-variance-authority';\nimport {\n Carousel as AriaCarousel,\n CarouselScroller as AriaCarouselScroller,\n CarouselItem as AriaCarouselItem,\n CarouselTabs as AriaCarouselTabs,\n CarouselTab as AriaCarouselTab,\n} from 'react-aria-carousel';\nimport styles from './Carousel.module.css';\nimport { CarouselButton } from './CarouselButton/CarouselButton';\nimport { CarouselDots } from './CarouselDots/CarouselDots';\nimport { AutoplayControl } from './AutoplayControl/AutoplayControl';\nimport { focusStyleVariants } from '../../utils/focus/focusStyles';\nimport { useFocusRing } from '@react-aria/focus';\n\nimport {\n WithResponsive,\n resolveResponsiveProp,\n} from '../../utils/breakpoint/responsive';\nimport { useBreakpoint } from '../../utils/breakpoint/hooks/useBreakpoint';\n\nconst carousel = cva(styles.carousel, {\n variants: {\n looping: {\n infinite: styles.infinite,\n backToStart: styles.native,\n off: undefined,\n },\n },\n});\n\nexport interface CarouselBaseProps {\n /**\n * Content to be displayed in the carousel\n */\n children?: React.ReactNode | React.ReactNode[];\n\n /**\n * Accessible name for the carousel\n * Describes the purpose or content of the carousel for screen readers\n * @example \"Featured products\" or \"Customer testimonials\"\n */\n 'aria-label'?: string;\n\n /**\n * ID of an element that provides additional description for the carousel\n * Can reference an element that gives more context about the carousel's content\n * @example \"carousel-description\"\n */\n 'aria-describedby'?: string;\n\n /**\n * Looping behavior\n * - 'infinite': Carousel loops continuously\n * - 'backToStart': Carousel loops back to start after reaching end\n * - 'off': Looping is disabled\n * @default 'off'\n */\n looping?: 'infinite' | 'backToStart' | 'off';\n\n /**\n * Number of items to display per page (minimum: 1)\n * Decimal values (e.g., 2.25) can be used to show partial items\n * @default 1\n * @min 1\n */\n itemsPerPage?: number;\n\n /**\n * Space between carousel items in pixels\n * @default 0\n */\n spaceBetweenItems?: number;\n\n /**\n * Amount of padding added to the scroll container to create a \"peek\" effect.\n * This shows a portion of the adjacent items.\n * Accepts any valid CSS dimension (px, rem, em, %) - e.g., \"15%\", \"20px\", \"2rem\"\n * @example \"15%\" or \"20px\" or \"2rem\"\n * @default undefined\n */\n scrollPadding?: string;\n\n /* Button and Controls Props */\n /**\n * Icon type for navigation buttons\n * @default 'chevron'\n */\n iconType?: 'chevron';\n\n /**\n * Style of carousel arrow buttons\n * @default 'neutral'\n */\n arrowStyleVariant?:\n | 'neutral'\n | 'white'\n | 'shapeFlat'\n | 'shapeElevated'\n | 'gradient';\n\n /**\n * Size of navigation buttons\n * @default 'md'\n */\n arrowSize?: 'sm' | 'md' | 'lg';\n\n /**\n * Whether to hide navigation buttons when disabled\n * Note: Navigation buttons are automatically hidden when all items fit on one page\n * @default false\n */\n hideDisabledArrow?: boolean;\n\n /**\n * Whether to show arrows only when hovering over the carousel or when it has focus\n * @default false\n */\n showArrowsOnHover?: boolean;\n\n /**\n * Focus style for interactive elements\n * @default 'default'\n */\n focusStyle?: 'default' | 'white';\n\n /* Dots/Tabs Props */\n /**\n * Size of pagination dots\n * @default 'md'\n */\n dotsSize?: 'md' | 'lg';\n\n /**\n * Visual style of pagination dots\n * @default 'standard'\n */\n dotsVariant?: 'standard' | 'transparent';\n\n /**\n * Whether to hide pagination dots\n * Note: Dots are automatically hidden when there's only one page\n * @default false\n */\n hideDots?: boolean;\n\n /* Autoplay Props */\n /**\n * Whether the autoplay is enabled\n * @default false\n */\n autoPlay?: boolean;\n\n /**\n * Interval in milliseconds between slides during autoplay\n * @default 2000\n */\n autoPlayInterval?: number;\n\n /**\n * Style variant for autoplay control button\n * @default 'neutralVibrant'\n */\n autoPlayStyleVariant?:\n | 'neutralVibrant'\n | 'neutralSubtle'\n | 'whiteVibrant'\n | 'whiteSubtle';\n\n /**\n * Size of autoplay control button\n * @default 'md'\n */\n autoPlayControlSize?: 'sm' | 'md';\n\n /**\n * Whether to enable mouse dragging for the carousel\n * @default true\n */\n mouseDragging?: boolean;\n\n /* Custom Class Names for Positioning and Styling */\n /**\n * Class name for the outermost wrapper of the carousel\n */\n carouselWrapperClassName?: string;\n\n /**\n * Class name for the previous button\n * Use this to position the previous button\n */\n prevArrowClassName?: string;\n\n /**\n * Class name for the next button\n * Use this to position the next button\n */\n nextArrowClassName?: string;\n\n /**\n * Class name for the autoplay control\n * Use this to position the autoplay button\n */\n autoplayControlClassName?: string;\n\n /**\n * Class name for the dots container\n * Use this to position the pagination dots\n */\n dotsContainerClassName?: string;\n\n /**\n * Class name for the dots wrapper\n * Use this to style the dots wrapper\n */\n dotsWrapperClassName?: string;\n\n /**\n * Class name for individual dots\n */\n dotClassName?: string;\n\n /**\n * Class name for the active dot\n */\n activeDotClassName?: string;\n\n /**\n * Class name for individual carousel item wrappers\n * Use this to style the wrapper div around each carousel item\n */\n itemWrapperClassName?: string;\n\n /**\n * Class name for the scroll container\n * Use this to customize the grid/scroll layout of carousel items\n */\n scrollerClassName?: string;\n\n /**\n * Class name for the currently active/visible carousel item\n * This class is applied to the item that corresponds to the current page\n */\n activeItemClassName?: string;\n\n /**\n * Callback fired when a carousel navigation button (prev/next) is pressed\n */\n onArrowPress?: (direction: 'prev' | 'next') => void;\n\n /**\n * Callback fired when a dot is pressed\n */\n onDotPress?: (index: number) => void;\n\n /**\n * Callback fired when the autoplay button is pressed\n */\n onAutoplayPress?: (state: 'play' | 'pause') => void;\n\n /**\n * Imperative controls ref for driving carousel navigation from external UI.\n */\n controlsRef?: React.MutableRefObject<CarouselControls | null>;\n\n /**\n * Callback fired when carousel navigation state changes.\n */\n onNavigationStateChange?: (state: CarouselNavigationState) => void;\n}\n\nexport interface CarouselControls {\n prev: () => void;\n next: () => void;\n goToSlide: (index: number) => void;\n canGoPrev: boolean;\n canGoNext: boolean;\n currentPage: number;\n totalPages: number;\n}\n\nexport interface CarouselNavigationState {\n canGoPrev: boolean;\n canGoNext: boolean;\n currentPage: number;\n totalPages: number;\n}\n\nconst toNavigationState = (\n currentPage: number,\n totalPages: number,\n): CarouselNavigationState => ({\n canGoPrev: currentPage > 0,\n canGoNext: currentPage < totalPages - 1,\n currentPage,\n totalPages,\n});\n\ntype ResponsiveKeys =\n | 'itemsPerPage'\n | 'spaceBetweenItems'\n | 'scrollPadding'\n | 'showArrowsOnHover'\n | 'arrowSize'\n | 'hideDots';\n\nexport type CarouselProps = WithResponsive<CarouselBaseProps, ResponsiveKeys>;\n\nexport const Carousel = ({\n children = [],\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedby,\n looping = 'off',\n itemsPerPage = 1,\n spaceBetweenItems = 0,\n scrollPadding,\n\n // Button and Controls Props\n iconType = 'chevron',\n arrowStyleVariant = 'neutral',\n arrowSize = 'md',\n hideDisabledArrow = false,\n showArrowsOnHover = false,\n focusStyle = 'default',\n\n // Dots/Tabs Props\n dotsSize = 'md',\n dotsVariant = 'standard',\n hideDots = false,\n\n // Autoplay Props\n autoPlay = false,\n autoPlayInterval = 2000,\n autoPlayStyleVariant = 'neutralVibrant',\n autoPlayControlSize = 'md',\n\n // Mouse Dragging\n mouseDragging = true,\n\n // Custom Class Names\n carouselWrapperClassName = '',\n prevArrowClassName = '',\n nextArrowClassName = '',\n autoplayControlClassName = '',\n dotsContainerClassName = '',\n dotsWrapperClassName = '',\n dotClassName = '',\n activeDotClassName = '',\n itemWrapperClassName = '',\n scrollerClassName = '',\n activeItemClassName = '',\n\n // Event Callbacks\n onArrowPress,\n onDotPress,\n onAutoplayPress,\n controlsRef,\n onNavigationStateChange,\n}: CarouselProps) => {\n // Map looping values to expected loop values for AriaCarousel\n const getLoopValue = () => {\n if (looping === 'infinite') return 'infinite';\n if (looping === 'backToStart') return 'native';\n if (looping === 'off') return undefined;\n return looping; // other values pass through\n };\n\n const items = React.Children.toArray(children);\n const totalItems = items.length;\n\n const breakpoint = useBreakpoint();\n const resolvedItemsPerPage =\n resolveResponsiveProp(itemsPerPage, breakpoint) ?? 1;\n const resolvedSpaceBetweenItems =\n resolveResponsiveProp(spaceBetweenItems, breakpoint) ?? 0;\n const resolvedScrollPadding =\n resolveResponsiveProp(scrollPadding, breakpoint) ?? undefined;\n const resolvedArrowSize =\n resolveResponsiveProp(arrowSize, breakpoint) ?? 'md';\n const resolvedHideDots = resolveResponsiveProp(hideDots, breakpoint) ?? false;\n\n // Ensure itemsPerPage is at least 1\n // We allow decimal values for showing partial items\n const safeItemsPerPage = Math.max(1, resolvedItemsPerPage);\n const wholeItemsPerPage = Math.max(1, Math.floor(safeItemsPerPage));\n\n const pageItemGroups = React.useMemo(() => {\n const groups = Array.from(\n { length: totalItems },\n (_, index) => index,\n ).reduce<number[][]>((accumulator, index) => {\n const currentGroup = accumulator.at(-1);\n\n if (currentGroup && currentGroup.length < wholeItemsPerPage) {\n currentGroup.push(index);\n } else {\n accumulator.push([index]);\n }\n\n return accumulator;\n }, []);\n\n if (groups.length >= 2) {\n const deficit = wholeItemsPerPage - groups.at(-1)!.length;\n\n if (deficit > 0) {\n const fill = [...groups.at(-2)!].splice(wholeItemsPerPage - deficit);\n groups.at(-1)!.unshift(...fill);\n }\n }\n\n return groups;\n }, [totalItems, wholeItemsPerPage]);\n\n const carouselRef = useRef<HTMLDivElement>(null);\n const ariaCarouselRef = useRef<HTMLDivElement>(null);\n const dragEndResetTimeoutRef = useRef<number | null>(null);\n const dragEndSyncTimeoutRef = useRef<number | null>(null);\n const scrollerRef = useRef<HTMLDivElement>(null);\n const itemElementsRef = useRef<HTMLElement[]>([]);\n\n const [currentPage, setCurrentPage] = useState(0);\n const [prevPage, setPrevPage] = useState(0);\n const [canScrollPrev, setCanScrollPrev] = useState(false);\n const [canScrollNext, setCanScrollNext] = useState(pageItemGroups.length > 1);\n const [ariaTabsPageCount, setAriaTabsPageCount] = useState(0);\n const [firstVisibleItemIndex, setFirstVisibleItemIndex] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [isTransitioning] = useState(false);\n\n // Add state variables for drag/link handling\n const [dragStartPosition, setDragStartPosition] = useState<{\n x: number;\n y: number;\n } | null>(null);\n const dragThreshold = 5; // Pixels of movement needed to consider as a drag\n const loopMode = getLoopValue();\n const totalPages =\n ariaTabsPageCount > 0 ? ariaTabsPageCount : pageItemGroups.length;\n\n // Determine if dots should be shown\n // Hide dots if:\n // 1. resolvedHideDots prop is true (responsive), OR\n // 2. There's only one page (totalPages <= 1)\n const shouldShowDots = !resolvedHideDots && totalPages > 1;\n\n // Helper function to get class names with defaults\n const getClassNameWithDefault = (\n customClassName: string,\n defaultClassName: string,\n ): string => {\n return customClassName || defaultClassName;\n };\n\n // Get CSS classes based on showing partial items\n const getCarouselWrapperClasses = () => {\n const classes = [styles.carouselWrapper];\n\n if (!Number.isInteger(safeItemsPerPage)) {\n classes.push(styles.showPartialItems);\n }\n\n if (resolvedScrollPadding) {\n classes.push(styles.hasScrollPadding);\n }\n\n // Apply default wrapper class if no custom wrapper class is provided\n if (carouselWrapperClassName && carouselWrapperClassName.trim()) {\n classes.push(carouselWrapperClassName);\n } else {\n classes.push(styles.defaultCarouselWrapper);\n }\n\n return classes.join(' ');\n };\n\n // Determine if we need to set CSS variables and return only what's needed\n const getCssVariables = () => {\n const cssVars: Record<string, string> = {};\n\n // Only set these if they differ from defaults\n if (resolvedItemsPerPage !== 1) {\n cssVars['--items-per-page'] = String(safeItemsPerPage);\n }\n\n if (resolvedSpaceBetweenItems > 0) {\n cssVars['--space-between-items'] = `${resolvedSpaceBetweenItems}px`;\n }\n\n if (resolvedScrollPadding) {\n cssVars['--scroll-padding'] = resolvedScrollPadding;\n }\n\n return Object.keys(cssVars).length > 0 ? cssVars : undefined;\n };\n\n // Helper function to determine which items are currently active/visible\n const getActiveItemIndices = () => {\n return pageItemGroups[currentPage] ?? [];\n };\n\n const getScrollerElement = useCallback(() => {\n return carouselRef.current?.querySelector(\n '[data-carousel-scroller=\"true\"]',\n ) as HTMLElement | null;\n }, []);\n\n const scrollToPage = useCallback(\n (scroller: HTMLElement, pageIndex: number) => {\n const itemsPerPageWhole = Math.max(1, Math.floor(safeItemsPerPage));\n const targetItemIndex = pageIndex * itemsPerPageWhole;\n const targetItem = scroller.querySelector(\n `[data-item-index=\"${targetItemIndex}\"]`,\n ) as HTMLElement | null;\n\n if (!targetItem) {\n return false;\n }\n\n const scrollerRect = scroller.getBoundingClientRect();\n const itemRect = targetItem.getBoundingClientRect();\n const targetScrollLeft =\n scroller.scrollLeft + (itemRect.left - scrollerRect.left);\n\n scroller.scrollTo({\n left: targetScrollLeft,\n behavior: 'smooth',\n });\n\n return true;\n },\n [safeItemsPerPage],\n );\n\n const navigateToSlide = useCallback(\n (index: number) => {\n if (index === currentPage || index < 0 || index >= totalPages) {\n return;\n }\n\n // Find and click the corresponding hidden tab\n if (carouselRef.current) {\n const targetTab = carouselRef.current.querySelector(\n `[data-slide-tab=\"true\"][data-index=\"${index}\"]`,\n ) as HTMLElement;\n\n if (targetTab) {\n targetTab.click();\n setPrevPage(currentPage);\n setCurrentPage(index);\n }\n }\n },\n [currentPage, totalPages],\n );\n\n const syncCurrentPageFromVisibleItem = useCallback(() => {\n if (!carouselRef.current || totalPages <= 1) {\n return;\n }\n\n const scroller = carouselRef.current.querySelector(\n '[data-carousel-scroller=\"true\"]',\n ) as HTMLElement | null;\n\n if (!scroller) {\n return;\n }\n\n const itemElements = Array.from(\n scroller.querySelectorAll('[data-item-index]'),\n ) as HTMLElement[];\n\n if (!itemElements.length) {\n return;\n }\n\n const scrollerLeft = scroller.getBoundingClientRect().left;\n let closestItemIndex = 0;\n let closestDistance = Number.POSITIVE_INFINITY;\n\n itemElements.forEach((element) => {\n const index = parseInt(element.dataset.itemIndex ?? '', 10);\n\n if (Number.isNaN(index)) {\n return;\n }\n\n const distance = Math.abs(\n element.getBoundingClientRect().left - scrollerLeft,\n );\n\n if (distance < closestDistance) {\n closestDistance = distance;\n closestItemIndex = index;\n }\n });\n\n const itemsPerPageWhole = Math.max(1, Math.floor(safeItemsPerPage));\n const nextPage = Math.min(\n totalPages - 1,\n Math.floor(closestItemIndex / itemsPerPageWhole),\n );\n\n if (nextPage !== currentPage) {\n setPrevPage(currentPage);\n setCurrentPage(nextPage);\n }\n }, [currentPage, safeItemsPerPage, totalPages]);\n\n const navigateToPreviousSlide = useCallback(() => {\n const scroller = getScrollerElement();\n\n if (scroller) {\n const targetPage = Math.max(0, currentPage - 1);\n const didScrollToItemGroup = scrollToPage(scroller, targetPage);\n\n if (!didScrollToItemGroup) {\n scroller.scrollBy({\n left: -scroller.clientWidth,\n behavior: 'smooth',\n });\n }\n\n setPrevPage(currentPage);\n setCurrentPage(targetPage);\n\n window.setTimeout(() => {\n syncCurrentPageFromVisibleItem();\n }, 200);\n return;\n }\n\n if (currentPage <= 0) {\n return;\n }\n\n navigateToSlide(currentPage - 1);\n }, [\n currentPage,\n getScrollerElement,\n navigateToSlide,\n scrollToPage,\n syncCurrentPageFromVisibleItem,\n ]);\n\n const navigateToNextSlide = useCallback(() => {\n const scroller = getScrollerElement();\n\n if (scroller) {\n const targetPage = Math.min(totalPages - 1, currentPage + 1);\n const didScrollToItemGroup = scrollToPage(scroller, targetPage);\n\n if (!didScrollToItemGroup) {\n scroller.scrollBy({\n left: scroller.clientWidth,\n behavior: 'smooth',\n });\n }\n\n setPrevPage(currentPage);\n setCurrentPage(targetPage);\n\n window.setTimeout(() => {\n syncCurrentPageFromVisibleItem();\n }, 200);\n return;\n }\n\n if (currentPage >= totalPages - 1) {\n return;\n }\n\n navigateToSlide(currentPage + 1);\n }, [\n currentPage,\n getScrollerElement,\n navigateToSlide,\n scrollToPage,\n syncCurrentPageFromVisibleItem,\n totalPages,\n ]);\n\n useEffect(() => {\n const navigationState = toNavigationState(currentPage, totalPages);\n\n if (!controlsRef) {\n onNavigationStateChange?.(navigationState);\n return;\n }\n\n controlsRef.current = {\n prev: navigateToPreviousSlide,\n next: navigateToNextSlide,\n goToSlide: navigateToSlide,\n ...navigationState,\n };\n\n onNavigationStateChange?.(navigationState);\n\n return () => {\n if (controlsRef.current) {\n controlsRef.current = null;\n }\n };\n }, [\n controlsRef,\n currentPage,\n navigateToNextSlide,\n navigateToPreviousSlide,\n navigateToSlide,\n onNavigationStateChange,\n totalPages,\n ]);\n\n useEffect(() => {\n return () => {\n if (dragEndResetTimeoutRef.current !== null) {\n window.clearTimeout(dragEndResetTimeoutRef.current);\n }\n\n if (dragEndSyncTimeoutRef.current !== null) {\n window.clearTimeout(dragEndSyncTimeoutRef.current);\n }\n };\n }, []);\n\n // Add effect to handle link click prevention during drag\n useEffect(() => {\n if (!carouselRef.current || !mouseDragging) return;\n\n // Function to prevent link activation during/after drag\n const preventLinkActivation = (e: MouseEvent) => {\n if (!isDragging) return;\n\n const target = e.target;\n if (!(target instanceof Element)) return;\n\n const link = target.closest('a[href]');\n if (!link) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n // Reset isDragging after the current event cycle completes\n setTimeout(() => {\n setIsDragging(false);\n }, 0);\n };\n\n // Function to handle mouse down events\n const handleMouseDown = (e: MouseEvent) => {\n setDragStartPosition({ x: e.clientX, y: e.clientY });\n };\n\n // Function to handle mouse move events\n const handleMouseMove = (e: MouseEvent) => {\n if (dragStartPosition === null) return;\n\n // Calculate the distance moved\n const dx = Math.abs(e.clientX - dragStartPosition.x);\n const dy = Math.abs(e.clientY - dragStartPosition.y);\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n // If the user has moved more than the threshold, consider it a drag\n if (distance > dragThreshold) {\n setIsDragging(true);\n }\n };\n\n // Function to handle mouse up events\n const handleMouseUp = () => {\n // If this wasn't a drag, don't interfere with normal clicks\n if (!isDragging) {\n setDragStartPosition(null);\n return;\n }\n\n // Add a brief delay for the click prevention to work\n setTimeout(() => {\n setIsDragging(false);\n setDragStartPosition(null);\n }, 50);\n };\n\n // Add event listeners for the carousel container\n const carouselElement = carouselRef.current;\n\n // Capture phase is important to catch events before they reach the links\n carouselElement.addEventListener('mousedown', handleMouseDown, true);\n carouselElement.addEventListener('mousemove', handleMouseMove, true);\n carouselElement.addEventListener('mouseup', handleMouseUp, true);\n\n // This is the key event listener that prevents link activation\n carouselElement.addEventListener('click', preventLinkActivation, true);\n\n return () => {\n // Clean up event listeners\n carouselElement.removeEventListener('mousedown', handleMouseDown, true);\n carouselElement.removeEventListener('mousemove', handleMouseMove, true);\n carouselElement.removeEventListener('mouseup', handleMouseUp, true);\n carouselElement.removeEventListener('click', preventLinkActivation, true);\n };\n }, [mouseDragging, isDragging, dragStartPosition, dragThreshold]);\n\n // Handle transition direction\n useEffect(() => {\n if (carouselRef.current) {\n const scroller = carouselRef.current.querySelector(`.${styles.scroller}`);\n if (scroller && scroller instanceof HTMLElement) {\n if (prevPage < currentPage) {\n scroller.classList.remove(styles.slideLeft);\n scroller.classList.add(styles.slideRight);\n } else if (prevPage > currentPage) {\n scroller.classList.remove(styles.slideRight);\n scroller.classList.add(styles.slideLeft);\n }\n }\n }\n }, [currentPage, prevPage]);\n\n // Keep total page count aligned with the pages emitted by react-aria-carousel.\n useEffect(() => {\n const carouselElement = carouselRef.current;\n if (!carouselElement) return;\n\n let animationFrameId: number | null = null;\n\n const updateAriaPageCount = () => {\n const nextCount = carouselElement.querySelectorAll(\n '[data-slide-tab=\"true\"]',\n ).length;\n\n setAriaTabsPageCount((previousCount) =>\n previousCount === nextCount ? previousCount : nextCount,\n );\n };\n\n const scheduleAriaPageCountUpdate = () => {\n if (animationFrameId !== null) {\n return;\n }\n\n animationFrameId = window.requestAnimationFrame(() => {\n animationFrameId = null;\n updateAriaPageCount();\n });\n };\n\n const observer = new MutationObserver(() => {\n scheduleAriaPageCountUpdate();\n });\n\n observer.observe(carouselElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['data-slide-tab'],\n });\n\n scheduleAriaPageCountUpdate();\n\n return () => {\n if (animationFrameId !== null) {\n window.cancelAnimationFrame(animationFrameId);\n }\n\n observer.disconnect();\n };\n }, [totalItems, safeItemsPerPage]);\n\n // Track the actual scroll position so arrow state matches mouse dragging,\n // even when the underlying library still considers the first page active.\n useEffect(() => {\n const scroller = scrollerRef.current;\n if (!scroller) return;\n\n let animationFrameId: number | null = null;\n\n const scheduleScrollNavigationUpdate = () => {\n if (animationFrameId !== null) {\n return;\n }\n\n animationFrameId = window.requestAnimationFrame(() => {\n animationFrameId = null;\n updateScrollNavigation();\n });\n };\n\n const updateScrollNavigation = () => {\n const maxScrollLeft = Math.max(\n 0,\n scroller.scrollWidth - scroller.clientWidth,\n );\n const scrollLeft = scroller.scrollLeft;\n const epsilon = 1;\n const hasScrollableOverflow = maxScrollLeft > epsilon;\n const nextCanScrollPrev = hasScrollableOverflow\n ? scrollLeft > epsilon\n : currentPage > 0;\n const nextCanScrollNext = hasScrollableOverflow\n ? scrollLeft < maxScrollLeft - epsilon\n : currentPage < totalPages - 1;\n\n const itemElements =\n itemElementsRef.current.length > 0\n ? itemElementsRef.current\n : Array.from(\n scroller.querySelectorAll<HTMLElement>('[data-item-index]'),\n );\n\n if (itemElementsRef.current.length === 0) {\n itemElementsRef.current = itemElements;\n }\n\n const viewportStart = scrollLeft + epsilon;\n const nextFirstVisibleItemIndex =\n itemElements.find((item) => {\n const itemEnd = item.offsetLeft + item.offsetWidth;\n return itemEnd > viewportStart;\n })?.dataset.itemIndex ?? '0';\n\n const parsedFirstVisibleItemIndex = parseInt(\n nextFirstVisibleItemIndex,\n 10,\n );\n const safeFirstVisibleItemIndex = Number.isNaN(\n parsedFirstVisibleItemIndex,\n )\n ? 0\n : parsedFirstVisibleItemIndex;\n\n // Keep the currently visible item window interactive even before\n // the underlying carousel page index snaps to the next page start.\n const visibleItemCount = Math.max(1, Math.ceil(safeItemsPerPage));\n const visibleWindowEndIndex =\n safeFirstVisibleItemIndex + visibleItemCount - 1;\n\n itemElements.forEach((item) => {\n const itemIndexString = item.dataset.itemIndex;\n if (typeof itemIndexString !== 'string') return;\n\n const itemIndex = parseInt(itemIndexString, 10);\n if (Number.isNaN(itemIndex)) return;\n\n const isInVisibleWindow =\n itemIndex >= safeFirstVisibleItemIndex &&\n itemIndex <= visibleWindowEndIndex;\n\n if (isInVisibleWindow) {\n item.removeAttribute('inert');\n item.removeAttribute('aria-hidden');\n } else {\n item.setAttribute('inert', 'true');\n item.setAttribute('aria-hidden', 'true');\n }\n });\n\n const nextPageIndexFromVisibleItem = pageItemGroups\n .map((page) => page[0])\n .reduce((resolvedPageIndex, pageStartIndex, pageIndex) => {\n if (pageStartIndex <= safeFirstVisibleItemIndex) {\n return pageIndex;\n }\n\n return resolvedPageIndex;\n }, 0);\n\n const nextPageIndex = hasScrollableOverflow\n ? nextPageIndexFromVisibleItem\n : currentPage;\n\n setCanScrollPrev((previousValue) =>\n previousValue === nextCanScrollPrev ? previousValue : nextCanScrollPrev,\n );\n setCanScrollNext((previousValue) =>\n previousValue === nextCanScrollNext ? previousValue : nextCanScrollNext,\n );\n setFirstVisibleItemIndex((previousValue) => {\n const nextIndex = safeFirstVisibleItemIndex;\n return previousValue === nextIndex ? previousValue : nextIndex;\n });\n setCurrentPage((previousPage) => {\n if (previousPage !== nextPageIndex) {\n setPrevPage(previousPage);\n }\n\n return previousPage === nextPageIndex ? previousPage : nextPageIndex;\n });\n };\n\n const resizeObserver = new ResizeObserver(() => {\n scheduleScrollNavigationUpdate();\n });\n\n const refreshItemElements = () => {\n const nextItemElements = Array.from(\n scroller.querySelectorAll<HTMLElement>('[data-item-index]'),\n );\n itemElementsRef.current = nextItemElements;\n return nextItemElements;\n };\n\n resizeObserver.observe(scroller);\n\n const itemElements = refreshItemElements();\n itemElements.forEach((item) => {\n resizeObserver.observe(item);\n });\n\n const handleImageLoad = () => {\n scheduleScrollNavigationUpdate();\n };\n\n scroller.addEventListener('load', handleImageLoad, true);\n updateScrollNavigation();\n scroller.addEventListener('scroll', updateScrollNavigation, {\n passive: true,\n });\n window.addEventListener('resize', scheduleScrollNavigationUpdate);\n\n return () => {\n if (animationFrameId !== null) {\n window.cancelAnimationFrame(animationFrameId);\n }\n\n resizeObserver.disconnect();\n scroller.removeEventListener('load', handleImageLoad, true);\n scroller.removeEventListener('scroll', updateScrollNavigation);\n window.removeEventListener('resize', scheduleScrollNavigationUpdate);\n itemElementsRef.current = [];\n };\n }, [currentPage, pageItemGroups, safeItemsPerPage, totalPages, totalItems]);\n\n const handleActivePageIndexChange = ({ index }: { index: number }) => {\n setCurrentPage((previousPage) => {\n if (previousPage !== index) {\n setPrevPage(previousPage);\n }\n\n return index;\n });\n };\n\n const getPrevTargetPage = () => {\n if (!canScrollPrev) {\n return currentPage;\n }\n\n if (firstVisibleItemIndex <= 0) {\n return 0;\n }\n\n return Math.max(\n 0,\n Math.ceil(firstVisibleItemIndex / wholeItemsPerPage) - 1,\n );\n };\n\n const getNextTargetPage = () => {\n if (!canScrollNext) {\n return currentPage;\n }\n\n return Math.min(\n totalPages - 1,\n Math.floor(firstVisibleItemIndex / wholeItemsPerPage) + 1,\n );\n };\n\n // Interaction handlers for hover and focus\n const interactionHandlers = showArrowsOnHover\n ? {\n onMouseEnter: () => setIsHovered(true),\n onMouseLeave: () => setIsHovered(false),\n onFocus: () => setIsFocused(true),\n onBlur: (e: React.FocusEvent) => {\n // Only blur if focus is moving outside the carousel\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n setIsFocused(false);\n }\n },\n }\n : {};\n\n // Determine whether arrows should be visible\n // Hide arrows if:\n // 1. There's only one page (all items fit on screen), OR\n // 2. showArrowsOnHover is true and carousel is not hovered/focused\n const shouldShowArrows = totalPages > 1;\n const arrowsVisible =\n shouldShowArrows && (!showArrowsOnHover || isHovered || isFocused);\n\n // Get style object only if we have variables to set\n const cssVars = getCssVariables();\n\n const { focusProps, isFocusVisible } = useFocusRing();\n\n // Custom styles for grid-auto-columns calculation\n const getScrollerStyles = () => {\n const styles: React.CSSProperties = {};\n\n // ALWAYS apply gap if spaceBetweenItems is set, regardless of itemsPerPage\n if (resolvedSpaceBetweenItems >= 0) {\n styles.gap = `${resolvedSpaceBetweenItems}px`;\n }\n\n // Only calculate grid-auto-columns if we have multiple items per page AND spacing\n if (resolvedItemsPerPage > 1 && resolvedSpaceBetweenItems > 0) {\n // Advanced calculation for grid layout to account for gap spacing\n const gapSpacePerItem =\n (resolvedSpaceBetweenItems * (safeItemsPerPage - 1)) / safeItemsPerPage;\n\n styles.gridAutoColumns = `calc(${100 / safeItemsPerPage}% - ${gapSpacePerItem}px)`;\n } else if (resolvedItemsPerPage > 1) {\n // No gap, just divide width evenly\n styles.gridAutoColumns = `${100 / safeItemsPerPage}%`;\n }\n\n return styles;\n };\n\n // Get active item indices for the current page\n const activeItemIndices = getActiveItemIndices();\n\n // Handle carousel keyboard navigation\n const handleCarouselKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n // Navigate to previous slide\n const prevButton = carouselRef.current?.querySelector(\n '[dir=\"prev\"]',\n ) as HTMLButtonElement;\n if (prevButton && !prevButton.disabled) {\n prevButton.click();\n }\n } else if (e.key === 'ArrowRight') {\n e.preventDefault();\n // Navigate to next slide\n const nextButton = carouselRef.current?.querySelector(\n '[dir=\"next\"]',\n ) as HTMLButtonElement;\n if (nextButton && !nextButton.disabled) {\n nextButton.click();\n }\n } else if (e.key === 'Home') {\n e.preventDefault();\n // Go to first slide\n navigateToSlide(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n // Go to last slide\n navigateToSlide(totalPages - 1);\n }\n };\n\n // Generate a meaningful default aria-label if none provided\n const getDefaultAriaLabel = () => {\n if (ariaLabel) return ariaLabel;\n\n const currentSlideNumber = currentPage + 1;\n const autoplayStatus = autoPlay ? ', autoplay enabled' : '';\n\n return `Carousel with ${totalItems} items, currently showing slide ${currentSlideNumber} of ${totalPages}${autoplayStatus}`;\n };\n\n return (\n <div\n className={getCarouselWrapperClasses()}\n ref={carouselRef}\n style={cssVars as React.CSSProperties}\n data-dots-size={dotsSize}\n {...interactionHandlers}\n data-arrows-visible={arrowsVisible ? 'true' : 'false'}\n data-transitioning={isTransitioning ? 'true' : undefined}\n data-scroll-padding={resolvedScrollPadding}\n role=\"region\"\n aria-label={getDefaultAriaLabel()}\n aria-describedby={ariaDescribedby}\n aria-roledescription=\"carousel\"\n >\n <AriaCarousel\n className={`${carousel({ looping })}`}\n loop={loopMode}\n ref={ariaCarouselRef}\n autoplay={autoPlay}\n autoplayInterval={autoPlayInterval}\n mouseDragging={mouseDragging && !isTransitioning}\n onActivePageIndexChange={handleActivePageIndexChange}\n onDragStart={(e) => {\n setIsDragging(true);\n if (e && e.nativeEvent) {\n setDragStartPosition({\n x: e.nativeEvent.clientX,\n y: e.nativeEvent.clientY,\n });\n }\n }}\n onDragEnd={() => {\n // We keep isDragging true for a short time after drag ends\n // This helps ensure click events don't fire immediately after dragging\n if (dragEndResetTimeoutRef.current !== null) {\n window.clearTimeout(dragEndResetTimeoutRef.current);\n }\n\n if (dragEndSyncTimeoutRef.current !== null) {\n window.clearTimeout(dragEndSyncTimeoutRef.current);\n }\n\n dragEndResetTimeoutRef.current = window.setTimeout(() => {\n setIsDragging(false);\n syncCurrentPageFromVisibleItem();\n }, 50);\n\n dragEndSyncTimeoutRef.current = window.setTimeout(() => {\n syncCurrentPageFromVisibleItem();\n }, 150);\n }}\n data-dragging={isDragging ? 'true' : 'false'}\n itemsPerPage={safeItemsPerPage}\n scrollPadding={resolvedScrollPadding}\n data-has-space-between={\n resolvedSpaceBetweenItems > 0 ? 'true' : undefined\n }\n data-items-per-page={\n resolvedItemsPerPage !== 1 ? safeItemsPerPage.toString() : undefined\n }\n >\n <AriaCarouselScroller\n className={`${styles.scroller} ${focusStyleVariants({ focusStyle })} ${scrollerClassName}`.trim()}\n ref={scrollerRef}\n {...focusProps}\n data-focused={isFocusVisible ? true : undefined}\n data-focus-visible={isFocusVisible ? true : undefined}\n data-transitioning={isTransitioning ? 'true' : undefined}\n style={getScrollerStyles()}\n tabIndex={0}\n role=\"region\"\n aria-label=\"Carousel content\"\n onKeyDown={handleCarouselKeyDown}\n >\n {items.map((child, index) => {\n const isActiveItem = activeItemIndices.includes(index);\n const itemClasses = [\n itemWrapperClassName || '',\n styles.item,\n isActiveItem && activeItemClassName ? activeItemClassName : '',\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <AriaCarouselItem\n key={index}\n className={itemClasses}\n data-is-active={isActiveItem ? 'true' : undefined}\n data-item-index={index}\n aria-label={`Slide ${index + 1} of ${totalItems}`}\n >\n {child}\n </AriaCarouselItem>\n );\n })}\n </AriaCarouselScroller>\n\n <div\n className={`${styles.controls} ${showArrowsOnHover ? styles.showOnHover : ''}`}\n >\n <CarouselButton\n dir=\"prev\"\n variant={arrowStyleVariant}\n size={resolvedArrowSize}\n focusStyle={focusStyle}\n iconType={iconType}\n hideDisabledArrow={hideDisabledArrow}\n overrideHideDisabledArrow={loopMode ? undefined : !canScrollPrev}\n onClick={\n loopMode ? undefined : () => navigateToSlide(getPrevTargetPage())\n }\n onPress={() => onArrowPress?.('prev')}\n className={`${getClassNameWithDefault(prevArrowClassName, styles.defaultPrevButton)} ${!arrowsVisible ? styles.hidden : ''}`}\n />\n\n <CarouselButton\n dir=\"next\"\n variant={arrowStyleVariant}\n size={resolvedArrowSize}\n focusStyle={focusStyle}\n iconType={iconType}\n hideDisabledArrow={hideDisabledArrow}\n overrideHideDisabledArrow={loopMode ? undefined : !canScrollNext}\n onClick={\n loopMode ? undefined : () => navigateToSlide(getNextTargetPage())\n }\n onPress={() => onArrowPress?.('next')}\n className={`${getClassNameWithDefault(nextArrowClassName, styles.defaultNextButton)} ${!arrowsVisible ? styles.hidden : ''}`}\n />\n </div>\n\n <div className={styles.hiddenTabs}>\n <AriaCarouselTabs>\n {(page) => (\n <AriaCarouselTab\n key={page.index}\n index={page.index}\n data-index={page.index}\n data-slide-tab=\"true\"\n tabIndex={-1}\n />\n )}\n </AriaCarouselTabs>\n </div>\n\n {autoPlay && (\n <div\n className={`${styles.autoplayControlWrapper} ${getClassNameWithDefault(autoplayControlClassName, styles.defaultAutoplayControl)}`}\n >\n <AutoplayControl\n variant={autoPlayStyleVariant}\n size={autoPlayControlSize}\n className=\"\"\n onPress={onAutoplayPress}\n />\n </div>\n )}\n\n {shouldShowDots && (\n <CarouselDots\n totalItems={totalPages}\n currentPage={currentPage}\n onDotClick={(index) => navigateToSlide(index)}\n onPress={onDotPress}\n focusStyle={focusStyle}\n dotsSize={dotsSize}\n variant={dotsVariant}\n className={getClassNameWithDefault(\n dotsContainerClassName,\n styles.defaultDotsContainer,\n )}\n dotsWrapperClassName={dotsWrapperClassName}\n dotClassName={dotClassName}\n activeDotClassName={activeDotClassName}\n isTransitioning={isTransitioning}\n />\n )}\n </AriaCarousel>\n </div>\n );\n};\n\nexport default Carousel;\n"],"names":["carousel","cva","styles","variants","looping","infinite","backToStart","native","off","Carousel","children","ariaLabel","ariaDescribedby","itemsPerPage","spaceBetweenItems","scrollPadding","iconType","arrowStyleVariant","arrowSize","hideDisabledArrow","showArrowsOnHover","focusStyle","dotsSize","dotsVariant","hideDots","autoPlay","autoPlayInterval","autoPlayStyleVariant","autoPlayControlSize","mouseDragging","carouselWrapperClassName","prevArrowClassName","nextArrowClassName","autoplayControlClassName","dotsContainerClassName","dotsWrapperClassName","dotClassName","activeDotClassName","itemWrapperClassName","scrollerClassName","activeItemClassName","onArrowPress","onDotPress","onAutoplayPress","controlsRef","onNavigationStateChange","items","React","Children","toArray","totalItems","length","breakpoint","useBreakpoint","resolvedItemsPerPage","resolveResponsiveProp","resolvedSpaceBetweenItems","resolvedScrollPadding","resolvedArrowSize","resolvedHideDots","safeItemsPerPage","Math","max","wholeItemsPerPage","floor","pageItemGroups","useMemo","groups","Array","from","_","index","reduce","accumulator","currentGroup","at","push","deficit","fill","splice","unshift","carouselRef","useRef","ariaCarouselRef","dragEndResetTimeoutRef","dragEndSyncTimeoutRef","scrollerRef","itemElementsRef","currentPage","setCurrentPage","useState","prevPage","setPrevPage","canScrollPrev","setCanScrollPrev","canScrollNext","setCanScrollNext","ariaTabsPageCount","setAriaTabsPageCount","firstVisibleItemIndex","setFirstVisibleItemIndex","isDragging","setIsDragging","isHovered","setIsHovered","isFocused","setIsFocused","isTransitioning","dragStartPosition","setDragStartPosition","loopMode","totalPages","shouldShowDots","getClassNameWithDefault","customClassName","defaultClassName","getScrollerElement","useCallback","current","querySelector","scrollToPage","scroller","pageIndex","targetItemIndex","targetItem","scrollerRect","getBoundingClientRect","itemRect","targetScrollLeft","scrollLeft","left","scrollTo","behavior","navigateToSlide","targetTab","click","syncCurrentPageFromVisibleItem","itemElements","querySelectorAll","scrollerLeft","closestItemIndex","closestDistance","Number","POSITIVE_INFINITY","forEach","element","parseInt","dataset","itemIndex","isNaN","distance","abs","itemsPerPageWhole","nextPage","min","navigateToPreviousSlide","targetPage","scrollBy","clientWidth","window","setTimeout","navigateToNextSlide","useEffect","navigationState","canGoPrev","canGoNext","toNavigationState","prev","next","goToSlide","clearTimeout","preventLinkActivation","e","target","Element","closest","preventDefault","stopPropagation","handleMouseDown","x","clientX","y","clientY","handleMouseMove","dx","dy","sqrt","handleMouseUp","carouselElement","addEventListener","removeEventListener","HTMLElement","classList","remove","slideLeft","add","slideRight","animationFrameId","scheduleAriaPageCountUpdate","requestAnimationFrame","nextCount","previousCount","updateAriaPageCount","observer","MutationObserver","observe","childList","subtree","attributes","attributeFilter","cancelAnimationFrame","disconnect","scheduleScrollNavigationUpdate","updateScrollNavigation","maxScrollLeft","scrollWidth","hasScrollableOverflow","nextCanScrollPrev","nextCanScrollNext","viewportStart","nextFirstVisibleItemIndex","find","item","offsetLeft","offsetWidth","parsedFirstVisibleItemIndex","safeFirstVisibleItemIndex","visibleItemCount","ceil","visibleWindowEndIndex","itemIndexString","removeAttribute","setAttribute","nextPageIndexFromVisibleItem","map","page","resolvedPageIndex","pageStartIndex","nextPageIndex","previousValue","previousPage","resizeObserver","ResizeObserver","nextItemElements","refreshItemElements","handleImageLoad","passive","interactionHandlers","onMouseEnter","onMouseLeave","onFocus","onBlur","currentTarget","contains","relatedTarget","arrowsVisible","cssVars","String","Object","keys","getCssVariables","focusProps","isFocusVisible","useFocusRing","activeItemIndices","jsx","className","classes","carouselWrapper","isInteger","showPartialItems","hasScrollPadding","trim","defaultCarouselWrapper","join","getCarouselWrapperClasses","ref","style","role","getDefaultAriaLabel","jsxs","AriaCarousel","loop","autoplay","autoplayInterval","onActivePageIndexChange","onDragStart","nativeEvent","onDragEnd","toString","AriaCarouselScroller","focusStyleVariants","gap","gapSpacePerItem","gridAutoColumns","getScrollerStyles","tabIndex","onKeyDown","key","prevButton","disabled","nextButton","child","isActiveItem","includes","itemClasses","filter","Boolean","AriaCarouselItem","controls","showOnHover","CarouselButton","dir","variant","size","overrideHideDisabledArrow","onClick","onPress","defaultPrevButton","hidden","defaultNextButton","hiddenTabs","AriaCarouselTabs","AriaCarouselTab","autoplayControlWrapper","defaultAutoplayControl","AutoplayControl","CarouselDots","onDotClick","defaultDotsContainer"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Carousel.js","sources":["../../../src/components/Carousel/Carousel.tsx"],"sourcesContent":["import React, { useState, useRef, useEffect, useCallback } from 'react';\nimport { cva } from 'class-variance-authority';\nimport {\n Carousel as AriaCarousel,\n CarouselScroller as AriaCarouselScroller,\n CarouselItem as AriaCarouselItem,\n CarouselTabs as AriaCarouselTabs,\n CarouselTab as AriaCarouselTab,\n} from 'react-aria-carousel';\nimport styles from './Carousel.module.css';\nimport { CarouselButton } from './CarouselButton/CarouselButton';\nimport { CarouselDots } from './CarouselDots/CarouselDots';\nimport { AutoplayControl } from './AutoplayControl/AutoplayControl';\nimport { focusStyleVariants } from '../../utils/focus/focusStyles';\nimport { useFocusRing } from '@react-aria/focus';\n\nimport {\n WithResponsive,\n resolveResponsiveProp,\n} from '../../utils/breakpoint/responsive';\nimport { useBreakpoint } from '../../utils/breakpoint/hooks/useBreakpoint';\n\nconst carousel = cva(styles.carousel, {\n variants: {\n looping: {\n infinite: styles.infinite,\n backToStart: styles.native,\n off: undefined,\n },\n },\n});\n\nexport interface CarouselBaseProps {\n /**\n * Content to be displayed in the carousel\n */\n children?: React.ReactNode | React.ReactNode[];\n\n /**\n * Accessible name for the carousel\n * Describes the purpose or content of the carousel for screen readers\n * @example \"Featured products\" or \"Customer testimonials\"\n */\n 'aria-label'?: string;\n\n /**\n * ID of an element that provides additional description for the carousel\n * Can reference an element that gives more context about the carousel's content\n * @example \"carousel-description\"\n */\n 'aria-describedby'?: string;\n\n /**\n * Looping behavior\n * - 'infinite': Carousel loops continuously\n * - 'backToStart': Carousel loops back to start after reaching end\n * - 'off': Looping is disabled\n * @default 'off'\n */\n looping?: 'infinite' | 'backToStart' | 'off';\n\n /**\n * Number of items to display per page (minimum: 1)\n * Decimal values (e.g., 2.25) can be used to show partial items\n * @default 1\n * @min 1\n */\n itemsPerPage?: number;\n\n /**\n * Space between carousel items in pixels\n * @default 0\n */\n spaceBetweenItems?: number;\n\n /**\n * Amount of padding added to the scroll container to create a \"peek\" effect.\n * This shows a portion of the adjacent items.\n * Accepts any valid CSS dimension (px, rem, em, %) - e.g., \"15%\", \"20px\", \"2rem\"\n * @example \"15%\" or \"20px\" or \"2rem\"\n * @default undefined\n */\n scrollPadding?: string;\n\n /* Button and Controls Props */\n /**\n * Icon type for navigation buttons\n * @default 'chevron'\n */\n iconType?: 'chevron';\n\n /**\n * Style of carousel arrow buttons\n * @default 'neutral'\n */\n arrowStyleVariant?:\n | 'neutral'\n | 'white'\n | 'shapeFlat'\n | 'shapeElevated'\n | 'gradient';\n\n /**\n * Size of navigation buttons\n * @default 'md'\n */\n arrowSize?: 'sm' | 'md' | 'lg';\n\n /**\n * Whether to hide navigation buttons when disabled\n * Note: Navigation buttons are automatically hidden when all items fit on one page\n * @default false\n */\n hideDisabledArrow?: boolean;\n\n /**\n * Whether to show arrows only when hovering over the carousel or when it has focus\n * @default false\n */\n showArrowsOnHover?: boolean;\n\n /**\n * Focus style for interactive elements\n * @default 'default'\n */\n focusStyle?: 'default' | 'white';\n\n /* Dots/Tabs Props */\n /**\n * Size of pagination dots\n * @default 'md'\n */\n dotsSize?: 'md' | 'lg';\n\n /**\n * Visual style of pagination dots\n * @default 'standard'\n */\n dotsVariant?: 'standard' | 'transparent';\n\n /**\n * Whether to hide pagination dots\n * Note: Dots are automatically hidden when there's only one page\n * @default false\n */\n hideDots?: boolean;\n\n /* Autoplay Props */\n /**\n * Whether the autoplay is enabled\n * @default false\n */\n autoPlay?: boolean;\n\n /**\n * Interval in milliseconds between slides during autoplay\n * @default 2000\n */\n autoPlayInterval?: number;\n\n /**\n * Style variant for autoplay control button\n * @default 'neutralVibrant'\n */\n autoPlayStyleVariant?:\n | 'neutralVibrant'\n | 'neutralSubtle'\n | 'whiteVibrant'\n | 'whiteSubtle';\n\n /**\n * Size of autoplay control button\n * @default 'md'\n */\n autoPlayControlSize?: 'sm' | 'md';\n\n /**\n * Whether to enable mouse dragging for the carousel\n * @default true\n */\n mouseDragging?: boolean;\n\n /* Custom Class Names for Positioning and Styling */\n /**\n * Class name for the outermost wrapper of the carousel\n */\n carouselWrapperClassName?: string;\n\n /**\n * Class name for the previous button\n * Use this to position the previous button\n */\n prevArrowClassName?: string;\n\n /**\n * Class name for the next button\n * Use this to position the next button\n */\n nextArrowClassName?: string;\n\n /**\n * Class name for the autoplay control\n * Use this to position the autoplay button\n */\n autoplayControlClassName?: string;\n\n /**\n * Class name for the dots container\n * Use this to position the pagination dots\n */\n dotsContainerClassName?: string;\n\n /**\n * Class name for the dots wrapper\n * Use this to style the dots wrapper\n */\n dotsWrapperClassName?: string;\n\n /**\n * Class name for individual dots\n */\n dotClassName?: string;\n\n /**\n * Class name for the active dot\n */\n activeDotClassName?: string;\n\n /**\n * Class name for individual carousel item wrappers\n * Use this to style the wrapper div around each carousel item\n */\n itemWrapperClassName?: string;\n\n /**\n * Class name for the scroll container\n * Use this to customize the grid/scroll layout of carousel items\n */\n scrollerClassName?: string;\n\n /**\n * Class name for the currently active/visible carousel item\n * This class is applied to the item that corresponds to the current page\n */\n activeItemClassName?: string;\n\n /**\n * Callback fired when a carousel navigation button (prev/next) is pressed\n */\n onArrowPress?: (direction: 'prev' | 'next') => void;\n\n /**\n * Callback fired when a dot is pressed\n */\n onDotPress?: (index: number) => void;\n\n /**\n * Callback fired when the autoplay button is pressed\n */\n onAutoplayPress?: (state: 'play' | 'pause') => void;\n\n /**\n * Imperative controls ref for driving carousel navigation from external UI.\n */\n controlsRef?: React.MutableRefObject<CarouselControls | null>;\n\n /**\n * Callback fired when carousel navigation state changes.\n */\n onNavigationStateChange?: (state: CarouselNavigationState) => void;\n}\n\nexport interface CarouselControls {\n prev: () => void;\n next: () => void;\n goToSlide: (index: number) => void;\n canGoPrev: boolean;\n canGoNext: boolean;\n currentPage: number;\n totalPages: number;\n}\n\nexport interface CarouselNavigationState {\n canGoPrev: boolean;\n canGoNext: boolean;\n currentPage: number;\n totalPages: number;\n}\n\nconst toNavigationState = (\n canGoPrev: boolean,\n canGoNext: boolean,\n currentPage: number,\n totalPages: number,\n): CarouselNavigationState => ({\n canGoPrev,\n canGoNext,\n currentPage,\n totalPages,\n});\n\ntype ResponsiveKeys =\n | 'itemsPerPage'\n | 'spaceBetweenItems'\n | 'scrollPadding'\n | 'showArrowsOnHover'\n | 'arrowSize'\n | 'hideDots';\n\nexport type CarouselProps = WithResponsive<CarouselBaseProps, ResponsiveKeys>;\n\nexport const Carousel = ({\n children = [],\n 'aria-label': ariaLabel,\n 'aria-describedby': ariaDescribedby,\n looping = 'off',\n itemsPerPage = 1,\n spaceBetweenItems = 0,\n scrollPadding,\n\n // Button and Controls Props\n iconType = 'chevron',\n arrowStyleVariant = 'neutral',\n arrowSize = 'md',\n hideDisabledArrow = false,\n showArrowsOnHover = false,\n focusStyle = 'default',\n\n // Dots/Tabs Props\n dotsSize = 'md',\n dotsVariant = 'standard',\n hideDots = false,\n\n // Autoplay Props\n autoPlay = false,\n autoPlayInterval = 2000,\n autoPlayStyleVariant = 'neutralVibrant',\n autoPlayControlSize = 'md',\n\n // Mouse Dragging\n mouseDragging = true,\n\n // Custom Class Names\n carouselWrapperClassName = '',\n prevArrowClassName = '',\n nextArrowClassName = '',\n autoplayControlClassName = '',\n dotsContainerClassName = '',\n dotsWrapperClassName = '',\n dotClassName = '',\n activeDotClassName = '',\n itemWrapperClassName = '',\n scrollerClassName = '',\n activeItemClassName = '',\n\n // Event Callbacks\n onArrowPress,\n onDotPress,\n onAutoplayPress,\n controlsRef,\n onNavigationStateChange,\n}: CarouselProps) => {\n // Map looping values to expected loop values for AriaCarousel\n const getLoopValue = () => {\n if (looping === 'infinite') return 'infinite';\n if (looping === 'backToStart') return 'native';\n if (looping === 'off') return undefined;\n return looping; // other values pass through\n };\n\n const items = React.Children.toArray(children);\n const totalItems = items.length;\n\n const breakpoint = useBreakpoint();\n const resolvedItemsPerPage =\n resolveResponsiveProp(itemsPerPage, breakpoint) ?? 1;\n const resolvedSpaceBetweenItems =\n resolveResponsiveProp(spaceBetweenItems, breakpoint) ?? 0;\n const resolvedScrollPadding =\n resolveResponsiveProp(scrollPadding, breakpoint) ?? undefined;\n const resolvedArrowSize =\n resolveResponsiveProp(arrowSize, breakpoint) ?? 'md';\n const resolvedHideDots = resolveResponsiveProp(hideDots, breakpoint) ?? false;\n\n // Ensure itemsPerPage is at least 1\n // We allow decimal values for showing partial items\n const safeItemsPerPage = Math.max(1, resolvedItemsPerPage);\n const wholeItemsPerPage = Math.max(1, Math.floor(safeItemsPerPage));\n\n const pageItemGroups = React.useMemo(() => {\n const groups = Array.from(\n { length: totalItems },\n (_, index) => index,\n ).reduce<number[][]>((accumulator, index) => {\n const currentGroup = accumulator.at(-1);\n\n if (currentGroup && currentGroup.length < wholeItemsPerPage) {\n currentGroup.push(index);\n } else {\n accumulator.push([index]);\n }\n\n return accumulator;\n }, []);\n\n if (groups.length >= 2) {\n const deficit = wholeItemsPerPage - groups.at(-1)!.length;\n\n if (deficit > 0) {\n const fill = [...groups.at(-2)!].splice(wholeItemsPerPage - deficit);\n groups.at(-1)!.unshift(...fill);\n }\n }\n\n return groups;\n }, [totalItems, wholeItemsPerPage]);\n\n const carouselRef = useRef<HTMLDivElement>(null);\n const ariaCarouselRef = useRef<HTMLDivElement>(null);\n const dragEndResetTimeoutRef = useRef<number | null>(null);\n const dragEndSyncTimeoutRef = useRef<number | null>(null);\n const scrollerRef = useRef<HTMLDivElement>(null);\n const itemElementsRef = useRef<HTMLElement[]>([]);\n\n const [currentPage, setCurrentPage] = useState(0);\n const [prevPage, setPrevPage] = useState(0);\n const [canScrollPrev, setCanScrollPrev] = useState(false);\n const [canScrollNext, setCanScrollNext] = useState(pageItemGroups.length > 1);\n const [ariaTabsPageCount, setAriaTabsPageCount] = useState(0);\n const [firstVisibleItemIndex, setFirstVisibleItemIndex] = useState(0);\n const [isDragging, setIsDragging] = useState(false);\n const [isHovered, setIsHovered] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n const [isTransitioning] = useState(false);\n\n // Add state variables for drag/link handling\n const [dragStartPosition, setDragStartPosition] = useState<{\n x: number;\n y: number;\n } | null>(null);\n const dragThreshold = 5; // Pixels of movement needed to consider as a drag\n const loopMode = getLoopValue();\n const totalPages =\n ariaTabsPageCount > 0 ? ariaTabsPageCount : pageItemGroups.length;\n\n // Determine if dots should be shown\n // Hide dots if:\n // 1. resolvedHideDots prop is true (responsive), OR\n // 2. There's only one page (totalPages <= 1)\n const shouldShowDots = !resolvedHideDots && totalPages > 1;\n\n // Helper function to get class names with defaults\n const getClassNameWithDefault = (\n customClassName: string,\n defaultClassName: string,\n ): string => {\n return customClassName || defaultClassName;\n };\n\n // Get CSS classes based on showing partial items\n const getCarouselWrapperClasses = () => {\n const classes = [styles.carouselWrapper];\n\n if (!Number.isInteger(safeItemsPerPage)) {\n classes.push(styles.showPartialItems);\n }\n\n if (resolvedScrollPadding) {\n classes.push(styles.hasScrollPadding);\n }\n\n // Apply default wrapper class if no custom wrapper class is provided\n if (carouselWrapperClassName && carouselWrapperClassName.trim()) {\n classes.push(carouselWrapperClassName);\n } else {\n classes.push(styles.defaultCarouselWrapper);\n }\n\n return classes.join(' ');\n };\n\n // Determine if we need to set CSS variables and return only what's needed\n const getCssVariables = () => {\n const cssVars: Record<string, string> = {};\n\n // Only set these if they differ from defaults\n if (resolvedItemsPerPage !== 1) {\n cssVars['--items-per-page'] = String(safeItemsPerPage);\n }\n\n if (resolvedSpaceBetweenItems > 0) {\n cssVars['--space-between-items'] = `${resolvedSpaceBetweenItems}px`;\n }\n\n if (resolvedScrollPadding) {\n cssVars['--scroll-padding'] = resolvedScrollPadding;\n }\n\n return Object.keys(cssVars).length > 0 ? cssVars : undefined;\n };\n\n // Helper function to determine which items are currently active/visible\n const getActiveItemIndices = () => {\n return pageItemGroups[currentPage] ?? [];\n };\n\n const getScrollerElement = useCallback(() => {\n return carouselRef.current?.querySelector(\n '[data-carousel-scroller=\"true\"]',\n ) as HTMLElement | null;\n }, []);\n\n const scrollToPage = useCallback(\n (scroller: HTMLElement, pageIndex: number) => {\n const itemsPerPageWhole = Math.max(1, Math.floor(safeItemsPerPage));\n const targetItemIndex = pageIndex * itemsPerPageWhole;\n const targetItem = scroller.querySelector(\n `[data-item-index=\"${targetItemIndex}\"]`,\n ) as HTMLElement | null;\n\n if (!targetItem) {\n return false;\n }\n\n const scrollerRect = scroller.getBoundingClientRect();\n const itemRect = targetItem.getBoundingClientRect();\n const scrollerPaddingLeft =\n parseFloat(getComputedStyle(scroller).paddingLeft) || 0;\n const maxScrollLeft = Math.max(\n 0,\n scroller.scrollWidth - scroller.clientWidth,\n );\n const targetScrollLeft =\n scroller.scrollLeft +\n (itemRect.left - scrollerRect.left) -\n scrollerPaddingLeft;\n const clampedTargetScrollLeft = Math.min(\n Math.max(0, targetScrollLeft),\n maxScrollLeft,\n );\n\n scroller.scrollTo({\n left: clampedTargetScrollLeft,\n behavior: 'smooth',\n });\n\n return true;\n },\n [safeItemsPerPage],\n );\n\n const navigateToSlide = useCallback(\n (index: number) => {\n if (index === currentPage || index < 0 || index >= totalPages) {\n return;\n }\n\n // Find and click the corresponding hidden tab\n if (carouselRef.current) {\n const targetTab = carouselRef.current.querySelector(\n `[data-slide-tab=\"true\"][data-index=\"${index}\"]`,\n ) as HTMLElement;\n\n if (targetTab) {\n targetTab.click();\n setPrevPage(currentPage);\n setCurrentPage(index);\n }\n }\n },\n [currentPage, totalPages],\n );\n\n const syncCurrentPageFromVisibleItem = useCallback(() => {\n if (!carouselRef.current || totalPages <= 1) {\n return;\n }\n\n const scroller = carouselRef.current.querySelector(\n '[data-carousel-scroller=\"true\"]',\n ) as HTMLElement | null;\n\n if (!scroller) {\n return;\n }\n\n const itemElements = Array.from(\n scroller.querySelectorAll('[data-item-index]'),\n ) as HTMLElement[];\n\n if (!itemElements.length) {\n return;\n }\n\n const scrollerLeft = scroller.getBoundingClientRect().left;\n let closestItemIndex = 0;\n let closestDistance = Number.POSITIVE_INFINITY;\n\n itemElements.forEach((element) => {\n const index = parseInt(element.dataset.itemIndex ?? '', 10);\n\n if (Number.isNaN(index)) {\n return;\n }\n\n const distance = Math.abs(\n element.getBoundingClientRect().left - scrollerLeft,\n );\n\n if (distance < closestDistance) {\n closestDistance = distance;\n closestItemIndex = index;\n }\n });\n\n const itemsPerPageWhole = Math.max(1, Math.floor(safeItemsPerPage));\n const nextPage = Math.min(\n totalPages - 1,\n Math.floor(closestItemIndex / itemsPerPageWhole),\n );\n\n if (nextPage !== currentPage) {\n setPrevPage(currentPage);\n setCurrentPage(nextPage);\n }\n }, [currentPage, safeItemsPerPage, totalPages]);\n\n const navigateToPreviousSlide = useCallback(() => {\n const scroller = getScrollerElement();\n\n if (scroller) {\n const targetPage = Math.max(0, currentPage - 1);\n const didScrollToItemGroup = scrollToPage(scroller, targetPage);\n\n if (!didScrollToItemGroup) {\n scroller.scrollBy({\n left: -scroller.clientWidth,\n behavior: 'smooth',\n });\n }\n\n setPrevPage(currentPage);\n setCurrentPage(targetPage);\n\n window.setTimeout(() => {\n syncCurrentPageFromVisibleItem();\n }, 200);\n return;\n }\n\n if (currentPage <= 0) {\n return;\n }\n\n navigateToSlide(currentPage - 1);\n }, [\n currentPage,\n getScrollerElement,\n navigateToSlide,\n scrollToPage,\n syncCurrentPageFromVisibleItem,\n ]);\n\n const navigateToNextSlide = useCallback(() => {\n const scroller = getScrollerElement();\n\n if (scroller) {\n const targetPage = Math.min(totalPages - 1, currentPage + 1);\n const didScrollToItemGroup = scrollToPage(scroller, targetPage);\n\n if (!didScrollToItemGroup) {\n scroller.scrollBy({\n left: scroller.clientWidth,\n behavior: 'smooth',\n });\n }\n\n setPrevPage(currentPage);\n setCurrentPage(targetPage);\n\n window.setTimeout(() => {\n syncCurrentPageFromVisibleItem();\n }, 200);\n return;\n }\n\n if (currentPage >= totalPages - 1) {\n return;\n }\n\n navigateToSlide(currentPage + 1);\n }, [\n currentPage,\n getScrollerElement,\n navigateToSlide,\n scrollToPage,\n syncCurrentPageFromVisibleItem,\n totalPages,\n ]);\n\n useEffect(() => {\n const nextCanGoPrev = canScrollPrev || currentPage > 0;\n const nextCanGoNext = canScrollNext || currentPage < totalPages - 1;\n\n const navigationState = toNavigationState(\n nextCanGoPrev,\n nextCanGoNext,\n currentPage,\n totalPages,\n );\n\n if (!controlsRef) {\n onNavigationStateChange?.(navigationState);\n return;\n }\n\n controlsRef.current = {\n prev: navigateToPreviousSlide,\n next: navigateToNextSlide,\n goToSlide: navigateToSlide,\n ...navigationState,\n };\n\n onNavigationStateChange?.(navigationState);\n\n return () => {\n if (controlsRef.current) {\n controlsRef.current = null;\n }\n };\n }, [\n controlsRef,\n canScrollNext,\n canScrollPrev,\n currentPage,\n navigateToNextSlide,\n navigateToPreviousSlide,\n navigateToSlide,\n onNavigationStateChange,\n totalPages,\n ]);\n\n useEffect(() => {\n return () => {\n if (dragEndResetTimeoutRef.current !== null) {\n window.clearTimeout(dragEndResetTimeoutRef.current);\n }\n\n if (dragEndSyncTimeoutRef.current !== null) {\n window.clearTimeout(dragEndSyncTimeoutRef.current);\n }\n };\n }, []);\n\n // Add effect to handle link click prevention during drag\n useEffect(() => {\n if (!carouselRef.current || !mouseDragging) return;\n\n // Function to prevent link activation during/after drag\n const preventLinkActivation = (e: MouseEvent) => {\n if (!isDragging) return;\n\n const target = e.target;\n if (!(target instanceof Element)) return;\n\n const link = target.closest('a[href]');\n if (!link) return;\n\n e.preventDefault();\n e.stopPropagation();\n\n // Reset isDragging after the current event cycle completes\n setTimeout(() => {\n setIsDragging(false);\n }, 0);\n };\n\n // Function to handle mouse down events\n const handleMouseDown = (e: MouseEvent) => {\n setDragStartPosition({ x: e.clientX, y: e.clientY });\n };\n\n // Function to handle mouse move events\n const handleMouseMove = (e: MouseEvent) => {\n if (dragStartPosition === null) return;\n\n // Calculate the distance moved\n const dx = Math.abs(e.clientX - dragStartPosition.x);\n const dy = Math.abs(e.clientY - dragStartPosition.y);\n const distance = Math.sqrt(dx * dx + dy * dy);\n\n // If the user has moved more than the threshold, consider it a drag\n if (distance > dragThreshold) {\n setIsDragging(true);\n }\n };\n\n // Function to handle mouse up events\n const handleMouseUp = () => {\n // If this wasn't a drag, don't interfere with normal clicks\n if (!isDragging) {\n setDragStartPosition(null);\n return;\n }\n\n // Add a brief delay for the click prevention to work\n setTimeout(() => {\n setIsDragging(false);\n setDragStartPosition(null);\n }, 50);\n };\n\n // Add event listeners for the carousel container\n const carouselElement = carouselRef.current;\n\n // Capture phase is important to catch events before they reach the links\n carouselElement.addEventListener('mousedown', handleMouseDown, true);\n carouselElement.addEventListener('mousemove', handleMouseMove, true);\n carouselElement.addEventListener('mouseup', handleMouseUp, true);\n\n // This is the key event listener that prevents link activation\n carouselElement.addEventListener('click', preventLinkActivation, true);\n\n return () => {\n // Clean up event listeners\n carouselElement.removeEventListener('mousedown', handleMouseDown, true);\n carouselElement.removeEventListener('mousemove', handleMouseMove, true);\n carouselElement.removeEventListener('mouseup', handleMouseUp, true);\n carouselElement.removeEventListener('click', preventLinkActivation, true);\n };\n }, [mouseDragging, isDragging, dragStartPosition, dragThreshold]);\n\n // Handle transition direction\n useEffect(() => {\n if (carouselRef.current) {\n const scroller = carouselRef.current.querySelector(`.${styles.scroller}`);\n if (scroller && scroller instanceof HTMLElement) {\n if (prevPage < currentPage) {\n scroller.classList.remove(styles.slideLeft);\n scroller.classList.add(styles.slideRight);\n } else if (prevPage > currentPage) {\n scroller.classList.remove(styles.slideRight);\n scroller.classList.add(styles.slideLeft);\n }\n }\n }\n }, [currentPage, prevPage]);\n\n // Keep total page count aligned with the pages emitted by react-aria-carousel.\n useEffect(() => {\n const carouselElement = carouselRef.current;\n if (!carouselElement) return;\n\n let animationFrameId: number | null = null;\n\n const updateAriaPageCount = () => {\n const nextCount = carouselElement.querySelectorAll(\n '[data-slide-tab=\"true\"]',\n ).length;\n\n setAriaTabsPageCount((previousCount) =>\n previousCount === nextCount ? previousCount : nextCount,\n );\n };\n\n const scheduleAriaPageCountUpdate = () => {\n if (animationFrameId !== null) {\n return;\n }\n\n animationFrameId = window.requestAnimationFrame(() => {\n animationFrameId = null;\n updateAriaPageCount();\n });\n };\n\n const observer = new MutationObserver(() => {\n scheduleAriaPageCountUpdate();\n });\n\n observer.observe(carouselElement, {\n childList: true,\n subtree: true,\n attributes: true,\n attributeFilter: ['data-slide-tab'],\n });\n\n scheduleAriaPageCountUpdate();\n\n return () => {\n if (animationFrameId !== null) {\n window.cancelAnimationFrame(animationFrameId);\n }\n\n observer.disconnect();\n };\n }, [totalItems, safeItemsPerPage]);\n\n // Track the actual scroll position so arrow state matches mouse dragging,\n // even when the underlying library still considers the first page active.\n useEffect(() => {\n const scroller =\n (getScrollerElement() as HTMLDivElement | null) ?? scrollerRef.current;\n if (!scroller) return;\n\n let animationFrameId: number | null = null;\n\n const scheduleScrollNavigationUpdate = () => {\n if (animationFrameId !== null) {\n return;\n }\n\n animationFrameId = window.requestAnimationFrame(() => {\n animationFrameId = null;\n updateScrollNavigation();\n });\n };\n\n const updateScrollNavigation = () => {\n const maxScrollLeft = Math.max(\n 0,\n scroller.scrollWidth - scroller.clientWidth,\n );\n const scrollLeft = scroller.scrollLeft;\n // Mouse wheel / trackpad scrolling can move by sub-pixel amounts,\n // especially when scroll padding and smooth scrolling are enabled.\n const epsilon = 0.01;\n const hasScrollableOverflow = maxScrollLeft > epsilon;\n const nextCanScrollPrev = hasScrollableOverflow\n ? scrollLeft > epsilon\n : currentPage > 0;\n const nextCanScrollNext = hasScrollableOverflow\n ? scrollLeft < maxScrollLeft - epsilon\n : currentPage < totalPages - 1;\n\n const itemElements =\n itemElementsRef.current.length > 0\n ? itemElementsRef.current\n : Array.from(\n scroller.querySelectorAll<HTMLElement>('[data-item-index]'),\n );\n\n if (itemElementsRef.current.length === 0) {\n itemElementsRef.current = itemElements;\n }\n\n // react-aria-carousel applies paddingInline=scrollPadding to the scroller,\n // so include computed padding-left to align visibility math correctly.\n const scrollerPaddingLeft =\n parseFloat(getComputedStyle(scroller).paddingLeft) || 0;\n const viewportStart = scrollLeft + scrollerPaddingLeft + epsilon;\n const nextFirstVisibleItemIndex =\n itemElements.find((item) => {\n const itemEnd = item.offsetLeft + item.offsetWidth;\n return itemEnd > viewportStart;\n })?.dataset.itemIndex ?? '0';\n\n const parsedFirstVisibleItemIndex = parseInt(\n nextFirstVisibleItemIndex,\n 10,\n );\n const safeFirstVisibleItemIndex = Number.isNaN(\n parsedFirstVisibleItemIndex,\n )\n ? 0\n : parsedFirstVisibleItemIndex;\n\n // Keep the currently visible item window interactive even before\n // the underlying carousel page index snaps to the next page start.\n const visibleItemCount = Math.max(1, Math.ceil(safeItemsPerPage));\n const visibleWindowEndIndex =\n safeFirstVisibleItemIndex + visibleItemCount - 1;\n\n itemElements.forEach((item) => {\n const itemIndexString = item.dataset.itemIndex;\n if (typeof itemIndexString !== 'string') return;\n\n const itemIndex = parseInt(itemIndexString, 10);\n if (Number.isNaN(itemIndex)) return;\n\n const isInVisibleWindow =\n itemIndex >= safeFirstVisibleItemIndex &&\n itemIndex <= visibleWindowEndIndex;\n\n if (isInVisibleWindow) {\n item.removeAttribute('inert');\n item.removeAttribute('aria-hidden');\n } else {\n item.setAttribute('inert', 'true');\n item.setAttribute('aria-hidden', 'true');\n }\n });\n\n const nextPageIndexFromVisibleItem = pageItemGroups\n .map((page) => page[0])\n .reduce((resolvedPageIndex, pageStartIndex, pageIndex) => {\n if (pageStartIndex <= safeFirstVisibleItemIndex) {\n return pageIndex;\n }\n\n return resolvedPageIndex;\n }, 0);\n\n const nextPageIndex = hasScrollableOverflow\n ? nextPageIndexFromVisibleItem\n : currentPage;\n\n setCanScrollPrev((previousValue) =>\n previousValue === nextCanScrollPrev ? previousValue : nextCanScrollPrev,\n );\n setCanScrollNext((previousValue) =>\n previousValue === nextCanScrollNext ? previousValue : nextCanScrollNext,\n );\n setFirstVisibleItemIndex((previousValue) => {\n const nextIndex = safeFirstVisibleItemIndex;\n return previousValue === nextIndex ? previousValue : nextIndex;\n });\n setCurrentPage((previousPage) => {\n if (previousPage !== nextPageIndex) {\n setPrevPage(previousPage);\n }\n\n return previousPage === nextPageIndex ? previousPage : nextPageIndex;\n });\n };\n\n const resizeObserver = new ResizeObserver(() => {\n scheduleScrollNavigationUpdate();\n });\n\n const refreshItemElements = () => {\n const nextItemElements = Array.from(\n scroller.querySelectorAll<HTMLElement>('[data-item-index]'),\n );\n itemElementsRef.current = nextItemElements;\n return nextItemElements;\n };\n\n resizeObserver.observe(scroller);\n\n const itemElements = refreshItemElements();\n itemElements.forEach((item) => {\n resizeObserver.observe(item);\n });\n\n const handleImageLoad = () => {\n scheduleScrollNavigationUpdate();\n };\n\n scroller.addEventListener('load', handleImageLoad, true);\n updateScrollNavigation();\n scroller.addEventListener('scroll', updateScrollNavigation, {\n passive: true,\n });\n window.addEventListener('resize', scheduleScrollNavigationUpdate);\n\n return () => {\n if (animationFrameId !== null) {\n window.cancelAnimationFrame(animationFrameId);\n }\n\n resizeObserver.disconnect();\n scroller.removeEventListener('load', handleImageLoad, true);\n scroller.removeEventListener('scroll', updateScrollNavigation);\n window.removeEventListener('resize', scheduleScrollNavigationUpdate);\n itemElementsRef.current = [];\n };\n }, [\n currentPage,\n getScrollerElement,\n pageItemGroups,\n safeItemsPerPage,\n totalPages,\n totalItems,\n ]);\n\n const handleActivePageIndexChange = ({ index }: { index: number }) => {\n setCurrentPage((previousPage) => {\n if (previousPage !== index) {\n setPrevPage(previousPage);\n }\n\n return index;\n });\n };\n\n const getPrevTargetPage = () => {\n if (!canScrollPrev) {\n return currentPage;\n }\n\n if (firstVisibleItemIndex <= 0) {\n return 0;\n }\n\n return Math.max(\n 0,\n Math.ceil(firstVisibleItemIndex / wholeItemsPerPage) - 1,\n );\n };\n\n const getNextTargetPage = () => {\n if (!canScrollNext) {\n return currentPage;\n }\n\n return Math.min(\n totalPages - 1,\n Math.floor(firstVisibleItemIndex / wholeItemsPerPage) + 1,\n );\n };\n\n // Interaction handlers for hover and focus\n const interactionHandlers = showArrowsOnHover\n ? {\n onMouseEnter: () => setIsHovered(true),\n onMouseLeave: () => setIsHovered(false),\n onFocus: () => setIsFocused(true),\n onBlur: (e: React.FocusEvent) => {\n // Only blur if focus is moving outside the carousel\n if (!e.currentTarget.contains(e.relatedTarget as Node)) {\n setIsFocused(false);\n }\n },\n }\n : {};\n\n // Determine whether arrows should be visible\n // Hide arrows if:\n // 1. There's only one page (all items fit on screen), OR\n // 2. showArrowsOnHover is true and carousel is not hovered/focused\n const shouldShowArrows = totalPages > 1;\n const arrowsVisible =\n shouldShowArrows && (!showArrowsOnHover || isHovered || isFocused);\n\n // Get style object only if we have variables to set\n const cssVars = getCssVariables();\n\n const { focusProps, isFocusVisible } = useFocusRing();\n\n // Custom styles for grid-auto-columns calculation\n const getScrollerStyles = () => {\n const styles: React.CSSProperties = {};\n\n // ALWAYS apply gap if spaceBetweenItems is set, regardless of itemsPerPage\n if (resolvedSpaceBetweenItems >= 0) {\n styles.gap = `${resolvedSpaceBetweenItems}px`;\n }\n\n // Only calculate grid-auto-columns if we have multiple items per page AND spacing\n if (resolvedItemsPerPage > 1 && resolvedSpaceBetweenItems > 0) {\n // Advanced calculation for grid layout to account for gap spacing\n const gapSpacePerItem =\n (resolvedSpaceBetweenItems * (safeItemsPerPage - 1)) / safeItemsPerPage;\n\n styles.gridAutoColumns = `calc(${100 / safeItemsPerPage}% - ${gapSpacePerItem}px)`;\n } else if (resolvedItemsPerPage > 1) {\n // No gap, just divide width evenly\n styles.gridAutoColumns = `${100 / safeItemsPerPage}%`;\n }\n\n return styles;\n };\n\n // Get active item indices for the current page\n const activeItemIndices = getActiveItemIndices();\n\n // Handle carousel keyboard navigation\n const handleCarouselKeyDown = (e: React.KeyboardEvent) => {\n if (e.key === 'ArrowLeft') {\n e.preventDefault();\n // Navigate to previous slide\n const prevButton = carouselRef.current?.querySelector(\n '[dir=\"prev\"]',\n ) as HTMLButtonElement;\n if (prevButton && !prevButton.disabled) {\n prevButton.click();\n }\n } else if (e.key === 'ArrowRight') {\n e.preventDefault();\n // Navigate to next slide\n const nextButton = carouselRef.current?.querySelector(\n '[dir=\"next\"]',\n ) as HTMLButtonElement;\n if (nextButton && !nextButton.disabled) {\n nextButton.click();\n }\n } else if (e.key === 'Home') {\n e.preventDefault();\n // Go to first slide\n navigateToSlide(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n // Go to last slide\n navigateToSlide(totalPages - 1);\n }\n };\n\n // Generate a meaningful default aria-label if none provided\n const getDefaultAriaLabel = () => {\n if (ariaLabel) return ariaLabel;\n\n const currentSlideNumber = currentPage + 1;\n const autoplayStatus = autoPlay ? ', autoplay enabled' : '';\n\n return `Carousel with ${totalItems} items, currently showing slide ${currentSlideNumber} of ${totalPages}${autoplayStatus}`;\n };\n\n return (\n <div\n className={getCarouselWrapperClasses()}\n ref={carouselRef}\n style={cssVars as React.CSSProperties}\n data-dots-size={dotsSize}\n {...interactionHandlers}\n data-arrows-visible={arrowsVisible ? 'true' : 'false'}\n data-transitioning={isTransitioning ? 'true' : undefined}\n data-scroll-padding={resolvedScrollPadding}\n role=\"region\"\n aria-label={getDefaultAriaLabel()}\n aria-describedby={ariaDescribedby}\n aria-roledescription=\"carousel\"\n >\n <AriaCarousel\n className={`${carousel({ looping })}`}\n loop={loopMode}\n ref={ariaCarouselRef}\n autoplay={autoPlay}\n autoplayInterval={autoPlayInterval}\n mouseDragging={mouseDragging && !isTransitioning}\n onActivePageIndexChange={handleActivePageIndexChange}\n onDragStart={(e) => {\n setIsDragging(true);\n if (e && e.nativeEvent) {\n setDragStartPosition({\n x: e.nativeEvent.clientX,\n y: e.nativeEvent.clientY,\n });\n }\n }}\n onDragEnd={() => {\n // We keep isDragging true for a short time after drag ends\n // This helps ensure click events don't fire immediately after dragging\n if (dragEndResetTimeoutRef.current !== null) {\n window.clearTimeout(dragEndResetTimeoutRef.current);\n }\n\n if (dragEndSyncTimeoutRef.current !== null) {\n window.clearTimeout(dragEndSyncTimeoutRef.current);\n }\n\n dragEndResetTimeoutRef.current = window.setTimeout(() => {\n setIsDragging(false);\n syncCurrentPageFromVisibleItem();\n }, 50);\n\n dragEndSyncTimeoutRef.current = window.setTimeout(() => {\n syncCurrentPageFromVisibleItem();\n }, 150);\n }}\n data-dragging={isDragging ? 'true' : 'false'}\n itemsPerPage={safeItemsPerPage}\n scrollPadding={resolvedScrollPadding}\n data-has-space-between={\n resolvedSpaceBetweenItems > 0 ? 'true' : undefined\n }\n data-items-per-page={\n resolvedItemsPerPage !== 1 ? safeItemsPerPage.toString() : undefined\n }\n >\n <AriaCarouselScroller\n className={`${styles.scroller} ${focusStyleVariants({ focusStyle })} ${scrollerClassName}`.trim()}\n ref={scrollerRef}\n data-carousel-scroller=\"true\"\n {...focusProps}\n data-focused={isFocusVisible ? true : undefined}\n data-focus-visible={isFocusVisible ? true : undefined}\n data-transitioning={isTransitioning ? 'true' : undefined}\n style={getScrollerStyles()}\n tabIndex={0}\n role=\"region\"\n aria-label=\"Carousel content\"\n onKeyDown={handleCarouselKeyDown}\n >\n {items.map((child, index) => {\n const isActiveItem = activeItemIndices.includes(index);\n const itemClasses = [\n itemWrapperClassName || '',\n styles.item,\n isActiveItem && activeItemClassName ? activeItemClassName : '',\n ]\n .filter(Boolean)\n .join(' ');\n\n return (\n <AriaCarouselItem\n key={index}\n className={itemClasses}\n data-is-active={isActiveItem ? 'true' : undefined}\n data-item-index={index}\n aria-label={`Slide ${index + 1} of ${totalItems}`}\n >\n {child}\n </AriaCarouselItem>\n );\n })}\n </AriaCarouselScroller>\n\n <div\n className={`${styles.controls} ${showArrowsOnHover ? styles.showOnHover : ''}`}\n >\n <CarouselButton\n dir=\"prev\"\n variant={arrowStyleVariant}\n size={resolvedArrowSize}\n focusStyle={focusStyle}\n iconType={iconType}\n hideDisabledArrow={hideDisabledArrow}\n overrideHideDisabledArrow={loopMode ? undefined : !canScrollPrev}\n onClick={\n loopMode ? undefined : () => navigateToSlide(getPrevTargetPage())\n }\n onPress={() => onArrowPress?.('prev')}\n className={`${getClassNameWithDefault(prevArrowClassName, styles.defaultPrevButton)} ${!arrowsVisible ? styles.hidden : ''}`}\n />\n\n <CarouselButton\n dir=\"next\"\n variant={arrowStyleVariant}\n size={resolvedArrowSize}\n focusStyle={focusStyle}\n iconType={iconType}\n hideDisabledArrow={hideDisabledArrow}\n overrideHideDisabledArrow={loopMode ? undefined : !canScrollNext}\n onClick={\n loopMode ? undefined : () => navigateToSlide(getNextTargetPage())\n }\n onPress={() => onArrowPress?.('next')}\n className={`${getClassNameWithDefault(nextArrowClassName, styles.defaultNextButton)} ${!arrowsVisible ? styles.hidden : ''}`}\n />\n </div>\n\n <div className={styles.hiddenTabs}>\n <AriaCarouselTabs>\n {(page) => (\n <AriaCarouselTab\n key={page.index}\n index={page.index}\n data-index={page.index}\n data-slide-tab=\"true\"\n tabIndex={-1}\n />\n )}\n </AriaCarouselTabs>\n </div>\n\n {autoPlay && (\n <div\n className={`${styles.autoplayControlWrapper} ${getClassNameWithDefault(autoplayControlClassName, styles.defaultAutoplayControl)}`}\n >\n <AutoplayControl\n variant={autoPlayStyleVariant}\n size={autoPlayControlSize}\n className=\"\"\n onPress={onAutoplayPress}\n />\n </div>\n )}\n\n {shouldShowDots && (\n <CarouselDots\n totalItems={totalPages}\n currentPage={currentPage}\n onDotClick={(index) => navigateToSlide(index)}\n onPress={onDotPress}\n focusStyle={focusStyle}\n dotsSize={dotsSize}\n variant={dotsVariant}\n className={getClassNameWithDefault(\n dotsContainerClassName,\n styles.defaultDotsContainer,\n )}\n dotsWrapperClassName={dotsWrapperClassName}\n dotClassName={dotClassName}\n activeDotClassName={activeDotClassName}\n isTransitioning={isTransitioning}\n />\n )}\n </AriaCarousel>\n </div>\n );\n};\n\nexport default Carousel;\n"],"names":["carousel","cva","styles","variants","looping","infinite","backToStart","native","off","Carousel","children","ariaLabel","ariaDescribedby","itemsPerPage","spaceBetweenItems","scrollPadding","iconType","arrowStyleVariant","arrowSize","hideDisabledArrow","showArrowsOnHover","focusStyle","dotsSize","dotsVariant","hideDots","autoPlay","autoPlayInterval","autoPlayStyleVariant","autoPlayControlSize","mouseDragging","carouselWrapperClassName","prevArrowClassName","nextArrowClassName","autoplayControlClassName","dotsContainerClassName","dotsWrapperClassName","dotClassName","activeDotClassName","itemWrapperClassName","scrollerClassName","activeItemClassName","onArrowPress","onDotPress","onAutoplayPress","controlsRef","onNavigationStateChange","items","React","Children","toArray","totalItems","length","breakpoint","useBreakpoint","resolvedItemsPerPage","resolveResponsiveProp","resolvedSpaceBetweenItems","resolvedScrollPadding","resolvedArrowSize","resolvedHideDots","safeItemsPerPage","Math","max","wholeItemsPerPage","floor","pageItemGroups","useMemo","groups","Array","from","_","index","reduce","accumulator","currentGroup","at","push","deficit","fill","splice","unshift","carouselRef","useRef","ariaCarouselRef","dragEndResetTimeoutRef","dragEndSyncTimeoutRef","scrollerRef","itemElementsRef","currentPage","setCurrentPage","useState","prevPage","setPrevPage","canScrollPrev","setCanScrollPrev","canScrollNext","setCanScrollNext","ariaTabsPageCount","setAriaTabsPageCount","firstVisibleItemIndex","setFirstVisibleItemIndex","isDragging","setIsDragging","isHovered","setIsHovered","isFocused","setIsFocused","isTransitioning","dragStartPosition","setDragStartPosition","loopMode","totalPages","shouldShowDots","getClassNameWithDefault","customClassName","defaultClassName","getScrollerElement","useCallback","current","querySelector","scrollToPage","scroller","pageIndex","targetItemIndex","targetItem","scrollerRect","getBoundingClientRect","itemRect","scrollerPaddingLeft","parseFloat","getComputedStyle","paddingLeft","maxScrollLeft","scrollWidth","clientWidth","targetScrollLeft","scrollLeft","left","clampedTargetScrollLeft","min","scrollTo","behavior","navigateToSlide","targetTab","click","syncCurrentPageFromVisibleItem","itemElements","querySelectorAll","scrollerLeft","closestItemIndex","closestDistance","Number","POSITIVE_INFINITY","forEach","element","parseInt","dataset","itemIndex","isNaN","distance","abs","itemsPerPageWhole","nextPage","navigateToPreviousSlide","targetPage","scrollBy","window","setTimeout","navigateToNextSlide","useEffect","navigationState","canGoPrev","canGoNext","toNavigationState","prev","next","goToSlide","clearTimeout","preventLinkActivation","e","target","Element","closest","preventDefault","stopPropagation","handleMouseDown","x","clientX","y","clientY","handleMouseMove","dx","dy","sqrt","handleMouseUp","carouselElement","addEventListener","removeEventListener","HTMLElement","classList","remove","slideLeft","add","slideRight","animationFrameId","scheduleAriaPageCountUpdate","requestAnimationFrame","nextCount","previousCount","updateAriaPageCount","observer","MutationObserver","observe","childList","subtree","attributes","attributeFilter","cancelAnimationFrame","disconnect","scheduleScrollNavigationUpdate","updateScrollNavigation","epsilon","hasScrollableOverflow","nextCanScrollPrev","nextCanScrollNext","viewportStart","nextFirstVisibleItemIndex","find","item","offsetLeft","offsetWidth","parsedFirstVisibleItemIndex","safeFirstVisibleItemIndex","visibleItemCount","ceil","visibleWindowEndIndex","itemIndexString","removeAttribute","setAttribute","nextPageIndexFromVisibleItem","map","page","resolvedPageIndex","pageStartIndex","nextPageIndex","previousValue","previousPage","resizeObserver","ResizeObserver","nextItemElements","refreshItemElements","handleImageLoad","passive","interactionHandlers","onMouseEnter","onMouseLeave","onFocus","onBlur","currentTarget","contains","relatedTarget","arrowsVisible","cssVars","String","Object","keys","getCssVariables","focusProps","isFocusVisible","useFocusRing","activeItemIndices","jsx","className","classes","carouselWrapper","isInteger","showPartialItems","hasScrollPadding","trim","defaultCarouselWrapper","join","getCarouselWrapperClasses","ref","style","role","getDefaultAriaLabel","jsxs","AriaCarousel","loop","autoplay","autoplayInterval","onActivePageIndexChange","onDragStart","nativeEvent","onDragEnd","toString","AriaCarouselScroller","focusStyleVariants","gap","gapSpacePerItem","gridAutoColumns","getScrollerStyles","tabIndex","onKeyDown","key","prevButton","disabled","nextButton","child","isActiveItem","includes","itemClasses","filter","Boolean","AriaCarouselItem","controls","showOnHover","CarouselButton","dir","variant","size","overrideHideDisabledArrow","onClick","onPress","defaultPrevButton","hidden","defaultNextButton","hiddenTabs","AriaCarouselTabs","AriaCarouselTab","autoplayControlWrapper","defaultAutoplayControl","AutoplayControl","CarouselDots","onDotClick","defaultDotsContainer"],"mappings":"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"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import{jsx as e,jsxs as r}from"react/jsx-runtime";import t,{forwardRef as a,useRef as o,
|
|
2
|
-
return e("carousel"===
|
|
3
|
-
/* @__PURE__ */e("div",{className:
|
|
4
|
-
/* @__PURE__ */e("div",{className:
|
|
5
|
-
/* @__PURE__ */e(
|
|
6
|
-
/* @__PURE__ */e(
|
|
7
|
-
/* @__PURE__ */e(
|
|
1
|
+
import{jsx as e,jsxs as r}from"react/jsx-runtime";import t,{forwardRef as a,useRef as o,useEffect as l,useImperativeHandle as i}from"react";import{Box as c}from"../Box/Box.js";import{BoxLink as s}from"../BoxLink/BoxLink.js";import"../BoxLink/BoxLinkContext.js";import{Label as n}from"../Label/Label.js";import{Carousel as u}from"../Carousel/Carousel.js";import{CarouselButton as _}from"../Carousel/CarouselButton/CarouselButton.js";import{useBreakpoint as d}from"../../utils/breakpoint/hooks/useBreakpoint.js";import{useCarousel as g}from"../../utils/carousel/hooks/useCarousel.js";import{useFitCount as m}from"../../utils/layout/hooks/useFitCount.js";import '../../assets/CategoryTileGroup.css';const y={"category-tile-group__container":"_category-tile-group__container_1137c_1","category-tile-group":"_category-tile-group_1137c_1","category-tile":"_category-tile_1137c_1","category-tile--list":"_category-tile--list_1137c_19","category-tile--carousel":"_category-tile--carousel_1137c_23","category-tile__title":"_category-tile__title_1137c_34","category-tile-carousel__shell":"_category-tile-carousel__shell_1137c_58","category-tile-carousel__wrapper":"_category-tile-carousel__wrapper_1137c_62","category-tile-carousel__scroller":"_category-tile-carousel__scroller_1137c_68","category-tile-carousel__item-wrapper":"_category-tile-carousel__item-wrapper_1137c_73","category-tile-carousel__internal-arrow--prev":"_category-tile-carousel__internal-arrow--prev_1137c_81","category-tile-carousel__internal-arrow--next":"_category-tile-carousel__internal-arrow--next_1137c_82","category-tile-carousel__arrow--prev":"_category-tile-carousel__arrow--prev_1137c_87","category-tile-carousel__arrow--next":"_category-tile-carousel__arrow--next_1137c_88"},p=a(function({href:t,isDisabled:a=!1,target:o,externalLinkScreenReaderText:l,media:i,title:u,renderAs:_="list"},d){/* @__PURE__ */
|
|
2
|
+
return e("carousel"===_?"div":"li",{className:"carousel"===_?`${y["category-tile"]} ${y["category-tile--carousel"]}`:`${y["category-tile"]} ${y["category-tile--list"]}`,"data-category-tile-item":"true",children:/* @__PURE__ */e(s,{href:t,isDisabled:a,target:o,externalLinkScreenReaderText:l,styleVariant:"default",allowDragGestures:"carousel"===_,ref:d,children:/* @__PURE__ */r(c,{backgroundColor:"layer1",borderRadius:"card",display:"flex",flexDirection:"column",gap:"2xs",alignItems:"center",justifyContent:"flexStart",padding:"xs",minHeight:"124px",height:"-webkit-fill-available",children:[
|
|
3
|
+
/* @__PURE__ */e("div",{className:y["category-tile__media"],children:i}),
|
|
4
|
+
/* @__PURE__ */e("div",{className:y["category-tile__title"],children:/* @__PURE__ */e(n,{as:"p",size:"sm",foregroundColor:"accentSecondary",children:u})})]})})})}),w=a(function({children:a,"aria-label":c},s){const n=o(null),p=o(null),w=o(null),f=o(null),h=d();l(()=>(f.current||"undefined"==typeof document||(f.current=document.createElement("ul")),()=>{f.current=null}),[]);const v=t.Children.toArray(a),x=[`.${y["category-tile-carousel__wrapper"]} > div`,'[data-category-tile-measure-container="true"]',`.${y["category-tile-carousel__wrapper"]}`].join(", "),{fitCount:C,itemsPerPage:b,isOverflowing:S,isReady:k}=m({containerRef:n,measureContainerSelector:'[data-category-tile-measure-container="true"]',measureItemSelector:'[data-category-tile-item="true"]',availableWidthSelector:x,itemCount:v.length,fallbackGap:12});i(s,()=>w.current?w.current:(f.current||"undefined"==typeof document||(f.current=document.createElement("ul")),f.current));const A=k&&S&&C>0,N=["base","sm","md",""].includes(h??""),j=N,D=N?b:C,B=N?"15%":void 0,{setScrollerRef:L,isOverflowing:P,scrollLeft:z,scrollRight:R,prevArrowProps:$,nextArrowProps:E}=g({spaceBetweenItems:8,mouseDragging:!1,arrowSize:"md",arrowStyleVariant:"shapeElevated",showArrowsOnHover:j,hideDisabledArrows:!0});let T;return l(()=>{if(!A||!p.current)return;const e=p.current,r=()=>{const r=e.querySelector('[data-carousel-scroller="true"]');r&&L(r)},t=window.requestAnimationFrame(r);return r(),()=>{window.cancelAnimationFrame(t)}},[L,A]),T=A?/* @__PURE__ */r("div",{className:y["category-tile-carousel__shell"],ref:p,"data-show-arrows-on-hover":j?"true":void 0,children:[
|
|
5
|
+
/* @__PURE__ */e(u,{"aria-label":c??"Category tile group",itemsPerPage:D,scrollPadding:B,hideDots:!0,showArrowsOnHover:j,arrowSize:"md",arrowStyleVariant:"shapeElevated",spaceBetweenItems:8,hideDisabledArrow:!0,carouselWrapperClassName:y["category-tile-carousel__wrapper"],itemWrapperClassName:y["category-tile-carousel__item-wrapper"],scrollerClassName:y["category-tile-carousel__scroller"],prevArrowClassName:y["category-tile-carousel__internal-arrow--prev"],nextArrowClassName:y["category-tile-carousel__internal-arrow--next"],children:v.map((e,r)=>t.isValidElement(e)?t.cloneElement(e,{renderAs:"carousel",key:e.key??`category-tile-${r}`}):e)}),
|
|
6
|
+
/* @__PURE__ */e(_,{dir:"prev",standalone:!0,isDisabled:$.disabled||!P,size:$.size,variant:$.styleVariant,focusStyle:$.focusStyle,className:y["category-tile-carousel__arrow--prev"],hideDisabledArrow:$.hidden||!P,onPress:z}),
|
|
7
|
+
/* @__PURE__ */e(_,{dir:"next",standalone:!0,isDisabled:E.disabled||!P,size:E.size,variant:E.styleVariant,focusStyle:E.focusStyle,className:y["category-tile-carousel__arrow--next"],hideDisabledArrow:E.hidden||!P,onPress:R})]}):/* @__PURE__ */e("ul",{className:y["category-tile-group"],ref:w,"data-category-tile-measure-container":"true",children:a}),/* @__PURE__ */e("div",{className:y["category-tile-group__container"],ref:n,children:T})});w.displayName="CategoryTileGroup",w.Tile=p,p.displayName="CategoryTileGroup.Tile";const f=p;export{w as CategoryTileGroup,f as CategoryTileGroupTile,w as default};
|
|
8
8
|
//# sourceMappingURL=CategoryTileGroup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CategoryTileGroup.js","sources":["../../../src/components/CategoryTileGroup/CategoryTileGroup.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n} from 'react';\nimport styles from './CategoryTileGroup.module.css';\nimport Box from '../Box';\nimport BoxLink from '../BoxLink';\nimport type { BoxLinkProps } from '../BoxLink';\nimport Label from '../Label';\nimport Carousel from '../Carousel';\nimport { CarouselButton } from '../Carousel';\nimport type { CarouselControls } from '../Carousel/Carousel';\nimport { useFitCount } from '../../utils';\nimport { useBreakpoint } from '../../utils/breakpoint/hooks/useBreakpoint';\n\nconst DEFAULT_CAROUSEL_ARROW_STATE = {\n canGoPrev: false,\n canGoNext: false,\n};\n\nexport interface CategoryTileGroupProps {\n /**\n * Category tile items rendered in the group.\n */\n children: React.ReactNode;\n /**\n * Accessible label for the carousel when tiles overflow into carousel layout.\n */\n 'aria-label'?: string;\n}\n\nexport interface CategoryTileGroupTileProps {\n /**\n * Destination URL for the tile.\n */\n href: NonNullable<BoxLinkProps['href']>;\n /**\n * Whether the tile is disabled.\n * Prevents navigation and reflects aria-disabled.\n * @default false\n */\n isDisabled?: BoxLinkProps['isDisabled'];\n /**\n * The target attribute for the link.\n * @default '_self'\n */\n target?: BoxLinkProps['target'];\n /**\n * Custom screen reader text for external links.\n * @default 'opens in a new tab'\n */\n externalLinkScreenReaderText?: BoxLinkProps['externalLinkScreenReaderText'];\n /**\n * Leading media content.\n */\n media: React.ReactNode;\n /**\n * Main title content.\n */\n title: string;\n /**\n * @internal\n */\n renderAs?: 'list' | 'carousel';\n}\n\ninterface CategoryTileGroupComponent\n extends React.ForwardRefExoticComponent<\n CategoryTileGroupProps & React.RefAttributes<HTMLUListElement>\n > {\n Tile: React.ForwardRefExoticComponent<\n CategoryTileGroupTileProps & React.RefAttributes<HTMLAnchorElement>\n >;\n}\n\nconst Tile = forwardRef<HTMLAnchorElement, CategoryTileGroupTileProps>(\n function CategoryTileGroupTile(\n {\n href,\n isDisabled = false,\n target,\n externalLinkScreenReaderText,\n media,\n title,\n renderAs = 'list',\n },\n ref,\n ) {\n const WrapperElement = renderAs === 'carousel' ? 'div' : 'li';\n const wrapperClassName =\n renderAs === 'carousel'\n ? `${styles['category-tile']} ${styles['category-tile--carousel']}`\n : `${styles['category-tile']} ${styles['category-tile--list']}`;\n\n return (\n <WrapperElement\n className={wrapperClassName}\n data-category-tile-item=\"true\"\n >\n <BoxLink\n href={href}\n isDisabled={isDisabled}\n target={target}\n externalLinkScreenReaderText={externalLinkScreenReaderText}\n styleVariant=\"default\"\n allowDragGestures={renderAs === 'carousel'}\n ref={ref}\n >\n <Box\n backgroundColor=\"layer1\"\n borderRadius=\"card\"\n display=\"flex\"\n flexDirection=\"column\"\n gap=\"2xs\"\n alignItems=\"center\"\n justifyContent=\"flexStart\"\n padding=\"xs\"\n minHeight=\"124px\"\n height=\"-webkit-fill-available\"\n >\n <div className={styles['category-tile__media']}>{media}</div>\n <div className={styles['category-tile__title']}>\n <Label as=\"p\" size=\"sm\" foregroundColor=\"accentSecondary\">\n {title}\n </Label>\n </div>\n </Box>\n </BoxLink>\n </WrapperElement>\n );\n },\n);\n\nexport const CategoryTileGroup = forwardRef<\n HTMLUListElement,\n CategoryTileGroupProps\n>(function CategoryTileGroup({ children, 'aria-label': ariaLabel }, ref) {\n const CATEGORY_TILE_GAP_FALLBACK = 12;\n const containerRef = useRef<HTMLDivElement>(null);\n const carouselShellRef = useRef<HTMLDivElement>(null);\n const carouselControlsRef = useRef<CarouselControls | null>(null);\n const listRef = useRef<HTMLUListElement>(null);\n const detachedListRef = useRef<HTMLUListElement | null>(null);\n const [carouselArrowState, setCarouselArrowState] = useState(\n DEFAULT_CAROUSEL_ARROW_STATE,\n );\n const breakpoint = useBreakpoint();\n\n useEffect(() => {\n if (!detachedListRef.current && typeof document !== 'undefined') {\n detachedListRef.current = document.createElement('ul');\n }\n\n return () => {\n detachedListRef.current = null;\n };\n }, []);\n\n const items = React.Children.toArray(children);\n const availableWidthSelector = [\n `.${styles['category-tile-carousel__wrapper']} > div`,\n `[data-category-tile-measure-container=\"true\"]`,\n `.${styles['category-tile-carousel__wrapper']}`,\n ].join(', ');\n\n const { fitCount, itemsPerPage, isOverflowing, isReady } = useFitCount({\n containerRef,\n measureContainerSelector: '[data-category-tile-measure-container=\"true\"]',\n measureItemSelector: '[data-category-tile-item=\"true\"]',\n availableWidthSelector,\n itemCount: items.length,\n fallbackGap: CATEGORY_TILE_GAP_FALLBACK,\n });\n\n useImperativeHandle(ref, () => {\n if (listRef.current) {\n return listRef.current;\n }\n\n if (!detachedListRef.current && typeof document !== 'undefined') {\n detachedListRef.current = document.createElement('ul');\n }\n\n return detachedListRef.current as HTMLUListElement;\n });\n\n const shouldRenderCarousel = isReady && isOverflowing && fitCount > 0;\n const isMobileBreakpoint = ['base', 'sm', 'md', ''].includes(\n breakpoint ?? '',\n );\n const shouldShowArrowsOnHover = isMobileBreakpoint;\n const carouselItemsPerPage = isMobileBreakpoint ? itemsPerPage : fitCount;\n const carouselScrollPadding = isMobileBreakpoint ? '15%' : undefined;\n\n const isCarouselItemFullyVisible = useCallback((itemIndex: number) => {\n const carouselShell = carouselShellRef.current;\n\n if (!carouselShell) {\n return true;\n }\n\n const scroller = carouselShell.querySelector(\n '[data-carousel-scroller=\"true\"]',\n ) as HTMLElement | null;\n\n if (!scroller) {\n return true;\n }\n\n const item = scroller.querySelector(\n `[data-item-index=\"${itemIndex}\"]`,\n ) as HTMLElement | null;\n\n if (!item) {\n return true;\n }\n\n const scrollerRect = scroller.getBoundingClientRect();\n const itemRect = item.getBoundingClientRect();\n const tolerance = 1;\n\n return (\n itemRect.left >= scrollerRect.left - tolerance &&\n itemRect.right <= scrollerRect.right + tolerance\n );\n }, []);\n\n const updateStandaloneArrowState = useCallback(() => {\n if (!shouldRenderCarousel || items.length === 0) {\n setCarouselArrowState(DEFAULT_CAROUSEL_ARROW_STATE);\n return;\n }\n\n const firstItemVisible = isCarouselItemFullyVisible(0);\n const lastItemVisible = isCarouselItemFullyVisible(items.length - 1);\n\n setCarouselArrowState({\n canGoPrev: !firstItemVisible,\n canGoNext: !lastItemVisible,\n });\n }, [isCarouselItemFullyVisible, items.length, shouldRenderCarousel]);\n\n useEffect(() => {\n if (!shouldRenderCarousel || !carouselShellRef.current) {\n setCarouselArrowState(DEFAULT_CAROUSEL_ARROW_STATE);\n return;\n }\n\n const carouselShell = carouselShellRef.current;\n const scroller = carouselShell.querySelector(\n '[data-carousel-scroller=\"true\"]',\n ) as HTMLElement | null;\n\n if (!scroller) {\n setCarouselArrowState(DEFAULT_CAROUSEL_ARROW_STATE);\n return;\n }\n\n const scheduleUpdate = () => {\n requestAnimationFrame(() => {\n updateStandaloneArrowState();\n });\n };\n\n const initialTimer = window.setTimeout(() => {\n updateStandaloneArrowState();\n }, 100);\n\n scroller.addEventListener('scroll', scheduleUpdate, { passive: true });\n\n const resizeObserver = new ResizeObserver(() => {\n scheduleUpdate();\n });\n\n resizeObserver.observe(carouselShell);\n resizeObserver.observe(scroller);\n\n window.addEventListener('resize', scheduleUpdate, { passive: true });\n\n return () => {\n window.clearTimeout(initialTimer);\n scroller.removeEventListener('scroll', scheduleUpdate);\n resizeObserver.disconnect();\n window.removeEventListener('resize', scheduleUpdate);\n };\n }, [items.length, shouldRenderCarousel, updateStandaloneArrowState]);\n\n const handleStandaloneArrowPress = (direction: 'prev' | 'next') => {\n if (isMobileBreakpoint) {\n const carouselShell = carouselShellRef.current;\n const scroller = carouselShell?.querySelector(\n '[data-carousel-scroller=\"true\"]',\n ) as HTMLElement | null;\n\n if (scroller) {\n const currentFirstItem = scroller.querySelector(\n '[data-item-index=\"0\"]',\n ) as HTMLElement | null;\n const nextItem = scroller.querySelector(\n '[data-item-index=\"1\"]',\n ) as HTMLElement | null;\n\n if (currentFirstItem) {\n const currentRect = currentFirstItem.getBoundingClientRect();\n const nextRect = nextItem?.getBoundingClientRect();\n const scrollAmount = nextRect\n ? Math.abs(nextRect.left - currentRect.left)\n : currentRect.width;\n\n scroller.scrollBy({\n left: direction === 'prev' ? -scrollAmount : scrollAmount,\n behavior: 'smooth',\n });\n return;\n }\n }\n }\n\n if (direction === 'prev') {\n carouselControlsRef.current?.prev();\n } else {\n carouselControlsRef.current?.next();\n }\n };\n\n let content: React.ReactNode;\n\n if (shouldRenderCarousel) {\n content = (\n <div\n className={styles['category-tile-carousel__shell']}\n ref={carouselShellRef}\n data-show-arrows-on-hover={shouldShowArrowsOnHover ? 'true' : undefined}\n >\n <Carousel\n aria-label={ariaLabel ?? 'Category tile group'}\n itemsPerPage={carouselItemsPerPage}\n scrollPadding={carouselScrollPadding}\n hideDots={true}\n showArrowsOnHover={shouldShowArrowsOnHover}\n arrowSize=\"md\"\n arrowStyleVariant=\"shapeElevated\"\n spaceBetweenItems={8}\n hideDisabledArrow={true}\n controlsRef={carouselControlsRef}\n carouselWrapperClassName={styles['category-tile-carousel__wrapper']}\n itemWrapperClassName={styles['category-tile-carousel__item-wrapper']}\n scrollerClassName={styles['category-tile-carousel__scroller']}\n prevArrowClassName={\n styles['category-tile-carousel__internal-arrow--prev']\n }\n nextArrowClassName={\n styles['category-tile-carousel__internal-arrow--next']\n }\n >\n {items.map((child, index) => {\n if (!React.isValidElement<CategoryTileGroupTileProps>(child)) {\n return child;\n }\n\n return React.cloneElement(child, {\n renderAs: 'carousel',\n key: child.key ?? `category-tile-${index}`,\n });\n })}\n </Carousel>\n\n <CarouselButton\n dir=\"prev\"\n standalone={true}\n isDisabled={!carouselArrowState.canGoPrev}\n size=\"md\"\n variant=\"shapeElevated\"\n className={styles['category-tile-carousel__arrow--prev']}\n hideDisabledArrow={true}\n onPress={() => handleStandaloneArrowPress('prev')}\n />\n <CarouselButton\n dir=\"next\"\n standalone={true}\n isDisabled={!carouselArrowState.canGoNext}\n size=\"md\"\n variant=\"shapeElevated\"\n className={styles['category-tile-carousel__arrow--next']}\n hideDisabledArrow={true}\n onPress={() => handleStandaloneArrowPress('next')}\n />\n </div>\n );\n } else {\n content = (\n <ul\n className={styles['category-tile-group']}\n ref={listRef}\n data-category-tile-measure-container=\"true\"\n >\n {children}\n </ul>\n );\n }\n\n return (\n <div\n className={styles['category-tile-group__container']}\n ref={containerRef}\n >\n {content}\n </div>\n );\n}) as CategoryTileGroupComponent;\n\nCategoryTileGroup.displayName = 'CategoryTileGroup';\nCategoryTileGroup.Tile = Tile;\nTile.displayName = 'CategoryTileGroup.Tile';\n\nexport const CategoryTileGroupTile = Tile;\n\nexport default CategoryTileGroup;\n"],"names":["DEFAULT_CAROUSEL_ARROW_STATE","canGoPrev","canGoNext","Tile","forwardRef","href","isDisabled","target","externalLinkScreenReaderText","media","title","renderAs","ref","jsx","className","styles","children","BoxLink","styleVariant","allowDragGestures","jsxs","Box","backgroundColor","borderRadius","display","flexDirection","gap","alignItems","justifyContent","padding","minHeight","height","Label","as","size","foregroundColor","CategoryTileGroup","ariaLabel","containerRef","useRef","carouselShellRef","carouselControlsRef","listRef","detachedListRef","carouselArrowState","setCarouselArrowState","useState","breakpoint","useBreakpoint","useEffect","current","document","createElement","items","React","Children","toArray","availableWidthSelector","join","fitCount","itemsPerPage","isOverflowing","isReady","useFitCount","measureContainerSelector","measureItemSelector","itemCount","length","fallbackGap","useImperativeHandle","shouldRenderCarousel","isMobileBreakpoint","includes","shouldShowArrowsOnHover","carouselItemsPerPage","carouselScrollPadding","isCarouselItemFullyVisible","useCallback","itemIndex","carouselShell","scroller","querySelector","item","scrollerRect","getBoundingClientRect","itemRect","left","right","updateStandaloneArrowState","firstItemVisible","lastItemVisible","scheduleUpdate","requestAnimationFrame","initialTimer","window","setTimeout","addEventListener","passive","resizeObserver","ResizeObserver","observe","clearTimeout","removeEventListener","disconnect","handleStandaloneArrowPress","direction","currentFirstItem","nextItem","currentRect","nextRect","scrollAmount","Math","abs","width","scrollBy","behavior","prev","next","content","Carousel","scrollPadding","hideDots","showArrowsOnHover","arrowSize","arrowStyleVariant","spaceBetweenItems","hideDisabledArrow","controlsRef","carouselWrapperClassName","itemWrapperClassName","scrollerClassName","prevArrowClassName","nextArrowClassName","map","child","index","isValidElement","cloneElement","key","CarouselButton","dir","standalone","variant","onPress","displayName","CategoryTileGroupTile"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CategoryTileGroup.js","sources":["../../../src/components/CategoryTileGroup/CategoryTileGroup.tsx"],"sourcesContent":["import React, {\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n} from 'react';\nimport styles from './CategoryTileGroup.module.css';\nimport Box from '../Box';\nimport BoxLink from '../BoxLink';\nimport type { BoxLinkProps } from '../BoxLink';\nimport Label from '../Label';\nimport Carousel from '../Carousel';\nimport { CarouselButton } from '../Carousel';\nimport { useFitCount, useCarousel } from '../../utils';\nimport { useBreakpoint } from '../../utils/breakpoint/hooks/useBreakpoint';\n\nexport interface CategoryTileGroupProps {\n /**\n * Category tile items rendered in the group.\n */\n children: React.ReactNode;\n /**\n * Accessible label for the carousel when tiles overflow into carousel layout.\n */\n 'aria-label'?: string;\n}\n\nexport interface CategoryTileGroupTileProps {\n /**\n * Destination URL for the tile.\n */\n href: NonNullable<BoxLinkProps['href']>;\n /**\n * Whether the tile is disabled.\n * Prevents navigation and reflects aria-disabled.\n * @default false\n */\n isDisabled?: BoxLinkProps['isDisabled'];\n /**\n * The target attribute for the link.\n * @default '_self'\n */\n target?: BoxLinkProps['target'];\n /**\n * Custom screen reader text for external links.\n * @default 'opens in a new tab'\n */\n externalLinkScreenReaderText?: BoxLinkProps['externalLinkScreenReaderText'];\n /**\n * Leading media content.\n */\n media: React.ReactNode;\n /**\n * Main title content.\n */\n title: string;\n /**\n * @internal\n */\n renderAs?: 'list' | 'carousel';\n}\n\ninterface CategoryTileGroupComponent\n extends React.ForwardRefExoticComponent<\n CategoryTileGroupProps & React.RefAttributes<HTMLUListElement>\n > {\n Tile: React.ForwardRefExoticComponent<\n CategoryTileGroupTileProps & React.RefAttributes<HTMLAnchorElement>\n >;\n}\n\nconst Tile = forwardRef<HTMLAnchorElement, CategoryTileGroupTileProps>(\n function CategoryTileGroupTile(\n {\n href,\n isDisabled = false,\n target,\n externalLinkScreenReaderText,\n media,\n title,\n renderAs = 'list',\n },\n ref,\n ) {\n const WrapperElement = renderAs === 'carousel' ? 'div' : 'li';\n const wrapperClassName =\n renderAs === 'carousel'\n ? `${styles['category-tile']} ${styles['category-tile--carousel']}`\n : `${styles['category-tile']} ${styles['category-tile--list']}`;\n\n return (\n <WrapperElement\n className={wrapperClassName}\n data-category-tile-item=\"true\"\n >\n <BoxLink\n href={href}\n isDisabled={isDisabled}\n target={target}\n externalLinkScreenReaderText={externalLinkScreenReaderText}\n styleVariant=\"default\"\n allowDragGestures={renderAs === 'carousel'}\n ref={ref}\n >\n <Box\n backgroundColor=\"layer1\"\n borderRadius=\"card\"\n display=\"flex\"\n flexDirection=\"column\"\n gap=\"2xs\"\n alignItems=\"center\"\n justifyContent=\"flexStart\"\n padding=\"xs\"\n minHeight=\"124px\"\n height=\"-webkit-fill-available\"\n >\n <div className={styles['category-tile__media']}>{media}</div>\n <div className={styles['category-tile__title']}>\n <Label as=\"p\" size=\"sm\" foregroundColor=\"accentSecondary\">\n {title}\n </Label>\n </div>\n </Box>\n </BoxLink>\n </WrapperElement>\n );\n },\n);\n\nexport const CategoryTileGroup = forwardRef<\n HTMLUListElement,\n CategoryTileGroupProps\n>(function CategoryTileGroup({ children, 'aria-label': ariaLabel }, ref) {\n const CATEGORY_TILE_GAP_FALLBACK = 12;\n const containerRef = useRef<HTMLDivElement>(null);\n const carouselShellRef = useRef<HTMLDivElement>(null);\n const listRef = useRef<HTMLUListElement>(null);\n const detachedListRef = useRef<HTMLUListElement | null>(null);\n const breakpoint = useBreakpoint();\n\n useEffect(() => {\n if (!detachedListRef.current && typeof document !== 'undefined') {\n detachedListRef.current = document.createElement('ul');\n }\n\n return () => {\n detachedListRef.current = null;\n };\n }, []);\n\n const items = React.Children.toArray(children);\n const availableWidthSelector = [\n `.${styles['category-tile-carousel__wrapper']} > div`,\n `[data-category-tile-measure-container=\"true\"]`,\n `.${styles['category-tile-carousel__wrapper']}`,\n ].join(', ');\n\n const { fitCount, itemsPerPage, isOverflowing, isReady } = useFitCount({\n containerRef,\n measureContainerSelector: '[data-category-tile-measure-container=\"true\"]',\n measureItemSelector: '[data-category-tile-item=\"true\"]',\n availableWidthSelector,\n itemCount: items.length,\n fallbackGap: CATEGORY_TILE_GAP_FALLBACK,\n });\n\n useImperativeHandle(ref, () => {\n if (listRef.current) {\n return listRef.current;\n }\n\n if (!detachedListRef.current && typeof document !== 'undefined') {\n detachedListRef.current = document.createElement('ul');\n }\n\n return detachedListRef.current as HTMLUListElement;\n });\n\n const shouldRenderCarousel = isReady && isOverflowing && fitCount > 0;\n const isMobileBreakpoint = ['base', 'sm', 'md', ''].includes(\n breakpoint ?? '',\n );\n const shouldShowArrowsOnHover = isMobileBreakpoint;\n const carouselItemsPerPage = isMobileBreakpoint ? itemsPerPage : fitCount;\n const carouselScrollPadding = isMobileBreakpoint ? '15%' : undefined;\n\n const {\n setScrollerRef,\n isOverflowing: isCarouselOverflowing,\n scrollLeft,\n scrollRight,\n prevArrowProps,\n nextArrowProps,\n } = useCarousel({\n spaceBetweenItems: 8,\n mouseDragging: false,\n arrowSize: 'md',\n arrowStyleVariant: 'shapeElevated',\n showArrowsOnHover: shouldShowArrowsOnHover,\n hideDisabledArrows: true,\n });\n\n useEffect(() => {\n if (!shouldRenderCarousel || !carouselShellRef.current) {\n return;\n }\n\n const carouselShell = carouselShellRef.current;\n\n const attachScroller = () => {\n const scroller = carouselShell.querySelector(\n '[data-carousel-scroller=\"true\"]',\n ) as HTMLElement | null;\n\n if (scroller) {\n setScrollerRef(scroller);\n }\n };\n\n const rafId = window.requestAnimationFrame(attachScroller);\n attachScroller();\n\n return () => {\n window.cancelAnimationFrame(rafId);\n };\n }, [setScrollerRef, shouldRenderCarousel]);\n\n let content: React.ReactNode;\n\n if (shouldRenderCarousel) {\n content = (\n <div\n className={styles['category-tile-carousel__shell']}\n ref={carouselShellRef}\n data-show-arrows-on-hover={shouldShowArrowsOnHover ? 'true' : undefined}\n >\n <Carousel\n aria-label={ariaLabel ?? 'Category tile group'}\n itemsPerPage={carouselItemsPerPage}\n scrollPadding={carouselScrollPadding}\n hideDots={true}\n showArrowsOnHover={shouldShowArrowsOnHover}\n arrowSize=\"md\"\n arrowStyleVariant=\"shapeElevated\"\n spaceBetweenItems={8}\n hideDisabledArrow={true}\n carouselWrapperClassName={styles['category-tile-carousel__wrapper']}\n itemWrapperClassName={styles['category-tile-carousel__item-wrapper']}\n scrollerClassName={styles['category-tile-carousel__scroller']}\n prevArrowClassName={\n styles['category-tile-carousel__internal-arrow--prev']\n }\n nextArrowClassName={\n styles['category-tile-carousel__internal-arrow--next']\n }\n >\n {items.map((child, index) => {\n if (!React.isValidElement<CategoryTileGroupTileProps>(child)) {\n return child;\n }\n\n return React.cloneElement(child, {\n renderAs: 'carousel',\n key: child.key ?? `category-tile-${index}`,\n });\n })}\n </Carousel>\n\n <CarouselButton\n dir=\"prev\"\n standalone={true}\n isDisabled={prevArrowProps.disabled || !isCarouselOverflowing}\n size={prevArrowProps.size}\n variant={prevArrowProps.styleVariant}\n focusStyle={prevArrowProps.focusStyle}\n className={styles['category-tile-carousel__arrow--prev']}\n hideDisabledArrow={prevArrowProps.hidden || !isCarouselOverflowing}\n onPress={scrollLeft}\n />\n <CarouselButton\n dir=\"next\"\n standalone={true}\n isDisabled={nextArrowProps.disabled || !isCarouselOverflowing}\n size={nextArrowProps.size}\n variant={nextArrowProps.styleVariant}\n focusStyle={nextArrowProps.focusStyle}\n className={styles['category-tile-carousel__arrow--next']}\n hideDisabledArrow={nextArrowProps.hidden || !isCarouselOverflowing}\n onPress={scrollRight}\n />\n </div>\n );\n } else {\n content = (\n <ul\n className={styles['category-tile-group']}\n ref={listRef}\n data-category-tile-measure-container=\"true\"\n >\n {children}\n </ul>\n );\n }\n\n return (\n <div\n className={styles['category-tile-group__container']}\n ref={containerRef}\n >\n {content}\n </div>\n );\n}) as CategoryTileGroupComponent;\n\nCategoryTileGroup.displayName = 'CategoryTileGroup';\nCategoryTileGroup.Tile = Tile;\nTile.displayName = 'CategoryTileGroup.Tile';\n\nexport const CategoryTileGroupTile = Tile;\n\nexport default CategoryTileGroup;\n"],"names":["Tile","forwardRef","href","isDisabled","target","externalLinkScreenReaderText","media","title","renderAs","ref","jsx","className","styles","children","BoxLink","styleVariant","allowDragGestures","jsxs","Box","backgroundColor","borderRadius","display","flexDirection","gap","alignItems","justifyContent","padding","minHeight","height","Label","as","size","foregroundColor","CategoryTileGroup","ariaLabel","containerRef","useRef","carouselShellRef","listRef","detachedListRef","breakpoint","useBreakpoint","useEffect","current","document","createElement","items","React","Children","toArray","availableWidthSelector","join","fitCount","itemsPerPage","isOverflowing","isReady","useFitCount","measureContainerSelector","measureItemSelector","itemCount","length","fallbackGap","useImperativeHandle","shouldRenderCarousel","isMobileBreakpoint","includes","shouldShowArrowsOnHover","carouselItemsPerPage","carouselScrollPadding","setScrollerRef","isCarouselOverflowing","scrollLeft","scrollRight","prevArrowProps","nextArrowProps","useCarousel","spaceBetweenItems","mouseDragging","arrowSize","arrowStyleVariant","showArrowsOnHover","hideDisabledArrows","content","carouselShell","attachScroller","scroller","querySelector","rafId","window","requestAnimationFrame","cancelAnimationFrame","Carousel","scrollPadding","hideDots","hideDisabledArrow","carouselWrapperClassName","itemWrapperClassName","scrollerClassName","prevArrowClassName","nextArrowClassName","map","child","index","isValidElement","cloneElement","key","CarouselButton","dir","standalone","disabled","variant","focusStyle","hidden","onPress","displayName","CategoryTileGroupTile"],"mappings":"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"}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { default as React } from 'react';
|
|
2
2
|
import { OverlayTriggerState } from 'react-stately';
|
|
3
|
+
import { ResponsiveValue } from '../../utils/breakpoint/responsiveSSR';
|
|
3
4
|
type PlacementType = 'top' | 'top start' | 'top end' | 'right' | 'right top' | 'right bottom' | 'bottom' | 'bottom start' | 'bottom end' | 'left' | 'left top' | 'left bottom';
|
|
4
|
-
type ArrowDirectionType = 'top' | 'right' | 'bottom' | 'left';
|
|
5
|
+
type ArrowDirectionType = 'top' | 'top-left' | 'top-right' | 'right' | 'bottom' | 'left';
|
|
5
6
|
export interface InternalPopoverProps {
|
|
6
7
|
/**
|
|
7
8
|
* @private Internal use only - placement of the popover
|
|
@@ -34,8 +35,8 @@ export interface PopoverProps {
|
|
|
34
35
|
children: React.ReactNode;
|
|
35
36
|
/** State object controlling the overlay's open/closed status */
|
|
36
37
|
state: OverlayTriggerState;
|
|
37
|
-
/** Direction of the arrow that points to the trigger element */
|
|
38
|
-
arrowDirection?: ArrowDirectionType
|
|
38
|
+
/** **[Responsive]** Direction of the arrow that points to the trigger element */
|
|
39
|
+
arrowDirection?: ResponsiveValue<ArrowDirectionType>;
|
|
39
40
|
/** Reference to the trigger element that opens the popover */
|
|
40
41
|
triggerRef: React.RefObject<HTMLButtonElement | null> | {
|
|
41
42
|
current: HTMLElement | null;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{jsx as
|
|
2
|
-
return
|
|
3
|
-
/* @__PURE__ */
|
|
1
|
+
import{jsx as t,jsxs as e}from"react/jsx-runtime";import o,{useRef as r,useEffect as s}from"react";import{usePopover as i,Overlay as n,DismissButton as a}from"@react-aria/overlays";import{FocusScope as p}from"@react-aria/focus";import{useBreakpoint as l}from"../../utils/breakpoint/hooks/useBreakpoint.js";import{resolveResponsiveProp as c}from"../../utils/breakpoint/responsive.js";import '../../assets/Popover.css';const m="_popover_1ntxl_1",u="_arrow_1ntxl_11",f={top:"top","top-left":"top start","top-right":"top end",right:"right",bottom:"bottom",left:"left"},d=t=>t?.split(" ")[0],h=({children:h,state:b,arrowDirection:v="bottom",triggerRef:w,hasArrow:g=!0,placement:_,shouldFlip:j=!0,isNonModal:F=!1,allowTabOut:k=!1,autoFocus:x=!0})=>{const y=r(null),M=l(),N=o.useMemo(()=>c(v,M)??"bottom",[v,M]),E=_||f[N],[O,R]=o.useState(E);s(()=>{const t=()=>{y.current&&w.current&&R(E)};return t(),window.addEventListener("resize",t),()=>window.removeEventListener("resize",t)},[E,w]);const{popoverProps:z,arrowProps:D,placement:L}=i({popoverRef:y,triggerRef:w,offset:12,placement:O,shouldFlip:j,isNonModal:F},b),P=d(L)===d(O)?O:L||O,A="top start"===P||"top end"===P?P:d(P)||P,B=o.useMemo(()=>{const t=D.style;if(t){if("top start"===A||"top end"===A){return Object.fromEntries(Object.entries(t).filter(([t])=>"left"!==t&&"right"!==t&&"top"!==t&&"bottom"!==t))}return t}},[D.style,A]);/* @__PURE__ */
|
|
2
|
+
return t(n,{children:/* @__PURE__ */t(p,{restoreFocus:!0,contain:!k,autoFocus:x,children:/* @__PURE__ */e("div",{...z,ref:y,className:m,children:[
|
|
3
|
+
/* @__PURE__ */t(a,{onDismiss:b.close}),h,g&&/* @__PURE__ */t("div",{...D,className:u,style:B,"data-placement":A})]})})})};export{h as Popover,h as default};
|
|
4
4
|
//# sourceMappingURL=Popover.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Popover.js","sources":["../../../src/components/Popover/Popover.tsx"],"sourcesContent":["import React, { useRef, useEffect } from 'react';\nimport { usePopover, DismissButton, Overlay } from '@react-aria/overlays';\nimport { FocusScope } from '@react-aria/focus';\nimport type { OverlayTriggerState } from 'react-stately';\nimport styles from './Popover.module.css';\n\n// Define all possible placement options for internal use (menu component)\ntype PlacementType =\n | 'top'\n | 'top start'\n | 'top end'\n | 'right'\n | 'right top'\n | 'right bottom'\n | 'bottom'\n | 'bottom start'\n | 'bottom end'\n | 'left'\n | 'left top'\n | 'left bottom';\n\n// Define the arrow direction options for the public use\ntype ArrowDirectionType
|
|
1
|
+
{"version":3,"file":"Popover.js","sources":["../../../src/components/Popover/Popover.tsx"],"sourcesContent":["import React, { useRef, useEffect } from 'react';\nimport { usePopover, DismissButton, Overlay } from '@react-aria/overlays';\nimport { FocusScope } from '@react-aria/focus';\nimport type { OverlayTriggerState } from 'react-stately';\nimport { useBreakpoint } from '../../utils/breakpoint/hooks/useBreakpoint';\nimport { resolveResponsiveProp } from '../../utils/breakpoint/responsive';\nimport type { ResponsiveValue } from '../../utils/breakpoint/responsiveSSR';\nimport styles from './Popover.module.css';\n\n// Define all possible placement options for internal use (menu component)\ntype PlacementType =\n | 'top'\n | 'top start'\n | 'top end'\n | 'right'\n | 'right top'\n | 'right bottom'\n | 'bottom'\n | 'bottom start'\n | 'bottom end'\n | 'left'\n | 'left top'\n | 'left bottom';\n\n// Define the arrow direction options for the public use\ntype ArrowDirectionType =\n | 'top'\n | 'top-left'\n | 'top-right'\n | 'right'\n | 'bottom'\n | 'left';\n\n// Mapping arrow direction to placement\nconst arrowDirectionToPlacement: Record<ArrowDirectionType, PlacementType> = {\n top: 'top',\n 'top-left': 'top start',\n 'top-right': 'top end',\n right: 'right',\n bottom: 'bottom',\n left: 'left',\n};\n\nconst getPlacementSide = (value?: string | null) => value?.split(' ')[0];\n\n// Internal props that should only be used by specific components like Menu\nexport interface InternalPopoverProps {\n /**\n * @private Internal use only - placement of the popover\n * This prop should only be used by internal components like Menu\n */\n placement?: PlacementType;\n\n /**\n * @private Internal use only - whether the popover should flip to fit in the viewport\n * This prop should only be used by internal components like Menu\n */\n shouldFlip?: boolean;\n\n /**\n * @private Internal use only - whether to allow elements outside the popover may be interacted with\n * This prop should only be used by internal components like Menu\n */\n isNonModal?: boolean;\n\n /**\n * @private Internal use only - whether to allow tabbing out of the popover\n * This prop should only be used by internal components like Menu\n */\n allowTabOut?: boolean;\n\n /**\n * @private Internal use only - whether to auto-focus elements within the popover\n * This prop should only be used by internal components like Menu\n */\n autoFocus?: boolean;\n}\n\n// Public props exposed to all component users\nexport interface PopoverProps {\n /** Content to be rendered inside the popover */\n children: React.ReactNode;\n\n /** State object controlling the overlay's open/closed status */\n state: OverlayTriggerState;\n\n /** **[Responsive]** Direction of the arrow that points to the trigger element */\n arrowDirection?: ResponsiveValue<ArrowDirectionType>;\n\n /** Reference to the trigger element that opens the popover */\n triggerRef:\n | React.RefObject<HTMLButtonElement | null>\n | { current: HTMLElement | null };\n\n /** Whether to show the directional arrow on the popover */\n hasArrow?: boolean;\n}\n\n// Combined props type for internal use\nexport type CombinedPopoverProps = PopoverProps & InternalPopoverProps;\n\nexport const Popover = ({\n children,\n state,\n arrowDirection = 'bottom',\n triggerRef,\n hasArrow = true,\n placement,\n shouldFlip = true,\n isNonModal = false,\n allowTabOut = false,\n autoFocus = true,\n}: CombinedPopoverProps) => {\n const popoverRef = useRef<HTMLDivElement>(null);\n const currentBreakpoint = useBreakpoint();\n\n const resolvedArrowDirection = React.useMemo(\n () => resolveResponsiveProp(arrowDirection, currentBreakpoint) ?? 'bottom',\n [arrowDirection, currentBreakpoint],\n );\n\n // Determine the placement - either from direct placement (internal) or mapped from arrowDirection\n const initialPlacement =\n placement || arrowDirectionToPlacement[resolvedArrowDirection];\n const [optimalPlacement, setOptimalPlacement] =\n React.useState<PlacementType>(initialPlacement);\n\n useEffect(() => {\n const updatePlacement = () => {\n if (!popoverRef.current || !triggerRef.current) return;\n\n // When viewport constraints are encountered, the placement can change\n // React Aria's shouldFlip handles most of this logic now\n setOptimalPlacement(initialPlacement);\n };\n\n updatePlacement();\n window.addEventListener('resize', updatePlacement);\n return () => window.removeEventListener('resize', updatePlacement);\n }, [initialPlacement, triggerRef]);\n\n const {\n popoverProps,\n arrowProps,\n placement: finalPlacement,\n } = usePopover(\n {\n popoverRef,\n triggerRef,\n offset: 12,\n placement: optimalPlacement,\n shouldFlip,\n isNonModal,\n },\n state,\n );\n\n const arrowPlacement =\n getPlacementSide(finalPlacement) === getPlacementSide(optimalPlacement)\n ? optimalPlacement\n : finalPlacement || optimalPlacement;\n\n const normalizedArrowPlacement =\n arrowPlacement === 'top start' || arrowPlacement === 'top end'\n ? arrowPlacement\n : getPlacementSide(arrowPlacement) || arrowPlacement;\n\n const resolvedArrowStyle = React.useMemo(() => {\n const inlineArrowStyle = arrowProps.style;\n\n if (!inlineArrowStyle) {\n return undefined;\n }\n\n // Keep react-aria computed styles generally, but preserve CSS-driven\n // arrow offsets for top start/end variants.\n if (\n normalizedArrowPlacement === 'top start' ||\n normalizedArrowPlacement === 'top end'\n ) {\n const nonPositionalStyles = Object.fromEntries(\n Object.entries(inlineArrowStyle).filter(\n ([key]) =>\n key !== 'left' &&\n key !== 'right' &&\n key !== 'top' &&\n key !== 'bottom',\n ),\n );\n\n return nonPositionalStyles;\n }\n\n return inlineArrowStyle;\n }, [arrowProps.style, normalizedArrowPlacement]);\n\n return (\n <Overlay>\n <FocusScope restoreFocus contain={!allowTabOut} autoFocus={autoFocus}>\n <div {...popoverProps} ref={popoverRef} className={styles.popover}>\n <DismissButton onDismiss={state.close} />\n {children}\n {hasArrow && (\n <div\n {...arrowProps}\n className={styles.arrow}\n style={resolvedArrowStyle}\n data-placement={normalizedArrowPlacement}\n />\n )}\n </div>\n </FocusScope>\n </Overlay>\n );\n};\n\nexport default Popover;\n"],"names":["arrowDirectionToPlacement","top","right","bottom","left","getPlacementSide","value","split","Popover","children","state","arrowDirection","triggerRef","hasArrow","placement","shouldFlip","isNonModal","allowTabOut","autoFocus","popoverRef","useRef","currentBreakpoint","useBreakpoint","resolvedArrowDirection","React","useMemo","resolveResponsiveProp","initialPlacement","optimalPlacement","setOptimalPlacement","useState","useEffect","updatePlacement","current","window","addEventListener","removeEventListener","popoverProps","arrowProps","finalPlacement","usePopover","offset","arrowPlacement","normalizedArrowPlacement","resolvedArrowStyle","inlineArrowStyle","style","Object","fromEntries","entries","filter","key","Overlay","jsx","FocusScope","restoreFocus","contain","jsxs","ref","className","styles","DismissButton","onDismiss","close"],"mappings":"8aAkCMA,EAAuE,CAC3EC,IAAK,MACL,WAAY,YACZ,YAAa,UACbC,MAAO,QACPC,OAAQ,SACRC,KAAM,QAGFC,EAAoBC,GAA0BA,GAAOC,MAAM,KAAK,GA0DzDC,EAAU,EACrBC,WACAC,QACAC,iBAAiB,SACjBC,aACAC,YAAW,EACXC,YACAC,cAAa,EACbC,cAAa,EACbC,eAAc,EACdC,aAAY,MAEZ,MAAMC,EAAaC,EAAuB,MACpCC,EAAoBC,IAEpBC,EAAyBC,EAAMC,QACnC,IAAMC,EAAsBf,EAAgBU,IAAsB,SAClE,CAACV,EAAgBU,IAIbM,EACJb,GAAad,EAA0BuB,IAClCK,EAAkBC,GACvBL,EAAMM,SAAwBH,GAEhCI,EAAU,KACR,MAAMC,EAAkB,KACjBb,EAAWc,SAAYrB,EAAWqB,SAIvCJ,EAAoBF,IAKtB,OAFAK,IACAE,OAAOC,iBAAiB,SAAUH,GAC3B,IAAME,OAAOE,oBAAoB,SAAUJ,IACjD,CAACL,EAAkBf,IAEtB,MAAMyB,aACJA,EAAAC,WACAA,EACAxB,UAAWyB,GACTC,EACF,CACErB,aACAP,aACA6B,OAAQ,GACR3B,UAAWc,EACXb,aACAC,cAEFN,GAGIgC,EACJrC,EAAiBkC,KAAoBlC,EAAiBuB,GAClDA,EACAW,GAAkBX,EAElBe,EACe,cAAnBD,GAAqD,YAAnBA,EAC9BA,EACArC,EAAiBqC,IAAmBA,EAEpCE,EAAqBpB,EAAMC,QAAQ,KACvC,MAAMoB,EAAmBP,EAAWQ,MAEpC,GAAKD,EAAL,CAMA,GAC+B,cAA7BF,GAC6B,YAA7BA,EACA,CAWA,OAV4BI,OAAOC,YACjCD,OAAOE,QAAQJ,GAAkBK,OAC/B,EAAEC,KACQ,SAARA,GACQ,UAARA,GACQ,QAARA,GACQ,WAARA,GAKR,CAEA,OAAON,CArBP,GAsBC,CAACP,EAAWQ,MAAOH;AAEtB,SACGS,EAAA,CACC3C,wBAAA4C,EAACC,GAAWC,cAAY,EAACC,SAAUvC,EAAaC,YAC9CT,wBAAAgD,EAAC,UAAQpB,EAAcqB,IAAKvC,EAAYwC,UAAWC,EACjDnD,SAAA;iBAACoD,EAAA,CAAcC,UAAWpD,EAAMqD,QAC/BtD,EACAI,kBACCwC,EAAC,MAAA,IACKf,EACJqB,UAAWC,EACXd,MAAOF,EACP,iBAAgBD"}
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import './assets/global.css';/* empty css */import{Accordion as o,AccordionGroup as e}from"./components/Accordion/Accordion.js";import{AviosCurrencyBadge as r}from"./components/AviosCurrencyBadge/AviosCurrencyBadge.js";import{AviosCurrency as t}from"./components/AviosCurrency/AviosCurrency.js";import{AviosBadge as
|
|
1
|
+
import './assets/global.css';/* empty css */import{Accordion as o,AccordionGroup as e}from"./components/Accordion/Accordion.js";import{AviosCurrencyBadge as r}from"./components/AviosCurrencyBadge/AviosCurrencyBadge.js";import{AviosCurrency as t}from"./components/AviosCurrency/AviosCurrency.js";import{AviosBadge as m}from"./components/AviosBadge/AviosBadge.js";import{AviosCurrencySymbol as n}from"./components/AviosCurrencySymbol/AviosCurrencySymbol.js";import{Badge as s}from"./components/Badge/Badge.js";import{BannerSectionContent as i}from"./components/BannerSectionContent/BannerSectionContent.js";import{BannerSectionPlectrum as p}from"./components/BannerSectionPlectrum/BannerSectionPlectrum.js";import{Box as a}from"./components/Box/Box.js";import{BoxLink as c}from"./components/BoxLink/BoxLink.js";import{BoxLinkContext as l,useBoxLink as d}from"./components/BoxLink/BoxLinkContext.js";import{Breadcrumbs as f}from"./components/Breadcrumbs/Breadcrumbs.js";import{Button as j}from"./components/Button/Button.js";import{ButtonGroup as u}from"./components/ButtonGroup/ButtonGroup.js";import{Calendar as C}from"./components/Calendar/Calendar.js";import{CalendarRange as B}from"./components/CalendarRange/CalendarRange.js";import{CalloutBanner as g}from"./components/CalloutBanner/CalloutBanner.js";import{default as F}from"./components/CardSection/CardSection.js";import{Carousel as S}from"./components/Carousel/Carousel.js";import{CarouselButton as b}from"./components/Carousel/CarouselButton/CarouselButton.js";import{CategoryTileGroup as k}from"./components/CategoryTileGroup/CategoryTileGroup.js";import{Checkbox as T}from"./components/Checkbox/Checkbox.js";import{CheckboxGroup as x}from"./components/CheckboxGroup/CheckboxGroup.js";import{ClearFieldButton as y}from"./components/ClearFieldButton/ClearFieldButton.js";import{ComboBox as h}from"./components/ComboBox/ComboBox.js";import{CreditCardNumberField as L}from"./components/CreditCardNumberField/CreditCardNumberField.js";import{CreditCardSecurityCodeField as P}from"./components/CreditCardSecurityCodeField/CreditCardSecurityCodeField.js";import{Currency as D}from"./components/Currency/Currency.js";import{DateField as I}from"./components/DateField/DateField.js";import{DatePicker as v}from"./components/DatePicker/DatePicker.js";import{DateRangePicker as A}from"./components/DateRangePicker/DateRangePicker.js";import{DestinationHeading as G}from"./components/DestinationHeading/DestinationHeading.js";import{DetailsDisclosure as H}from"./components/DetailsDisclosure/DetailsDisclosure.js";import{Dialog as N}from"./components/Dialog/Dialog.js";import{ErrorSummary as R}from"./components/ErrorSummary/ErrorSummary.js";import{Eyebrow as w}from"./components/Eyebrow/Eyebrow.js";import{default as E}from"./components/FieldDescription/FieldDescription.js";import{FieldError as M}from"./components/FieldError/FieldError.js";import{FieldHeader as V}from"./components/FieldHeader/FieldHeader.js";import{FieldLabel as Y}from"./components/FieldLabel/FieldLabel.js";import{default as q}from"./components/Fieldset/Fieldset.js";import{FieldsetHeader as z}from"./components/FieldsetHeader/FieldsetHeader.js";import{Form as J}from"./components/Form/Form.js";import{Grid as K}from"./components/Grid/Grid.js";import{Heading as O}from"./components/Heading/Heading.js";import{Icon as Q}from"./components/Icon/Icon.js";import{IconBackdrop as U}from"./components/IconBackdrop/IconBackdrop.js";import{IconButton as W}from"./components/IconButton/IconButton.js";import{Image as X}from"./components/Image/Image.js";import{IntroSection as Z}from"./components/IntroSection/IntroSection.js";import{Label as $}from"./components/Label/Label.js";import{default as _}from"./components/Link/Link.js";import{ListBox as oo}from"./components/ListBox/ListBox.js";import{ListBoxItem as eo}from"./components/ListBoxItem/ListBoxItem.js";import{default as ro}from"./components/LoadingSpinner/LoadingSpinner.js";import{Menu as to}from"./components/Menu/Menu.js";import{MonthYearField as mo}from"./components/MonthYearField/MonthYearField.js";import{NumberField as no}from"./components/NumberField/NumberField.js";import{Paragraph as so}from"./components/Paragraph/Paragraph.js";import{PasswordField as io}from"./components/PasswordField/PasswordField.js";import{PhoneNumberField as po}from"./components/PhoneNumberField/PhoneNumberField.js";import"./utils/phoneNumber/phoneNumber.js";import{Popover as ao}from"./components/Popover/Popover.js";import{ProductTile as co}from"./components/ProductTile/ProductTile.js";import{Radio as lo}from"./components/Radio/Radio.js";import{RadioGroup as fo}from"./components/RadioGroup/RadioGroup.js";import{SearchField as jo}from"./components/SearchField/SearchField.js";import{default as uo}from"./components/Section/Section.js";import{default as Co}from"./components/SelectCard/SelectCard.js";import{default as Bo}from"./components/SelectNative/SelectNative.js";import{SkeletonLoader as go}from"./components/SkeletonLoader/SkeletonLoader.js";import{Slider as Fo}from"./components/Slider/Slider.js";import{Spacer as So}from"./components/Spacer/Spacer.js";import{SubHeading as bo}from"./components/SubHeading/SubHeading.js";import{Switch as ko}from"./components/Switch/Switch.js";import{Tab as To,TabList as xo,Tabs as yo,useTabsContext as ho}from"./components/Tabs/Tabs.js";import{T as Lo}from"./TabPanel-C7ANBbTA.js";import{Tag as Po}from"./components/Tag/Tag.js";import{TagGroup as Do}from"./components/TagGroup/TagGroup.js";import{TextField as Io}from"./components/TextField/TextField.js";import{TextAreaField as vo}from"./components/TextAreaField/TextAreaField.js";import{default as Ao}from"./components/ToggleButton/ToggleButton.js";import{default as Go}from"./components/ToggleIconButton/ToggleIconButton.js";import{Tooltip as Ho}from"./components/Tooltip/Tooltip.js";import{VisuallyHidden as No}from"./components/VisuallyHidden/VisuallyHidden.js";import{useBreakpoint as Ro}from"./utils/breakpoint/hooks/useBreakpoint.js";import{useCarousel as wo}from"./utils/carousel/hooks/useCarousel.js";import{BREAKPOINTS as Eo}from"./utils/breakpoint/theme/breakpoints.js";import{useFitCount as Mo}from"./utils/layout/hooks/useFitCount.js";export{o as Accordion,e as AccordionGroup,m as AviosBadge,t as AviosCurrency,r as AviosCurrencyBadge,n as AviosCurrencySymbol,Eo as BREAKPOINTS,s as Badge,i as BannerSectionContent,p as BannerSectionPlectrum,a as Box,c as BoxLink,l as BoxLinkContext,f as Breadcrumbs,j as Button,u as ButtonGroup,C as Calendar,B as CalendarRange,g as CalloutBanner,F as CardSection,S as Carousel,b as CarouselButton,k as CategoryTileGroup,T as Checkbox,x as CheckboxGroup,y as ClearFieldButton,h as ComboBox,L as CreditCardNumberField,P as CreditCardSecurityCodeField,D as Currency,I as DateField,v as DatePicker,A as DateRangePicker,G as DestinationHeading,H as DetailsDisclosure,N as Dialog,R as ErrorSummary,w as Eyebrow,E as FieldDescription,M as FieldError,V as FieldHeader,Y as FieldLabel,q as Fieldset,z as FieldsetHeader,J as Form,K as Grid,O as Heading,Q as Icon,U as IconBackdrop,W as IconButton,X as Image,Z as IntroSection,$ as Label,_ as Link,oo as ListBox,eo as ListBoxItem,ro as LoadingSpinner,to as Menu,mo as MonthYearField,no as NumberField,so as Paragraph,io as PasswordField,po as PhoneNumberField,ao as Popover,co as ProductTile,lo as Radio,fo as RadioGroup,jo as SearchField,uo as Section,Co as SelectCard,Bo as SelectNative,go as SkeletonLoader,Fo as Slider,So as Spacer,bo as SubHeading,ko as Switch,To as Tab,xo as TabList,Lo as TabPanel,yo as Tabs,Po as Tag,Do as TagGroup,vo as TextAreaField,Io as TextField,Ao as ToggleButton,Go as ToggleIconButton,Ho as Tooltip,No as VisuallyHidden,d as useBoxLink,Ro as useBreakpoint,wo as useCarousel,Mo as useFitCount,ho as useTabsContext};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { RefObject, PointerEvent as ReactPointerEvent, MouseEvent as ReactMouseEvent, CSSProperties, RefCallback } from 'react';
|
|
2
|
+
export interface UseCarouselOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Space between items (px). Used when computing per-item scroll steps.
|
|
5
|
+
* @default 0
|
|
6
|
+
*/
|
|
7
|
+
spaceBetweenItems?: number;
|
|
8
|
+
/**
|
|
9
|
+
* Enable pointer/mouse drag-to-scroll.
|
|
10
|
+
* @default true
|
|
11
|
+
*/
|
|
12
|
+
mouseDragging?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Pointer types that can initiate drag-to-scroll.
|
|
15
|
+
* @default ['mouse']
|
|
16
|
+
*/
|
|
17
|
+
dragPointerTypes?: Array<'mouse' | 'touch' | 'pen'>;
|
|
18
|
+
/**
|
|
19
|
+
* Pointer delta (px) before a gesture is considered a drag.
|
|
20
|
+
* @default 4
|
|
21
|
+
*/
|
|
22
|
+
dragThreshold?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Size token forwarded to arrow button components.
|
|
25
|
+
* @default 'md'
|
|
26
|
+
*/
|
|
27
|
+
arrowSize?: 'sm' | 'md' | 'lg';
|
|
28
|
+
/**
|
|
29
|
+
* Visual variant forwarded to arrow button components.
|
|
30
|
+
* @default 'neutral'
|
|
31
|
+
*/
|
|
32
|
+
arrowStyleVariant?: 'neutral' | 'white' | 'shapeFlat' | 'shapeElevated' | 'gradient';
|
|
33
|
+
/**
|
|
34
|
+
* Focus ring style forwarded to arrow button components.
|
|
35
|
+
* @default 'default'
|
|
36
|
+
*/
|
|
37
|
+
focusStyle?: 'default' | 'white';
|
|
38
|
+
/**
|
|
39
|
+
* Only show arrows when the carousel is hovered or focused.
|
|
40
|
+
* @default false
|
|
41
|
+
*/
|
|
42
|
+
showArrowsOnHover?: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* Completely hide an arrow when it is disabled (not just grey it out).
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
hideDisabledArrows?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface CarouselArrowProps {
|
|
50
|
+
disabled: boolean;
|
|
51
|
+
/** True when hideDisabledArrows is set and the button is disabled. */
|
|
52
|
+
hidden: boolean;
|
|
53
|
+
size: 'sm' | 'md' | 'lg';
|
|
54
|
+
styleVariant: NonNullable<UseCarouselOptions['arrowStyleVariant']>;
|
|
55
|
+
focusStyle: NonNullable<UseCarouselOptions['focusStyle']>;
|
|
56
|
+
}
|
|
57
|
+
export interface CarouselDragProps {
|
|
58
|
+
onPointerDown?: (e: ReactPointerEvent) => void;
|
|
59
|
+
onPointerMove?: (e: ReactPointerEvent) => void;
|
|
60
|
+
onPointerUp?: (e: ReactPointerEvent) => void;
|
|
61
|
+
onPointerLeave?: (e: ReactPointerEvent) => void;
|
|
62
|
+
onClick?: (e: ReactMouseEvent) => void;
|
|
63
|
+
style?: CSSProperties;
|
|
64
|
+
}
|
|
65
|
+
export interface UseCarouselReturn {
|
|
66
|
+
/** Attach to the scrollable track element. */
|
|
67
|
+
scrollerRef: RefObject<HTMLElement>;
|
|
68
|
+
/** Callback ref that keeps listeners in sync if the DOM node changes. */
|
|
69
|
+
setScrollerRef: RefCallback<HTMLElement>;
|
|
70
|
+
/** Whether the track can scroll further left. */
|
|
71
|
+
canScrollLeft: boolean;
|
|
72
|
+
/** Whether the track can scroll further right. */
|
|
73
|
+
canScrollRight: boolean;
|
|
74
|
+
/** Derived: canScrollLeft || canScrollRight. */
|
|
75
|
+
isOverflowing: boolean;
|
|
76
|
+
/** Scroll left by one logical page. */
|
|
77
|
+
scrollLeft: () => void;
|
|
78
|
+
/** Scroll right by one logical page. */
|
|
79
|
+
scrollRight: () => void;
|
|
80
|
+
/** Whether the item at the given index is fully visible in the viewport. */
|
|
81
|
+
isItemFullyVisible: (index: number, tolerance?: number) => boolean;
|
|
82
|
+
/** Scroll the item at the given index into view when it exists. */
|
|
83
|
+
scrollToItem: (index: number, behavior?: ScrollBehavior) => void;
|
|
84
|
+
/** Resolved props for the previous arrow button. */
|
|
85
|
+
prevArrowProps: CarouselArrowProps;
|
|
86
|
+
/** Resolved props for the next arrow button. */
|
|
87
|
+
nextArrowProps: CarouselArrowProps;
|
|
88
|
+
/**
|
|
89
|
+
* True when arrows should be visible.
|
|
90
|
+
* Always true when showArrowsOnHover is false; otherwise tracks hover/focus.
|
|
91
|
+
*/
|
|
92
|
+
arrowsVisible: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Spread onto the scroll track to enable mouse drag behaviour.
|
|
95
|
+
* Empty object when mouseDragging is false.
|
|
96
|
+
*/
|
|
97
|
+
dragProps: CarouselDragProps;
|
|
98
|
+
}
|
|
99
|
+
export declare function useCarousel(options?: UseCarouselOptions): UseCarouselReturn;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{useRef as e,useState as t,useCallback as r,useEffect as n}from"react";function o(o={}){const{spaceBetweenItems:i=0,mouseDragging:s=!0,dragPointerTypes:l=["mouse"],dragThreshold:c=4,arrowSize:u="md",arrowStyleVariant:a="neutral",focusStyle:d="default",showArrowsOnHover:f=!1,hideDisabledArrows:m=!1}=o,v=e(null),[h,g]=t(null),p=e(null),w=r(e=>{v.current=e,g(e)},[]),[b,E]=t(!1),[L,y]=t(!1),[I,T]=t(!1),[P,M]=t(!1),R=e(!1),A=e(0),C=e(0),S=e(!1),B=r(()=>{const e=v.current;if(!e)return;const t=Math.max(0,e.scrollWidth-e.clientWidth);let r=!1,n=!1;const o=Array.from(e.children).filter(e=>e instanceof HTMLElement);if(o.length>1){const t=(e.parentElement??e).getBoundingClientRect(),i=e.getBoundingClientRect(),s=Math.max(t.left,i.left),l=Math.min(t.right,i.right);let c=Number.POSITIVE_INFINITY,u=Number.NEGATIVE_INFINITY;for(const e of o){const t=e.getBoundingClientRect();t.left<c&&(c=t.left),t.right>u&&(u=t.right)}r=c<s-2,n=u>l+2}else{const o=e.scrollLeft;r=o>2,n=o<t-2}E(e=>e===r?e:r),y(e=>e===n?e:n)},[]),N=r(()=>{null===p.current&&(p.current=window.requestAnimationFrame(()=>{p.current=null,B()}))},[B]);n(()=>{const e=h;if(!e||"undefined"==typeof window)return;N();const t="undefined"!=typeof ResizeObserver,r="undefined"!=typeof MutationObserver;let n=null,o=null;return t?(n=new ResizeObserver(N),n.observe(e),e.parentElement&&n.observe(e.parentElement)):window.addEventListener("resize",N,{passive:!0}),r&&(o=new MutationObserver(N),o.observe(e,{childList:!0,subtree:!0})),e.addEventListener("scroll",N,{passive:!0}),()=>{null!==p.current&&(window.cancelAnimationFrame(p.current),p.current=null),n?.disconnect(),o?.disconnect(),t||window.removeEventListener("resize",N),e.removeEventListener("scroll",N)}},[N,h]),n(()=>{if(!f)return;const e=h;if(!e)return;const t=e.parentElement??e,r=()=>T(!0),n=()=>T(!1),o=()=>M(!0),i=e=>{t.contains(e.relatedTarget)||M(!1)};return t.addEventListener("mouseenter",r),t.addEventListener("mouseleave",n),t.addEventListener("focusin",o),t.addEventListener("focusout",i),()=>{t.removeEventListener("mouseenter",r),t.removeEventListener("mouseleave",n),t.removeEventListener("focusin",o),t.removeEventListener("focusout",i)}},[f,h]);const V=r(()=>{const e=v.current;if(!e||!b)return;const t=Number.isFinite(i)&&i>0?i:0,r=.8*e.clientWidth+t;e.scrollBy({left:-r,behavior:"smooth"})},[b,i]),z=r(()=>{const e=v.current;if(!e||!L)return;const t=Number.isFinite(i)&&i>0?i:0,r=.8*e.clientWidth+t;e.scrollBy({left:r,behavior:"smooth"})},[L,i]),F=r((e,t=1)=>{const r=v.current;if(!r)return!0;const n=r.parentElement??r,o=Array.from(r.children).filter(e=>e instanceof HTMLElement)[e];if(!o)return!0;const i=n.getBoundingClientRect(),s=r.getBoundingClientRect(),l=o.getBoundingClientRect(),c=Math.max(i.left,s.left),u=Math.min(i.right,s.right);return l.left>=c-t&&l.right<=u+t},[]),O=r((e,t="smooth")=>{const r=v.current;if(!r)return;const n=Array.from(r.children).filter(e=>e instanceof HTMLElement)[e];n&&n.scrollIntoView({block:"nearest",inline:"nearest",behavior:t})},[]);return{scrollerRef:v,setScrollerRef:w,canScrollLeft:b,canScrollRight:L,isOverflowing:b||L,scrollLeft:V,scrollRight:z,isItemFullyVisible:F,scrollToItem:O,prevArrowProps:{disabled:!b,hidden:m&&!b,size:u,styleVariant:a,focusStyle:d},nextArrowProps:{disabled:!L,hidden:m&&!L,size:u,styleVariant:a,focusStyle:d},arrowsVisible:!f||(I||P),dragProps:s?{onPointerDown:e=>{if(!l.includes(e.pointerType))return;const t=v.current;t&&(R.current=!0,S.current=!1,A.current=e.clientX,C.current=t.scrollLeft,e.currentTarget.setPointerCapture(e.pointerId))},onPointerMove:e=>{if(!R.current)return;const t=v.current;if(!t)return;const r=e.clientX-A.current;Math.abs(r)>c&&(S.current=!0),t.scrollLeft=C.current-r},onPointerUp:e=>{R.current&&(R.current=!1,e.currentTarget.releasePointerCapture(e.pointerId),N())},onPointerLeave:e=>{R.current&&(R.current=!1,e.currentTarget.releasePointerCapture(e.pointerId),N())},onClick:e=>{S.current&&(e.preventDefault(),e.stopPropagation(),S.current=!1)},style:{cursor:"grab",userSelect:"none"}}:{}}}export{o as useCarousel};
|
|
2
|
+
//# sourceMappingURL=useCarousel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCarousel.js","sources":["../../../../src/utils/carousel/hooks/useCarousel.ts"],"sourcesContent":["import {\n useRef,\n useState,\n useCallback,\n useEffect,\n RefObject,\n PointerEvent as ReactPointerEvent,\n MouseEvent as ReactMouseEvent,\n CSSProperties,\n RefCallback,\n} from 'react';\n\n// ─── Types ────────────────────────────────────────────────────────────────────\n\nexport interface UseCarouselOptions {\n /**\n * Space between items (px). Used when computing per-item scroll steps.\n * @default 0\n */\n spaceBetweenItems?: number;\n\n /**\n * Enable pointer/mouse drag-to-scroll.\n * @default true\n */\n mouseDragging?: boolean;\n\n /**\n * Pointer types that can initiate drag-to-scroll.\n * @default ['mouse']\n */\n dragPointerTypes?: Array<'mouse' | 'touch' | 'pen'>;\n\n /**\n * Pointer delta (px) before a gesture is considered a drag.\n * @default 4\n */\n dragThreshold?: number;\n\n /**\n * Size token forwarded to arrow button components.\n * @default 'md'\n */\n arrowSize?: 'sm' | 'md' | 'lg';\n\n /**\n * Visual variant forwarded to arrow button components.\n * @default 'neutral'\n */\n arrowStyleVariant?:\n | 'neutral'\n | 'white'\n | 'shapeFlat'\n | 'shapeElevated'\n | 'gradient';\n\n /**\n * Focus ring style forwarded to arrow button components.\n * @default 'default'\n */\n focusStyle?: 'default' | 'white';\n\n /**\n * Only show arrows when the carousel is hovered or focused.\n * @default false\n */\n showArrowsOnHover?: boolean;\n\n /**\n * Completely hide an arrow when it is disabled (not just grey it out).\n * @default false\n */\n hideDisabledArrows?: boolean;\n}\n\nexport interface CarouselArrowProps {\n disabled: boolean;\n /** True when hideDisabledArrows is set and the button is disabled. */\n hidden: boolean;\n size: 'sm' | 'md' | 'lg';\n styleVariant: NonNullable<UseCarouselOptions['arrowStyleVariant']>;\n focusStyle: NonNullable<UseCarouselOptions['focusStyle']>;\n}\n\nexport interface CarouselDragProps {\n onPointerDown?: (e: ReactPointerEvent) => void;\n onPointerMove?: (e: ReactPointerEvent) => void;\n onPointerUp?: (e: ReactPointerEvent) => void;\n onPointerLeave?: (e: ReactPointerEvent) => void;\n onClick?: (e: ReactMouseEvent) => void;\n style?: CSSProperties;\n}\n\nexport interface UseCarouselReturn {\n /** Attach to the scrollable track element. */\n scrollerRef: RefObject<HTMLElement>;\n\n /** Callback ref that keeps listeners in sync if the DOM node changes. */\n setScrollerRef: RefCallback<HTMLElement>;\n\n /** Whether the track can scroll further left. */\n canScrollLeft: boolean;\n\n /** Whether the track can scroll further right. */\n canScrollRight: boolean;\n\n /** Derived: canScrollLeft || canScrollRight. */\n isOverflowing: boolean;\n\n /** Scroll left by one logical page. */\n scrollLeft: () => void;\n\n /** Scroll right by one logical page. */\n scrollRight: () => void;\n\n /** Whether the item at the given index is fully visible in the viewport. */\n isItemFullyVisible: (index: number, tolerance?: number) => boolean;\n\n /** Scroll the item at the given index into view when it exists. */\n scrollToItem: (index: number, behavior?: ScrollBehavior) => void;\n\n /** Resolved props for the previous arrow button. */\n prevArrowProps: CarouselArrowProps;\n\n /** Resolved props for the next arrow button. */\n nextArrowProps: CarouselArrowProps;\n\n /**\n * True when arrows should be visible.\n * Always true when showArrowsOnHover is false; otherwise tracks hover/focus.\n */\n arrowsVisible: boolean;\n\n /**\n * Spread onto the scroll track to enable mouse drag behaviour.\n * Empty object when mouseDragging is false.\n */\n dragProps: CarouselDragProps;\n}\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\n/** px used as epsilon near scroll boundaries to avoid flicker. */\nconst SCROLL_THRESHOLD = 2;\n\n// ─── Hook ─────────────────────────────────────────────────────────────────────\n\nexport function useCarousel(\n options: UseCarouselOptions = {},\n): UseCarouselReturn {\n const {\n spaceBetweenItems = 0,\n mouseDragging = true,\n dragPointerTypes = ['mouse'],\n dragThreshold = 4,\n arrowSize = 'md',\n arrowStyleVariant = 'neutral',\n focusStyle = 'default',\n showArrowsOnHover = false,\n hideDisabledArrows = false,\n } = options;\n\n const scrollerRef = useRef<HTMLElement | null>(null);\n const [scrollerElement, setScrollerElement] = useState<HTMLElement | null>(\n null,\n );\n const rafIdRef = useRef<number | null>(null);\n\n const setScrollerRef = useCallback<RefCallback<HTMLElement>>((node) => {\n scrollerRef.current = node;\n setScrollerElement(node);\n }, []);\n\n // ── Overflow state ────────────────────────────────────────────────────────\n const [canScrollLeft, setCanScrollLeft] = useState(false);\n const [canScrollRight, setCanScrollRight] = useState(false);\n\n // ── Hover/focus for arrow visibility ─────────────────────────────────────\n const [isHovered, setIsHovered] = useState(false);\n const [isFocused, setIsFocused] = useState(false);\n\n // ── Drag state ────────────────────────────────────────────────────────────\n const isDraggingRef = useRef(false);\n const dragStartXRef = useRef(0);\n const dragScrollLeftRef = useRef(0);\n const didDragRef = useRef(false);\n\n // ── Overflow measurement ──────────────────────────────────────────────────\n\n const measureOverflow = useCallback(() => {\n const el = scrollerRef.current;\n if (!el) return;\n\n // In fallback mode we rely on the scroller's native range, since scrolling\n // is performed on the element itself.\n const maxScrollLeft = Math.max(0, el.scrollWidth - el.clientWidth);\n\n let nextCanScrollLeft = false;\n let nextCanScrollRight = false;\n\n const children = Array.from(el.children).filter(\n (child): child is HTMLElement => child instanceof HTMLElement,\n );\n if (children.length > 1) {\n const viewport = el.parentElement ?? el;\n const viewportRect = viewport.getBoundingClientRect();\n const scrollerRect = el.getBoundingClientRect();\n const visibleLeft = Math.max(viewportRect.left, scrollerRect.left);\n const visibleRight = Math.min(viewportRect.right, scrollerRect.right);\n\n let leftMost = Number.POSITIVE_INFINITY;\n let rightMost = Number.NEGATIVE_INFINITY;\n for (const child of children) {\n const rect = child.getBoundingClientRect();\n if (rect.left < leftMost) leftMost = rect.left;\n if (rect.right > rightMost) rightMost = rect.right;\n }\n\n nextCanScrollLeft = leftMost < visibleLeft - SCROLL_THRESHOLD;\n nextCanScrollRight = rightMost > visibleRight + SCROLL_THRESHOLD;\n } else {\n // Geometry fallback for simple/non-itemized scrollers.\n const scrollLeft = el.scrollLeft;\n nextCanScrollLeft = scrollLeft > SCROLL_THRESHOLD;\n nextCanScrollRight = scrollLeft < maxScrollLeft - SCROLL_THRESHOLD;\n }\n\n setCanScrollLeft((prev) =>\n prev === nextCanScrollLeft ? prev : nextCanScrollLeft,\n );\n setCanScrollRight((prev) =>\n prev === nextCanScrollRight ? prev : nextCanScrollRight,\n );\n }, []);\n\n const scheduleMeasure = useCallback(() => {\n if (rafIdRef.current !== null) return;\n rafIdRef.current = window.requestAnimationFrame(() => {\n rafIdRef.current = null;\n measureOverflow();\n });\n }, [measureOverflow]);\n\n // ── Setup observers and listeners ─────────────────────────────────────────\n\n useEffect(() => {\n const el = scrollerElement;\n if (!el || typeof window === 'undefined') return;\n\n // Initial measurement\n scheduleMeasure();\n\n const canUseResizeObserver = typeof ResizeObserver !== 'undefined';\n const canUseMutationObserver = typeof MutationObserver !== 'undefined';\n\n let ro: ResizeObserver | null = null;\n let mo: MutationObserver | null = null;\n\n // ResizeObserver watches the scroller and its parent\n if (canUseResizeObserver) {\n ro = new ResizeObserver(scheduleMeasure);\n ro.observe(el);\n if (el.parentElement) ro.observe(el.parentElement);\n } else {\n // Fallback for environments without ResizeObserver support.\n window.addEventListener('resize', scheduleMeasure, { passive: true });\n }\n\n // MutationObserver watches childList/subtree DOM mutations.\n if (canUseMutationObserver) {\n mo = new MutationObserver(scheduleMeasure);\n mo.observe(el, { childList: true, subtree: true });\n }\n\n el.addEventListener('scroll', scheduleMeasure, { passive: true });\n\n return () => {\n if (rafIdRef.current !== null) {\n window.cancelAnimationFrame(rafIdRef.current);\n rafIdRef.current = null;\n }\n ro?.disconnect();\n mo?.disconnect();\n if (!canUseResizeObserver) {\n window.removeEventListener('resize', scheduleMeasure);\n }\n el.removeEventListener('scroll', scheduleMeasure);\n };\n }, [scheduleMeasure, scrollerElement]);\n\n // ── Hover/focus tracking for arrowsVisible ────────────────────────────────\n\n useEffect(() => {\n if (!showArrowsOnHover) return;\n\n const el = scrollerElement;\n if (!el) return;\n\n const container = el.parentElement ?? el;\n\n const onMouseEnter = () => setIsHovered(true);\n const onMouseLeave = () => setIsHovered(false);\n const onFocusIn = () => setIsFocused(true);\n const onFocusOut = (e: FocusEvent) => {\n if (!container.contains(e.relatedTarget as Node | null)) {\n setIsFocused(false);\n }\n };\n\n container.addEventListener('mouseenter', onMouseEnter);\n container.addEventListener('mouseleave', onMouseLeave);\n container.addEventListener('focusin', onFocusIn);\n container.addEventListener('focusout', onFocusOut);\n\n return () => {\n container.removeEventListener('mouseenter', onMouseEnter);\n container.removeEventListener('mouseleave', onMouseLeave);\n container.removeEventListener('focusin', onFocusIn);\n container.removeEventListener('focusout', onFocusOut);\n };\n }, [showArrowsOnHover, scrollerElement]);\n\n // ── Scroll actions ────────────────────────────────────────────────────────\n\n const scrollLeft = useCallback(() => {\n const el = scrollerRef.current;\n if (!el || !canScrollLeft) return;\n\n // Include configured item gap so each step better aligns with spaced layouts.\n const normalizedGap =\n Number.isFinite(spaceBetweenItems) && spaceBetweenItems > 0\n ? spaceBetweenItems\n : 0;\n const scrollAmount = el.clientWidth * 0.8 + normalizedGap;\n\n el.scrollBy({ left: -scrollAmount, behavior: 'smooth' });\n }, [canScrollLeft, spaceBetweenItems]);\n\n const scrollRight = useCallback(() => {\n const el = scrollerRef.current;\n if (!el || !canScrollRight) return;\n\n // Include configured item gap so each step better aligns with spaced layouts.\n const normalizedGap =\n Number.isFinite(spaceBetweenItems) && spaceBetweenItems > 0\n ? spaceBetweenItems\n : 0;\n const scrollAmount = el.clientWidth * 0.8 + normalizedGap;\n\n el.scrollBy({ left: scrollAmount, behavior: 'smooth' });\n }, [canScrollRight, spaceBetweenItems]);\n\n const isItemFullyVisible = useCallback(\n (index: number, tolerance = 1): boolean => {\n const el = scrollerRef.current;\n if (!el) return true;\n\n const viewport = el.parentElement ?? el;\n const itemElements = Array.from(el.children).filter(\n (child): child is HTMLElement => child instanceof HTMLElement,\n );\n const target = itemElements[index];\n\n if (!target) return true;\n\n const viewportRect = viewport.getBoundingClientRect();\n const scrollerRect = el.getBoundingClientRect();\n const itemRect = target.getBoundingClientRect();\n\n const visibleLeft = Math.max(viewportRect.left, scrollerRect.left);\n const visibleRight = Math.min(viewportRect.right, scrollerRect.right);\n\n return (\n itemRect.left >= visibleLeft - tolerance &&\n itemRect.right <= visibleRight + tolerance\n );\n },\n [],\n );\n\n const scrollToItem = useCallback(\n (index: number, behavior: ScrollBehavior = 'smooth') => {\n const el = scrollerRef.current;\n if (!el) return;\n\n const itemElements = Array.from(el.children).filter(\n (child): child is HTMLElement => child instanceof HTMLElement,\n );\n const target = itemElements[index];\n\n if (!target) return;\n\n target.scrollIntoView({\n block: 'nearest',\n inline: 'nearest',\n behavior,\n });\n },\n [],\n );\n\n // ── Drag handlers ─────────────────────────────────────────────────────────\n\n const dragProps: CarouselDragProps = mouseDragging\n ? {\n onPointerDown: (e: ReactPointerEvent) => {\n if (\n !dragPointerTypes.includes(\n e.pointerType as 'mouse' | 'touch' | 'pen',\n )\n ) {\n return;\n }\n const el = scrollerRef.current;\n if (!el) return;\n isDraggingRef.current = true;\n didDragRef.current = false;\n dragStartXRef.current = e.clientX;\n dragScrollLeftRef.current = el.scrollLeft;\n (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);\n },\n onPointerMove: (e: ReactPointerEvent) => {\n if (!isDraggingRef.current) return;\n const el = scrollerRef.current;\n if (!el) return;\n const delta = e.clientX - dragStartXRef.current;\n if (Math.abs(delta) > dragThreshold) {\n didDragRef.current = true;\n }\n el.scrollLeft = dragScrollLeftRef.current - delta;\n },\n onPointerUp: (e: ReactPointerEvent) => {\n if (!isDraggingRef.current) return;\n isDraggingRef.current = false;\n (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);\n scheduleMeasure();\n },\n onPointerLeave: (e: ReactPointerEvent) => {\n if (!isDraggingRef.current) return;\n isDraggingRef.current = false;\n (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);\n scheduleMeasure();\n },\n onClick: (e: ReactMouseEvent) => {\n // Suppress the click that fires immediately after a drag gesture\n if (didDragRef.current) {\n e.preventDefault();\n e.stopPropagation();\n didDragRef.current = false;\n }\n },\n style: { cursor: 'grab', userSelect: 'none' },\n }\n : {};\n\n // ── Derived values ────────────────────────────────────────────────────────\n\n const arrowsVisible = showArrowsOnHover ? isHovered || isFocused : true;\n\n const prevArrowProps: CarouselArrowProps = {\n disabled: !canScrollLeft,\n hidden: hideDisabledArrows && !canScrollLeft,\n size: arrowSize,\n styleVariant: arrowStyleVariant,\n focusStyle,\n };\n\n const nextArrowProps: CarouselArrowProps = {\n disabled: !canScrollRight,\n hidden: hideDisabledArrows && !canScrollRight,\n size: arrowSize,\n styleVariant: arrowStyleVariant,\n focusStyle,\n };\n\n return {\n scrollerRef,\n setScrollerRef,\n canScrollLeft,\n canScrollRight,\n isOverflowing: canScrollLeft || canScrollRight,\n scrollLeft,\n scrollRight,\n isItemFullyVisible,\n scrollToItem,\n prevArrowProps,\n nextArrowProps,\n arrowsVisible,\n dragProps,\n };\n}\n"],"names":["useCarousel","options","spaceBetweenItems","mouseDragging","dragPointerTypes","dragThreshold","arrowSize","arrowStyleVariant","focusStyle","showArrowsOnHover","hideDisabledArrows","scrollerRef","useRef","scrollerElement","setScrollerElement","useState","rafIdRef","setScrollerRef","useCallback","node","current","canScrollLeft","setCanScrollLeft","canScrollRight","setCanScrollRight","isHovered","setIsHovered","isFocused","setIsFocused","isDraggingRef","dragStartXRef","dragScrollLeftRef","didDragRef","measureOverflow","el","maxScrollLeft","Math","max","scrollWidth","clientWidth","nextCanScrollLeft","nextCanScrollRight","children","Array","from","filter","child","HTMLElement","length","viewportRect","parentElement","getBoundingClientRect","scrollerRect","visibleLeft","left","visibleRight","min","right","leftMost","Number","POSITIVE_INFINITY","rightMost","NEGATIVE_INFINITY","rect","scrollLeft","prev","scheduleMeasure","window","requestAnimationFrame","useEffect","canUseResizeObserver","ResizeObserver","canUseMutationObserver","MutationObserver","ro","mo","observe","addEventListener","passive","childList","subtree","cancelAnimationFrame","disconnect","removeEventListener","container","onMouseEnter","onMouseLeave","onFocusIn","onFocusOut","e","contains","relatedTarget","normalizedGap","isFinite","scrollAmount","scrollBy","behavior","scrollRight","isItemFullyVisible","index","tolerance","viewport","target","itemRect","scrollToItem","scrollIntoView","block","inline","isOverflowing","prevArrowProps","disabled","hidden","size","styleVariant","nextArrowProps","arrowsVisible","dragProps","onPointerDown","includes","pointerType","clientX","currentTarget","setPointerCapture","pointerId","onPointerMove","delta","abs","onPointerUp","releasePointerCapture","onPointerLeave","onClick","preventDefault","stopPropagation","style","cursor","userSelect"],"mappings":"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"}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { useBreakpoint } from './breakpoint/hooks/useBreakpoint';
|
|
2
|
+
export { useCarousel } from './carousel/hooks/useCarousel';
|
|
3
|
+
export type { UseCarouselOptions, UseCarouselReturn, CarouselArrowProps, CarouselDragProps, } from './carousel/hooks/useCarousel';
|
|
2
4
|
export type { Breakpoint } from './breakpoint/theme/breakpoints';
|
|
3
5
|
export { BREAKPOINTS } from './breakpoint/theme/breakpoints';
|
|
4
6
|
export { useFitCount } from './layout/hooks/useFitCount';
|
package/dist/utils/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{useBreakpoint as o}from"./breakpoint/hooks/useBreakpoint.js";import{BREAKPOINTS as
|
|
1
|
+
import{useBreakpoint as o}from"./breakpoint/hooks/useBreakpoint.js";import{useCarousel as r}from"./carousel/hooks/useCarousel.js";import{BREAKPOINTS as s}from"./breakpoint/theme/breakpoints.js";import{useFitCount as t}from"./layout/hooks/useFitCount.js";export{s as BREAKPOINTS,o as useBreakpoint,r as useCarousel,t as useFitCount};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useFitCount.js","sources":["../../../../src/utils/layout/hooks/useFitCount.ts"],"sourcesContent":["import {\n RefObject,\n useCallback,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\n\nconst useIsomorphicLayoutEffect =\n typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\nexport interface UseFitCountOptions<\n TContainer extends HTMLElement,\n TMeasure extends HTMLElement,\n> {\n /**\n * Ref for the element whose available width should be measured.\n */\n containerRef: RefObject<TContainer>;\n /**\n * Ref for a representative item used to measure item width.\n */\n measureRef?: RefObject<TMeasure>;\n /**\n * Selector used to find the container that provides the gap styles.\n * Looked up within containerRef.\n */\n measureContainerSelector?: string;\n /**\n * Selector used to find the representative item used for width measurement.\n * Looked up within the measure container first, then within containerRef.\n */\n measureItemSelector?: string;\n /**\n * Selector used to find the element whose content-box width should be used\n * as the available width for fit calculations.\n * Looked up within containerRef.\n */\n availableWidthSelector?: string;\n /**\n * Total number of items that may be displayed.\n */\n itemCount: number;\n /**\n * Minimum number of items that should fit.\n * @default 1\n */\n minFitCount?: number;\n /**\n * Maximum number of items that should fit.\n * Defaults to itemCount.\n */\n maxFitCount?: number;\n /**\n * Fallback horizontal gap (px) to use when computed gap resolves to 0.\n * @default 0\n */\n fallbackGap?: number;\n}\n\nexport interface UseFitCountResult {\n /**\n * Number of whole items that fit in the available width.\n */\n fitCount: number;\n /**\n * Exact (possibly decimal) number of items that fit in the container width.\n * Useful for fixed-width carousel items that should not scale.\n */\n itemsPerPage: number;\n /**\n * Resolved horizontal gap between items in pixels.\n */\n gap: number;\n /**\n * Whether the total number of items exceeds the fit count.\n */\n isOverflowing: boolean;\n /**\n * Whether an initial measurement has completed.\n */\n isReady: boolean;\n /**\n * Triggers a manual recalculation.\n */\n recalculate: () => void;\n}\n\nexport const useFitCount = <\n TContainer extends HTMLElement,\n TMeasure extends HTMLElement,\n>({\n containerRef,\n measureRef,\n measureContainerSelector,\n measureItemSelector,\n availableWidthSelector,\n itemCount,\n minFitCount = 1,\n maxFitCount,\n fallbackGap = 0,\n}: UseFitCountOptions<TContainer, TMeasure>): UseFitCountResult => {\n const lastMeasuredItemWidthRef = useRef(0);\n const lastMeasuredGapRef = useRef(0);\n const [fitCount, setFitCount] = useState(() =>\n itemCount === 0 ? 0 : Math.max(minFitCount, 1),\n );\n const [itemsPerPage, setItemsPerPage] = useState(() =>\n itemCount === 0 ? 0 : Math.max(minFitCount, 1),\n );\n const [gap, setGap] = useState(0);\n const [isReady, setIsReady] = useState(false);\n\n const getContentBoxWidth = useCallback((element: HTMLElement): number => {\n const rectWidth =\n element.getBoundingClientRect().width ||\n element.clientWidth ||\n element.offsetWidth;\n\n if (typeof window === 'undefined') {\n return rectWidth;\n }\n\n const computedStyle = window.getComputedStyle(element);\n const paddingLeft = parseFloat(computedStyle.paddingLeft || '0') || 0;\n const paddingRight = parseFloat(computedStyle.paddingRight || '0') || 0;\n const borderLeft = parseFloat(computedStyle.borderLeftWidth || '0') || 0;\n const borderRight = parseFloat(computedStyle.borderRightWidth || '0') || 0;\n const contentWidth =\n rectWidth - paddingLeft - paddingRight - borderLeft - borderRight;\n\n return contentWidth > 0 ? contentWidth : rectWidth;\n }, []);\n\n const recalculate = useCallback(() => {\n if (itemCount === 0) {\n setFitCount(0);\n setItemsPerPage(0);\n setGap(0);\n setIsReady(true);\n return;\n }\n\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n const measureContainer =\n (measureContainerSelector\n ? container?.querySelector<HTMLElement>(measureContainerSelector)\n : null) ??\n measureRef?.current?.parentElement ??\n null;\n const availableWidthElement =\n (availableWidthSelector\n ? container.querySelector<HTMLElement>(availableWidthSelector)\n : null) ??\n measureContainer ??\n container;\n const measureItem =\n measureRef?.current ??\n (measureItemSelector\n ? (measureContainer?.querySelector<TMeasure>(measureItemSelector) ??\n container?.querySelector<TMeasure>(measureItemSelector))\n : null) ??\n null;\n\n let availableWidth = getContentBoxWidth(availableWidthElement);\n\n if (availableWidth <= 0 && availableWidthElement !== container) {\n availableWidth = getContentBoxWidth(container);\n }\n\n const itemWidth =\n measureItem?.getBoundingClientRect().width ||\n measureItem?.offsetWidth ||\n lastMeasuredItemWidthRef.current;\n const measuredGap = measureContainer\n ? typeof window !== 'undefined'\n ? parseFloat(\n window.getComputedStyle(measureContainer).columnGap ||\n window.getComputedStyle(measureContainer).gap ||\n '0',\n ) || 0\n : lastMeasuredGapRef.current\n : lastMeasuredGapRef.current;\n const resolvedGap = measuredGap > 0 ? measuredGap : fallbackGap;\n\n if (measureItem && itemWidth > 0) {\n lastMeasuredItemWidthRef.current = itemWidth;\n }\n\n if (measureContainer) {\n lastMeasuredGapRef.current = resolvedGap;\n }\n\n if (itemWidth <= 0) {\n return;\n }\n\n if (availableWidth <= 0 || itemWidth <= 0) {\n return;\n }\n\n const rawItemsPerPage =\n (availableWidth + resolvedGap) / (itemWidth + resolvedGap);\n\n const rawFitCount = Math.floor(\n (availableWidth + resolvedGap) / (itemWidth + resolvedGap),\n );\n const clampedItemsPerPage = Math.min(\n maxFitCount ?? itemCount,\n Math.max(minFitCount, rawItemsPerPage, 1),\n );\n const clampedFitCount = Math.min(\n maxFitCount ?? itemCount,\n Math.max(minFitCount, rawFitCount, 1),\n );\n\n setFitCount((previousFitCount) =>\n previousFitCount === clampedFitCount ? previousFitCount : clampedFitCount,\n );\n setItemsPerPage((previousItemsPerPage) =>\n previousItemsPerPage === clampedItemsPerPage\n ? previousItemsPerPage\n : clampedItemsPerPage,\n );\n setGap((previousGap) =>\n previousGap === resolvedGap ? previousGap : resolvedGap,\n );\n setIsReady(true);\n }, [\n containerRef,\n itemCount,\n maxFitCount,\n measureContainerSelector,\n measureItemSelector,\n availableWidthSelector,\n measureRef,\n minFitCount,\n fallbackGap,\n getContentBoxWidth,\n ]);\n\n useIsomorphicLayoutEffect(() => {\n recalculate();\n }, [recalculate]);\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const handleWindowResize = () => {\n recalculate();\n };\n\n window.addEventListener('resize', handleWindowResize, { passive: true });\n\n return () => {\n window.removeEventListener('resize', handleWindowResize);\n };\n }, [recalculate]);\n\n useEffect(() => {\n const container = containerRef.current;\n const measureContainer =\n (measureContainerSelector\n ? container?.querySelector<HTMLElement>(measureContainerSelector)\n : null) ??\n measureRef?.current?.parentElement ??\n null;\n const availableWidthElement =\n (availableWidthSelector\n ? container?.querySelector<HTMLElement>(availableWidthSelector)\n : null) ??\n measureContainer ??\n null;\n const measureItem =\n measureRef?.current ??\n (measureItemSelector\n ? (measureContainer?.querySelector<TMeasure>(measureItemSelector) ??\n container?.querySelector<TMeasure>(measureItemSelector))\n : null) ??\n null;\n\n if (!container || typeof ResizeObserver === 'undefined') {\n return;\n }\n\n const resizeObserver = new ResizeObserver(() => {\n recalculate();\n });\n\n resizeObserver.observe(container);\n if (measureItem) {\n resizeObserver.observe(measureItem);\n }\n\n if (measureContainer && measureContainer !== measureItem) {\n resizeObserver.observe(measureContainer);\n }\n\n if (\n availableWidthElement &&\n availableWidthElement !== container &&\n availableWidthElement !== measureContainer &&\n availableWidthElement !== measureItem\n ) {\n resizeObserver.observe(availableWidthElement);\n }\n\n return () => {\n resizeObserver.disconnect();\n };\n }, [\n containerRef,\n measureContainerSelector,\n measureItemSelector,\n availableWidthSelector,\n measureRef,\n recalculate,\n ]);\n\n return {\n fitCount,\n itemsPerPage,\n gap,\n isOverflowing: itemCount > fitCount,\n isReady,\n recalculate,\n };\n};\n"],"names":["useIsomorphicLayoutEffect","window","useLayoutEffect","useEffect","useFitCount","containerRef","measureRef","measureContainerSelector","measureItemSelector","availableWidthSelector","itemCount","minFitCount","maxFitCount","fallbackGap","lastMeasuredItemWidthRef","useRef","lastMeasuredGapRef","fitCount","setFitCount","useState","Math","max","itemsPerPage","setItemsPerPage","gap","setGap","isReady","setIsReady","getContentBoxWidth","useCallback","element","rectWidth","getBoundingClientRect","width","clientWidth","offsetWidth","computedStyle","getComputedStyle","contentWidth","parseFloat","paddingLeft","paddingRight","borderLeftWidth","borderRightWidth","recalculate","container","current","measureContainer","querySelector","parentElement","availableWidthElement","measureItem","availableWidth","itemWidth","measuredGap","columnGap","resolvedGap","rawItemsPerPage","rawFitCount","floor","clampedItemsPerPage","min","clampedFitCount","previousFitCount","previousItemsPerPage","previousGap","handleWindowResize","addEventListener","passive","removeEventListener","ResizeObserver","resizeObserver","observe","disconnect","isOverflowing"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useFitCount.js","sources":["../../../../src/utils/layout/hooks/useFitCount.ts"],"sourcesContent":["import {\n RefObject,\n useCallback,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react';\n\n/**\n * useFitCount calculates how many fixed-width items fit within available\n * horizontal space before rendering/interaction decisions are made.\n *\n * It is a layout-capacity hook (fit count, fractional items per page, gap,\n * overflow readiness), not a carousel interaction hook.\n */\n\nconst useIsomorphicLayoutEffect =\n typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\nexport interface UseFitCountOptions<\n TContainer extends HTMLElement,\n TMeasure extends HTMLElement,\n> {\n /**\n * Ref for the element whose available width should be measured.\n */\n containerRef: RefObject<TContainer>;\n /**\n * Ref for a representative item used to measure item width.\n */\n measureRef?: RefObject<TMeasure>;\n /**\n * Selector used to find the container that provides the gap styles.\n * Looked up within containerRef.\n */\n measureContainerSelector?: string;\n /**\n * Selector used to find the representative item used for width measurement.\n * Looked up within the measure container first, then within containerRef.\n */\n measureItemSelector?: string;\n /**\n * Selector used to find the element whose content-box width should be used\n * as the available width for fit calculations.\n * Looked up within containerRef.\n */\n availableWidthSelector?: string;\n /**\n * Total number of items that may be displayed.\n */\n itemCount: number;\n /**\n * Minimum number of items that should fit.\n * @default 1\n */\n minFitCount?: number;\n /**\n * Maximum number of items that should fit.\n * Defaults to itemCount.\n */\n maxFitCount?: number;\n /**\n * Fallback horizontal gap (px) to use when computed gap resolves to 0.\n * @default 0\n */\n fallbackGap?: number;\n}\n\nexport interface UseFitCountResult {\n /**\n * Number of whole items that fit in the available width.\n */\n fitCount: number;\n /**\n * Exact (possibly decimal) number of items that fit in the container width.\n * Useful for fixed-width carousel items that should not scale.\n */\n itemsPerPage: number;\n /**\n * Resolved horizontal gap between items in pixels.\n */\n gap: number;\n /**\n * Whether the total number of items exceeds the fit count.\n */\n isOverflowing: boolean;\n /**\n * Whether an initial measurement has completed.\n */\n isReady: boolean;\n /**\n * Triggers a manual recalculation.\n */\n recalculate: () => void;\n}\n\nexport const useFitCount = <\n TContainer extends HTMLElement,\n TMeasure extends HTMLElement,\n>({\n containerRef,\n measureRef,\n measureContainerSelector,\n measureItemSelector,\n availableWidthSelector,\n itemCount,\n minFitCount = 1,\n maxFitCount,\n fallbackGap = 0,\n}: UseFitCountOptions<TContainer, TMeasure>): UseFitCountResult => {\n const lastMeasuredItemWidthRef = useRef(0);\n const lastMeasuredGapRef = useRef(0);\n const [fitCount, setFitCount] = useState(() =>\n itemCount === 0 ? 0 : Math.max(minFitCount, 1),\n );\n const [itemsPerPage, setItemsPerPage] = useState(() =>\n itemCount === 0 ? 0 : Math.max(minFitCount, 1),\n );\n const [gap, setGap] = useState(0);\n const [isReady, setIsReady] = useState(false);\n\n const getContentBoxWidth = useCallback((element: HTMLElement): number => {\n const rectWidth =\n element.getBoundingClientRect().width ||\n element.clientWidth ||\n element.offsetWidth;\n\n if (typeof window === 'undefined') {\n return rectWidth;\n }\n\n const computedStyle = window.getComputedStyle(element);\n const paddingLeft = parseFloat(computedStyle.paddingLeft || '0') || 0;\n const paddingRight = parseFloat(computedStyle.paddingRight || '0') || 0;\n const borderLeft = parseFloat(computedStyle.borderLeftWidth || '0') || 0;\n const borderRight = parseFloat(computedStyle.borderRightWidth || '0') || 0;\n const contentWidth =\n rectWidth - paddingLeft - paddingRight - borderLeft - borderRight;\n\n return contentWidth > 0 ? contentWidth : rectWidth;\n }, []);\n\n const recalculate = useCallback(() => {\n if (itemCount === 0) {\n setFitCount(0);\n setItemsPerPage(0);\n setGap(0);\n setIsReady(true);\n return;\n }\n\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n const measureContainer =\n (measureContainerSelector\n ? container?.querySelector<HTMLElement>(measureContainerSelector)\n : null) ??\n measureRef?.current?.parentElement ??\n null;\n const availableWidthElement =\n (availableWidthSelector\n ? container.querySelector<HTMLElement>(availableWidthSelector)\n : null) ??\n measureContainer ??\n container;\n const measureItem =\n measureRef?.current ??\n (measureItemSelector\n ? (measureContainer?.querySelector<TMeasure>(measureItemSelector) ??\n container?.querySelector<TMeasure>(measureItemSelector))\n : null) ??\n null;\n\n let availableWidth = getContentBoxWidth(availableWidthElement);\n\n if (availableWidth <= 0 && availableWidthElement !== container) {\n availableWidth = getContentBoxWidth(container);\n }\n\n const itemWidth =\n measureItem?.getBoundingClientRect().width ||\n measureItem?.offsetWidth ||\n lastMeasuredItemWidthRef.current;\n const measuredGap = measureContainer\n ? typeof window !== 'undefined'\n ? parseFloat(\n window.getComputedStyle(measureContainer).columnGap ||\n window.getComputedStyle(measureContainer).gap ||\n '0',\n ) || 0\n : lastMeasuredGapRef.current\n : lastMeasuredGapRef.current;\n const resolvedGap = measuredGap > 0 ? measuredGap : fallbackGap;\n\n if (measureItem && itemWidth > 0) {\n lastMeasuredItemWidthRef.current = itemWidth;\n }\n\n if (measureContainer) {\n lastMeasuredGapRef.current = resolvedGap;\n }\n\n if (itemWidth <= 0) {\n return;\n }\n\n if (availableWidth <= 0 || itemWidth <= 0) {\n return;\n }\n\n const rawItemsPerPage =\n (availableWidth + resolvedGap) / (itemWidth + resolvedGap);\n\n const rawFitCount = Math.floor(\n (availableWidth + resolvedGap) / (itemWidth + resolvedGap),\n );\n const clampedItemsPerPage = Math.min(\n maxFitCount ?? itemCount,\n Math.max(minFitCount, rawItemsPerPage, 1),\n );\n const clampedFitCount = Math.min(\n maxFitCount ?? itemCount,\n Math.max(minFitCount, rawFitCount, 1),\n );\n\n setFitCount((previousFitCount) =>\n previousFitCount === clampedFitCount ? previousFitCount : clampedFitCount,\n );\n setItemsPerPage((previousItemsPerPage) =>\n previousItemsPerPage === clampedItemsPerPage\n ? previousItemsPerPage\n : clampedItemsPerPage,\n );\n setGap((previousGap) =>\n previousGap === resolvedGap ? previousGap : resolvedGap,\n );\n setIsReady(true);\n }, [\n containerRef,\n itemCount,\n maxFitCount,\n measureContainerSelector,\n measureItemSelector,\n availableWidthSelector,\n measureRef,\n minFitCount,\n fallbackGap,\n getContentBoxWidth,\n ]);\n\n useIsomorphicLayoutEffect(() => {\n recalculate();\n }, [recalculate]);\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const handleWindowResize = () => {\n recalculate();\n };\n\n window.addEventListener('resize', handleWindowResize, { passive: true });\n\n return () => {\n window.removeEventListener('resize', handleWindowResize);\n };\n }, [recalculate]);\n\n useEffect(() => {\n const container = containerRef.current;\n const measureContainer =\n (measureContainerSelector\n ? container?.querySelector<HTMLElement>(measureContainerSelector)\n : null) ??\n measureRef?.current?.parentElement ??\n null;\n const availableWidthElement =\n (availableWidthSelector\n ? container?.querySelector<HTMLElement>(availableWidthSelector)\n : null) ??\n measureContainer ??\n null;\n const measureItem =\n measureRef?.current ??\n (measureItemSelector\n ? (measureContainer?.querySelector<TMeasure>(measureItemSelector) ??\n container?.querySelector<TMeasure>(measureItemSelector))\n : null) ??\n null;\n\n if (!container || typeof ResizeObserver === 'undefined') {\n return;\n }\n\n const resizeObserver = new ResizeObserver(() => {\n recalculate();\n });\n\n resizeObserver.observe(container);\n if (measureItem) {\n resizeObserver.observe(measureItem);\n }\n\n if (measureContainer && measureContainer !== measureItem) {\n resizeObserver.observe(measureContainer);\n }\n\n if (\n availableWidthElement &&\n availableWidthElement !== container &&\n availableWidthElement !== measureContainer &&\n availableWidthElement !== measureItem\n ) {\n resizeObserver.observe(availableWidthElement);\n }\n\n return () => {\n resizeObserver.disconnect();\n };\n }, [\n containerRef,\n measureContainerSelector,\n measureItemSelector,\n availableWidthSelector,\n measureRef,\n recalculate,\n ]);\n\n return {\n fitCount,\n itemsPerPage,\n gap,\n isOverflowing: itemCount > fitCount,\n isReady,\n recalculate,\n };\n};\n"],"names":["useIsomorphicLayoutEffect","window","useLayoutEffect","useEffect","useFitCount","containerRef","measureRef","measureContainerSelector","measureItemSelector","availableWidthSelector","itemCount","minFitCount","maxFitCount","fallbackGap","lastMeasuredItemWidthRef","useRef","lastMeasuredGapRef","fitCount","setFitCount","useState","Math","max","itemsPerPage","setItemsPerPage","gap","setGap","isReady","setIsReady","getContentBoxWidth","useCallback","element","rectWidth","getBoundingClientRect","width","clientWidth","offsetWidth","computedStyle","getComputedStyle","contentWidth","parseFloat","paddingLeft","paddingRight","borderLeftWidth","borderRightWidth","recalculate","container","current","measureContainer","querySelector","parentElement","availableWidthElement","measureItem","availableWidth","itemWidth","measuredGap","columnGap","resolvedGap","rawItemsPerPage","rawFitCount","floor","clampedItemsPerPage","min","clampedFitCount","previousFitCount","previousItemsPerPage","previousGap","handleWindowResize","addEventListener","passive","removeEventListener","ResizeObserver","resizeObserver","observe","disconnect","isOverflowing"],"mappings":"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"}
|