@puzzle-ui/ui 0.4.0 → 0.5.0

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.
Files changed (156) hide show
  1. package/README.md +3 -4
  2. package/dist/components/actions/button/button.css +5 -2
  3. package/dist/components/actions/button-group/button-group.css +16 -17
  4. package/dist/components/data-display/badge/badge.css +15 -2
  5. package/dist/components/data-display/badge/index.cjs +13 -2
  6. package/dist/components/data-display/badge/index.cjs.map +1 -1
  7. package/dist/components/data-display/badge/index.d.cts +7 -2
  8. package/dist/components/data-display/badge/index.d.ts +7 -2
  9. package/dist/components/data-display/badge/index.js +13 -2
  10. package/dist/components/data-display/badge/index.js.map +1 -1
  11. package/dist/components/data-display/rating/index.cjs +84 -0
  12. package/dist/components/data-display/rating/index.cjs.map +1 -0
  13. package/dist/components/data-display/rating/index.d.cts +3 -0
  14. package/dist/components/data-display/rating/index.d.ts +3 -0
  15. package/dist/components/data-display/rating/index.js +84 -0
  16. package/dist/components/data-display/rating/index.js.map +1 -0
  17. package/dist/components/data-display/rating/rating.css +72 -0
  18. package/dist/components/data-display/rating/rating.types.d.cts +15 -0
  19. package/dist/components/data-display/rating/rating.types.d.ts +15 -0
  20. package/dist/components/data-display/rating/rating.utils.cjs +27 -0
  21. package/dist/components/data-display/rating/rating.utils.cjs.map +1 -0
  22. package/dist/components/data-display/rating/rating.utils.js +25 -0
  23. package/dist/components/data-display/rating/rating.utils.js.map +1 -0
  24. package/dist/components/forms/checkbox/checkbox.css +2 -10
  25. package/dist/components/forms/input-group/input-group.d.cts +4 -2
  26. package/dist/components/forms/input-group/input-group.d.ts +4 -2
  27. package/dist/components/forms/radio-group/index.d.cts +2 -1
  28. package/dist/components/forms/radio-group/index.d.ts +2 -1
  29. package/dist/components/forms/stepper/index.d.cts +4 -2
  30. package/dist/components/forms/stepper/index.d.ts +4 -2
  31. package/dist/components/forms/time-picker/index.cjs +108 -0
  32. package/dist/components/forms/time-picker/index.cjs.map +1 -0
  33. package/dist/components/forms/time-picker/index.d.cts +3 -0
  34. package/dist/components/forms/time-picker/index.d.ts +3 -0
  35. package/dist/components/forms/time-picker/index.js +108 -0
  36. package/dist/components/forms/time-picker/index.js.map +1 -0
  37. package/dist/components/forms/time-picker/time-picker.css +166 -0
  38. package/dist/components/forms/time-picker/time-picker.types.d.cts +34 -0
  39. package/dist/components/forms/time-picker/time-picker.types.d.ts +34 -0
  40. package/dist/components/forms/time-picker/time-picker.utils.cjs +25 -0
  41. package/dist/components/forms/time-picker/time-picker.utils.cjs.map +1 -0
  42. package/dist/components/forms/time-picker/time-picker.utils.js +24 -0
  43. package/dist/components/forms/time-picker/time-picker.utils.js.map +1 -0
  44. package/dist/components/forms/token-input/index.cjs +189 -0
  45. package/dist/components/forms/token-input/index.cjs.map +1 -0
  46. package/dist/components/forms/token-input/index.d.cts +3 -0
  47. package/dist/components/forms/token-input/index.d.ts +3 -0
  48. package/dist/components/forms/token-input/index.js +189 -0
  49. package/dist/components/forms/token-input/index.js.map +1 -0
  50. package/dist/components/forms/token-input/token-input.css +145 -0
  51. package/dist/components/forms/token-input/token-input.types.d.cts +48 -0
  52. package/dist/components/forms/token-input/token-input.types.d.ts +48 -0
  53. package/dist/components/forms/token-input/token-input.utils.cjs +30 -0
  54. package/dist/components/forms/token-input/token-input.utils.cjs.map +1 -0
  55. package/dist/components/forms/token-input/token-input.utils.js +24 -0
  56. package/dist/components/forms/token-input/token-input.utils.js.map +1 -0
  57. package/dist/components/layout/aspect-ratio/aspect-ratio.css +14 -0
  58. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.cjs +11 -0
  59. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.cjs.map +1 -0
  60. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.js +11 -0
  61. package/dist/components/layout/aspect-ratio/aspect-ratio.utils.js.map +1 -0
  62. package/dist/components/layout/aspect-ratio/index.cjs +16 -0
  63. package/dist/components/layout/aspect-ratio/index.cjs.map +1 -0
  64. package/dist/components/layout/aspect-ratio/index.d.cts +11 -0
  65. package/dist/components/layout/aspect-ratio/index.d.ts +11 -0
  66. package/dist/components/layout/aspect-ratio/index.js +16 -0
  67. package/dist/components/layout/aspect-ratio/index.js.map +1 -0
  68. package/dist/components/layout/container/container.css +1 -0
  69. package/dist/components/layout/page-layout/page-layout.css +1 -0
  70. package/dist/components/layout/scroll-area/index.cjs +27 -0
  71. package/dist/components/layout/scroll-area/index.cjs.map +1 -0
  72. package/dist/components/layout/scroll-area/index.d.cts +15 -0
  73. package/dist/components/layout/scroll-area/index.d.ts +15 -0
  74. package/dist/components/layout/scroll-area/index.js +27 -0
  75. package/dist/components/layout/scroll-area/index.js.map +1 -0
  76. package/dist/components/layout/scroll-area/scroll-area.css +25 -0
  77. package/dist/components/layout/scroll-area/scroll-area.utils.cjs +8 -0
  78. package/dist/components/layout/scroll-area/scroll-area.utils.cjs.map +1 -0
  79. package/dist/components/layout/scroll-area/scroll-area.utils.d.cts +2 -0
  80. package/dist/components/layout/scroll-area/scroll-area.utils.d.ts +2 -0
  81. package/dist/components/layout/scroll-area/scroll-area.utils.js +8 -0
  82. package/dist/components/layout/scroll-area/scroll-area.utils.js.map +1 -0
  83. package/dist/components/layout/section/index.cjs +10 -4
  84. package/dist/components/layout/section/index.cjs.map +1 -1
  85. package/dist/components/layout/section/index.d.cts +1 -1
  86. package/dist/components/layout/section/index.d.ts +1 -1
  87. package/dist/components/layout/section/index.js +11 -5
  88. package/dist/components/layout/section/index.js.map +1 -1
  89. package/dist/components/layout/section/section.css +9 -3
  90. package/dist/components/layout/section/types.d.cts +5 -5
  91. package/dist/components/layout/section/types.d.ts +5 -5
  92. package/dist/components/layout/separator/index.cjs +18 -0
  93. package/dist/components/layout/separator/index.cjs.map +1 -0
  94. package/dist/components/layout/separator/index.d.cts +17 -0
  95. package/dist/components/layout/separator/index.d.ts +17 -0
  96. package/dist/components/layout/separator/index.js +18 -0
  97. package/dist/components/layout/separator/index.js.map +1 -0
  98. package/dist/components/layout/separator/separator.css +15 -0
  99. package/dist/components/layout/separator/separator.utils.cjs +11 -0
  100. package/dist/components/layout/separator/separator.utils.cjs.map +1 -0
  101. package/dist/components/layout/separator/separator.utils.d.cts +2 -0
  102. package/dist/components/layout/separator/separator.utils.d.ts +2 -0
  103. package/dist/components/layout/separator/separator.utils.js +11 -0
  104. package/dist/components/layout/separator/separator.utils.js.map +1 -0
  105. package/dist/components/layout/stack/index.cjs +18 -2
  106. package/dist/components/layout/stack/index.cjs.map +1 -1
  107. package/dist/components/layout/stack/index.d.cts +8 -6
  108. package/dist/components/layout/stack/index.d.ts +8 -6
  109. package/dist/components/layout/stack/index.js +18 -2
  110. package/dist/components/layout/stack/index.js.map +1 -1
  111. package/dist/components/layout/stack/stack.css +118 -138
  112. package/dist/components/navigation/carousel/index.cjs +4 -1
  113. package/dist/components/navigation/carousel/index.cjs.map +1 -1
  114. package/dist/components/navigation/carousel/index.js +4 -1
  115. package/dist/components/navigation/carousel/index.js.map +1 -1
  116. package/dist/components/navigation/tabs/index.d.cts +3 -2
  117. package/dist/components/navigation/tabs/index.d.ts +3 -2
  118. package/dist/components/overlays/context-menu/context-menu.css +151 -0
  119. package/dist/components/overlays/context-menu/context-menu.types.d.cts +69 -0
  120. package/dist/components/overlays/context-menu/context-menu.types.d.ts +69 -0
  121. package/dist/components/overlays/context-menu/context-menu.utils.cjs +46 -0
  122. package/dist/components/overlays/context-menu/context-menu.utils.cjs.map +1 -0
  123. package/dist/components/overlays/context-menu/context-menu.utils.js +40 -0
  124. package/dist/components/overlays/context-menu/context-menu.utils.js.map +1 -0
  125. package/dist/components/overlays/context-menu/index.cjs +830 -0
  126. package/dist/components/overlays/context-menu/index.cjs.map +1 -0
  127. package/dist/components/overlays/context-menu/index.d.cts +42 -0
  128. package/dist/components/overlays/context-menu/index.d.ts +42 -0
  129. package/dist/components/overlays/context-menu/index.js +818 -0
  130. package/dist/components/overlays/context-menu/index.js.map +1 -0
  131. package/dist/components/typography/heading.cjs +6 -2
  132. package/dist/components/typography/heading.cjs.map +1 -1
  133. package/dist/components/typography/heading.d.cts +3 -2
  134. package/dist/components/typography/heading.d.ts +3 -2
  135. package/dist/components/typography/heading.js +6 -2
  136. package/dist/components/typography/heading.js.map +1 -1
  137. package/dist/components/typography/text.cjs +6 -2
  138. package/dist/components/typography/text.cjs.map +1 -1
  139. package/dist/components/typography/text.d.cts +3 -2
  140. package/dist/components/typography/text.d.ts +3 -2
  141. package/dist/components/typography/text.js +6 -2
  142. package/dist/components/typography/text.js.map +1 -1
  143. package/dist/components/utilities/preview/index.cjs +88 -20
  144. package/dist/components/utilities/preview/index.cjs.map +1 -1
  145. package/dist/components/utilities/preview/index.d.cts +6 -2
  146. package/dist/components/utilities/preview/index.d.ts +6 -2
  147. package/dist/components/utilities/preview/index.js +88 -20
  148. package/dist/components/utilities/preview/index.js.map +1 -1
  149. package/dist/components/utilities/preview/preview.css +26 -11
  150. package/dist/components/utilities/transition/index.d.cts +1 -1
  151. package/dist/components/utilities/transition/index.d.ts +1 -1
  152. package/dist/index.cjs +123 -97
  153. package/dist/index.d.cts +15 -2
  154. package/dist/index.d.ts +15 -2
  155. package/dist/index.js +8 -1
  156. package/package.json +6 -6
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/navigation/carousel/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { cn, resolveResponsiveValue } from \"@puzzle-ui/core\";\nimport {\n cloneElement,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type Key,\n type MouseEvent as ReactMouseEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport { useCarouselContext } from \"./controller\";\nimport \"./carousel.css\";\nimport type { ResponsiveValue } from \"@puzzle-ui/core\";\n\ntype CarouselAlignment = \"left\" | \"center\" | \"right\";\n\ntype CarouselButtonAlignment = \"aside\" | \"over\";\n\ntype CarouselPagerAlignment = \"under\" | \"over\";\n\ntype CarouselAlignmentConfig = ResponsiveValue<CarouselButtonAlignment>;\n\ntype PagerAlignmentConfig = ResponsiveValue<CarouselPagerAlignment>;\n\ntype CarouselLayout = {\n itemsPerPage: number;\n numberOfPages: number;\n maxStartIndex: number;\n maxOffset: number;\n itemOffsets: number[];\n};\n\ntype DragSample = {\n x: number;\n time: number;\n};\n\ntype DragState = {\n pointerId: number;\n startX: number;\n startOffset: number;\n currentOffset: number;\n renderedOffset: number;\n hasMoved: boolean;\n interruptedAnimation: boolean;\n samples: DragSample[];\n};\n\ntype CarouselProps<T> = {\n renderItem: (item: T, index: number) => ReactNode;\n\n /** Returns a stable React key when items may be inserted, removed, or reordered. */\n getItemKey?: (item: T, index: number) => Key;\n\n panelsPerView?: ResponsiveValue<number>;\n itemWidth?: ResponsiveValue<CSSProperties[\"width\"]>;\n gap?: ResponsiveValue<CSSProperties[\"gap\"]>;\n\n className?: string;\n viewportClassName?: string;\n viewportStyle?: CSSProperties;\n ariaLabel?: string;\n\n singlePageAlignment?: CarouselAlignment;\n\n backButton?: ReactElement;\n nextButton?: ReactElement;\n\n buttonAlignment?: CarouselAlignmentConfig;\n pagerAlignment?: PagerAlignmentConfig;\n renderPager?: (currentPage: number, numberOfPages: number) => ReactNode;\n};\n\nconst defaultLayout: CarouselLayout = {\n itemsPerPage: 1,\n numberOfPages: 0,\n maxStartIndex: 0,\n maxOffset: 0,\n itemOffsets: [],\n};\n\nconst rubberBandDistance = (distance: number, dimension: number): number => {\n const size = Math.max(1, dimension);\n const resistance = 0.55;\n\n return (distance * resistance * size) / (size + resistance * distance);\n};\n\nconst rubberBandOffset = (\n offset: number,\n maxOffset: number,\n viewportWidth: number,\n): number => {\n if (offset < 0) {\n return -rubberBandDistance(-offset, viewportWidth);\n }\n\n if (offset > maxOffset) {\n return maxOffset + rubberBandDistance(offset - maxOffset, viewportWidth);\n }\n\n return offset;\n};\n\nconst readRenderedOffset = (element: HTMLElement, fallback: number): number => {\n const ownerWindow = element.ownerDocument.defaultView ?? window;\n const transform = ownerWindow.getComputedStyle(element).transform;\n\n if (!transform || transform === \"none\") {\n return fallback;\n }\n\n const isMatrix3d = transform.startsWith(\"matrix3d(\");\n const values = transform\n .slice(isMatrix3d ? 9 : 7, -1)\n .split(\",\")\n .map(Number);\n\n const translateX = values[isMatrix3d ? 12 : 4] ?? Number.NaN;\n\n return Number.isFinite(translateX) ? -translateX : fallback;\n};\n\nconst estimatePointerVelocity = (samples: DragSample[]): number => {\n if (samples.length < 2) {\n return 0;\n }\n\n const end = samples[samples.length - 1]!;\n const cutoff = end.time - 100;\n\n let start = samples[0]!;\n\n for (const sample of samples) {\n if (sample.time <= cutoff) {\n start = sample;\n } else {\n break;\n }\n }\n\n const elapsedSeconds = (end.time - start.time) / 1000;\n\n if (elapsedSeconds <= 0) {\n return 0;\n }\n\n // Convert pointer movement to content movement.\n return -(end.x - start.x) / elapsedSeconds;\n};\n\nconst toCssLength = (\n value: CSSProperties[\"width\"] | CSSProperties[\"gap\"] | undefined,\n): string => {\n if (typeof value === \"number\") {\n return `${value}px`;\n }\n\n return value ?? \"0px\";\n};\n\nconst getItemBasis = (itemsPerPage: number, gap: string): string => {\n const count = Math.max(1, Math.floor(itemsPerPage || 1));\n\n if (count === 1) {\n return \"100%\";\n }\n\n if (gap === \"\" || gap === \"0px\" || gap === \"0\") {\n return `calc((100% / ${count}) - (1px / ${count}))`;\n }\n\n const gapTerms = Array.from({ length: count - 1 }, () => `(${gap})`).join(\n \" + \",\n );\n\n return `calc((100% - (${gapTerms})) / ${count})`;\n};\n\nconst getPagerDataAttribute = (alignment: CarouselPagerAlignment) =>\n alignment === \"under\" ? \"under\" : \"over\";\n\nconst cloneNavigationButton = (\n button: ReactElement | undefined,\n disabled: boolean,\n onNavigate: () => void,\n): ReactNode => {\n if (!button) {\n return null;\n }\n\n const originalProps = button.props as {\n disabled?: boolean;\n onClick?: (event: ReactMouseEvent<HTMLElement>) => void;\n };\n\n const isDisabled = disabled || originalProps.disabled === true;\n\n return cloneElement(button as ReactElement<Record<string, unknown>>, {\n disabled: isDisabled,\n \"aria-disabled\": isDisabled,\n onClick: (event: ReactMouseEvent<HTMLElement>) => {\n if (isDisabled) {\n event.preventDefault();\n return;\n }\n\n originalProps.onClick?.(event);\n onNavigate();\n },\n });\n};\n\nconst Carousel = <T,>({\n renderItem,\n getItemKey,\n panelsPerView,\n itemWidth,\n gap = 0,\n className,\n viewportClassName,\n viewportStyle,\n ariaLabel = \"Carousel\",\n singlePageAlignment = \"left\",\n backButton,\n nextButton,\n buttonAlignment,\n pagerAlignment,\n renderPager,\n}: CarouselProps<T>) => {\n const { items, currentPanel, setPanel } = useCarouselContext<T>();\n\n const viewportRef = useRef<HTMLDivElement>(null);\n const trackRef = useRef<HTMLDivElement>(null);\n const itemRefs = useRef<Array<HTMLDivElement | null>>([]);\n\n const dragRef = useRef<DragState | null>(null);\n const pointerCleanupRef = useRef<(() => void) | null>(null);\n const animationFrameRef = useRef<number | null>(null);\n const visualOffsetRef = useRef(0);\n const suppressClickRef = useRef(false);\n\n const [isDragging, setIsDragging] = useState(false);\n const [isAnimating, setIsAnimating] = useState(false);\n\n const [layout, setLayout] = useState<CarouselLayout>(defaultLayout);\n\n const sizingMode = itemWidth !== undefined ? \"item-width\" : \"panels\";\n\n const panelCounts = resolveResponsiveValue(panelsPerView, 1);\n const itemWidths = resolveResponsiveValue(itemWidth, \"100%\");\n const gaps = resolveResponsiveValue(gap, 0);\n\n const buttonAlignments = resolveResponsiveValue<CarouselButtonAlignment>(\n buttonAlignment,\n \"over\",\n );\n\n const pagerAlignments = resolveResponsiveValue<CarouselPagerAlignment>(\n pagerAlignment,\n \"under\",\n );\n\n const cssVariables = {\n \"--puzzle-carousel-gap-mobile\": toCssLength(gaps.mobile),\n \"--puzzle-carousel-gap-tablet\": toCssLength(gaps.tablet),\n \"--puzzle-carousel-gap-desktop\": toCssLength(gaps.desktop),\n \"--puzzle-carousel-basis-mobile\": getItemBasis(\n panelCounts.mobile,\n toCssLength(gaps.mobile),\n ),\n \"--puzzle-carousel-basis-tablet\": getItemBasis(\n panelCounts.tablet ?? panelCounts.mobile,\n toCssLength(gaps.tablet),\n ),\n \"--puzzle-carousel-basis-desktop\": getItemBasis(\n panelCounts.desktop ?? panelCounts.tablet ?? panelCounts.mobile,\n toCssLength(gaps.desktop),\n ),\n \"--puzzle-carousel-item-width-mobile\": toCssLength(itemWidths.mobile),\n \"--puzzle-carousel-item-width-tablet\": toCssLength(itemWidths.tablet),\n \"--puzzle-carousel-item-width-desktop\": toCssLength(itemWidths.desktop),\n } as CSSProperties;\n\n /**\n * Read the actual rendered widths and positions of the items.\n * This supports both panelsPerView and itemWidth sizing.\n */\n const measureLayout = useCallback(() => {\n const viewport = viewportRef.current;\n const track = trackRef.current;\n\n if (!viewport || !track || items.length === 0) {\n setLayout(defaultLayout);\n return;\n }\n\n const viewportRect = viewport.getBoundingClientRect();\n const viewportWidth = viewportRect.width;\n const itemNodes = itemRefs.current\n .slice(0, items.length)\n .filter((item): item is HTMLDivElement => item !== null);\n\n if (viewportWidth <= 0 || itemNodes.length === 0) {\n return;\n }\n\n // Relative positions preserve fractional CSS pixels and cancel the\n // track's current translate transform.\n const trackRect = track.getBoundingClientRect();\n const itemRects = itemNodes.map((item) => item.getBoundingClientRect());\n\n const itemOffsets = itemRects.map((rect) => rect.left - trackRect.left);\n\n let itemsPerPage = 0;\n\n for (const rect of itemRects) {\n const itemEnd = rect.right - trackRect.left;\n\n if (itemEnd <= viewportWidth + 0.01) {\n itemsPerPage += 1;\n } else {\n break;\n }\n }\n\n itemsPerPage = Math.max(1, itemsPerPage);\n\n const numberOfPages = Math.ceil(items.length / itemsPerPage);\n const maxStartIndex = Math.max(0, items.length - itemsPerPage);\n const lastItemEnd = itemRects[itemRects.length - 1]!.right - trackRect.left;\n const maxOffset = Math.max(0, lastItemEnd - viewportWidth);\n\n setLayout((previous) => {\n const unchanged =\n previous.itemsPerPage === itemsPerPage &&\n previous.numberOfPages === numberOfPages &&\n previous.maxStartIndex === maxStartIndex &&\n previous.maxOffset === maxOffset &&\n previous.itemOffsets.length === itemOffsets.length &&\n previous.itemOffsets.every(\n (offset, index) => offset === itemOffsets[index],\n );\n\n if (unchanged) {\n return previous;\n }\n\n return {\n itemsPerPage,\n numberOfPages,\n maxStartIndex,\n maxOffset,\n itemOffsets,\n };\n });\n }, [items.length]);\n\n const itemOrderSignature = items\n .map((item, index) => String(getItemKey ? getItemKey(item, index) : index))\n .join(\"\\u0000\");\n\n useEffect(() => {\n const viewport = viewportRef.current;\n const track = trackRef.current;\n\n if (!viewport || !track) {\n return;\n }\n\n const ownerWindow = viewport.ownerDocument.defaultView ?? window;\n\n measureLayout();\n\n if (typeof ResizeObserver === \"undefined\") {\n ownerWindow.addEventListener(\"resize\", measureLayout);\n\n return () => {\n ownerWindow.removeEventListener(\"resize\", measureLayout);\n };\n }\n\n const observer = new ResizeObserver(measureLayout);\n\n observer.observe(viewport);\n observer.observe(track);\n\n itemRefs.current.slice(0, items.length).forEach((item) => {\n if (item) {\n observer.observe(item);\n }\n });\n\n return () => observer.disconnect();\n }, [items.length, itemOrderSignature, measureLayout]);\n\n /**\n * currentPanel is always an item index.\n *\n * Restrict it to the last position from which the viewport can be\n * completely filled without scrolling beyond the end of the track.\n */\n const activePanel = Math.min(\n Math.max(0, Math.floor(currentPanel)),\n layout.maxStartIndex,\n );\n\n /**\n * Logical page starts can overlap at the end.\n *\n * Example: 5 items with 3 visible per page:\n * [0, 2], not [0, 3].\n *\n * Example: 8 items with 3 visible per page:\n * [0, 3, 5].\n */\n const pageStartIndices = useMemo(\n () =>\n Array.from({ length: layout.numberOfPages }, (_, pageIndex) => {\n if (pageIndex === layout.numberOfPages - 1) {\n return layout.maxStartIndex;\n }\n\n return Math.min(pageIndex * layout.itemsPerPage, layout.maxStartIndex);\n }),\n [layout.itemsPerPage, layout.maxStartIndex, layout.numberOfPages],\n );\n\n /**\n * Map the active item to the nearest logical page.\n * This lets swipe-to-panel and page-based navigation coexist.\n */\n const activePageIndex = useMemo(() => {\n if (pageStartIndices.length === 0) {\n return 0;\n }\n\n return pageStartIndices.reduce((closestIndex, pageStart, index) => {\n const closestStart = pageStartIndices[closestIndex]!;\n\n return Math.abs(pageStart - activePanel) <\n Math.abs(closestStart - activePanel)\n ? index\n : closestIndex;\n }, 0);\n }, [activePanel, pageStartIndices]);\n\n const clampOffset = useCallback(\n (offset: number) => Math.min(layout.maxOffset, Math.max(0, offset)),\n [layout.maxOffset],\n );\n\n const activeOffset = clampOffset(layout.itemOffsets[activePanel] ?? 0);\n\n useEffect(() => {\n if (dragRef.current || animationFrameRef.current !== null) {\n return;\n }\n\n visualOffsetRef.current = activeOffset;\n }, [activeOffset]);\n\n useEffect(\n () => () => {\n pointerCleanupRef.current?.();\n\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n },\n [],\n );\n\n /**\n * Only correct the controller after layout measurement has completed.\n * This avoids forcing currentPanel to 0 during the initial render.\n */\n useEffect(() => {\n if (layout.numberOfPages > 0 && currentPanel !== activePanel) {\n setPanel(activePanel);\n }\n }, [activePanel, currentPanel, layout.numberOfPages, setPanel]);\n\n const goToPage = useCallback(\n (pageIndex: number) => {\n const targetPage = Math.max(\n 0,\n Math.min(pageIndex, pageStartIndices.length - 1),\n );\n\n const targetPanel = pageStartIndices[targetPage];\n\n if (targetPanel !== undefined) {\n setPanel(targetPanel);\n }\n },\n [pageStartIndices, setPanel],\n );\n\n const goToPreviousPage = useCallback(() => {\n goToPage(activePageIndex - 1);\n }, [activePageIndex, goToPage]);\n\n const goToNextPage = useCallback(() => {\n goToPage(activePageIndex + 1);\n }, [activePageIndex, goToPage]);\n\n /**\n * Start tracking a swipe or mouse drag.\n *\n * Capture on the original element rather than always on the viewport,\n * so a normal click on an interactive child can still work.\n */\n\n const writeTrackOffset = useCallback((offset: number) => {\n visualOffsetRef.current = offset;\n\n if (trackRef.current) {\n trackRef.current.style.transform = `translate3d(${-offset}px, 0, 0)`;\n }\n }, []);\n\n const getNearestPanelIndex = useCallback(\n (offset: number) => {\n let nearestIndex = 0;\n let nearestDistance = Number.POSITIVE_INFINITY;\n\n for (let index = 0; index <= layout.maxStartIndex; index += 1) {\n const candidateOffset = clampOffset(layout.itemOffsets[index] ?? 0);\n\n const distance = Math.abs(candidateOffset - offset);\n\n if (distance < nearestDistance) {\n nearestDistance = distance;\n nearestIndex = index;\n }\n }\n\n return nearestIndex;\n },\n [clampOffset, layout.itemOffsets, layout.maxStartIndex],\n );\n\n /**\n * Animate to a panel using a damped spring.\n *\n * A little underdamping gives the motion a natural overshoot.\n * The target remains a real, reachable item offset.\n */\n const animateToPanel = useCallback(\n (fromOffset: number, targetPanel: number, initialVelocity: number) => {\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n const track = trackRef.current;\n const ownerWindow =\n viewportRef.current?.ownerDocument.defaultView ?? window;\n const targetOffset = clampOffset(layout.itemOffsets[targetPanel] ?? 0);\n\n if (\n !track ||\n ownerWindow.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n ) {\n writeTrackOffset(targetOffset);\n track?.style.removeProperty(\"transition\");\n setPanel(targetPanel);\n setIsDragging(false);\n setIsAnimating(false);\n return;\n }\n\n track.style.transition = \"none\";\n\n setIsDragging(false);\n setIsAnimating(true);\n\n let position = fromOffset;\n\n // Preserve some release momentum without allowing extreme fling speeds.\n let velocity = Math.max(-1800, Math.min(1800, initialVelocity * 0.4));\n\n let previousTime = performance.now();\n\n const stiffness = 230;\n const damping = 24;\n\n const animate = (now: number) => {\n const deltaSeconds = Math.min((now - previousTime) / 1000, 1 / 30);\n\n previousTime = now;\n\n const springAcceleration = (targetOffset - position) * stiffness;\n\n const dampingAcceleration = velocity * damping;\n\n velocity += (springAcceleration - dampingAcceleration) * deltaSeconds;\n\n position += velocity * deltaSeconds;\n\n const viewportWidth = viewportRef.current?.clientWidth ?? 1;\n\n writeTrackOffset(\n rubberBandOffset(position, layout.maxOffset, viewportWidth),\n );\n\n const positionSettled = Math.abs(targetOffset - position) < 0.5;\n\n const velocitySettled = Math.abs(velocity) < 10;\n\n if (positionSettled && velocitySettled) {\n animationFrameRef.current = null;\n\n writeTrackOffset(targetOffset);\n track.style.removeProperty(\"transition\");\n\n setPanel(targetPanel);\n setIsAnimating(false);\n\n return;\n }\n\n animationFrameRef.current = requestAnimationFrame(animate);\n };\n\n animationFrameRef.current = requestAnimationFrame(animate);\n },\n [\n clampOffset,\n layout.itemOffsets,\n layout.maxOffset,\n setPanel,\n writeTrackOffset,\n ],\n );\n\n const handlePointerDown = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (!event.isPrimary || dragRef.current) {\n return;\n }\n\n if (event.pointerType === \"mouse\" && event.button !== 0) {\n return;\n }\n\n if (layout.maxOffset <= 0) {\n return;\n }\n\n const track = trackRef.current;\n const viewport = viewportRef.current;\n const ownerWindow = viewport?.ownerDocument.defaultView ?? window;\n\n if (!track || !viewport || !ownerWindow) {\n return;\n }\n\n const interruptedAnimation = animationFrameRef.current !== null;\n\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n /*\n * Read the currently displayed offset before disabling the CSS\n * transition. This also handles starting a drag during an animation.\n */\n const startOffset = readRenderedOffset(track, visualOffsetRef.current);\n\n track.style.transition = \"none\";\n writeTrackOffset(startOffset);\n\n setIsAnimating(false);\n setIsDragging(true);\n\n const startTime = performance.now();\n\n const drag: DragState = {\n pointerId: event.pointerId,\n startX: event.clientX,\n startOffset,\n currentOffset: startOffset,\n renderedOffset: startOffset,\n hasMoved: false,\n interruptedAnimation,\n samples: [\n {\n x: event.clientX,\n time: startTime,\n },\n ],\n };\n\n dragRef.current = drag;\n\n const updatePointerPosition = (pointerEvent: PointerEvent) => {\n const deltaX = pointerEvent.clientX - drag.startX;\n\n if (!drag.hasMoved && Math.abs(deltaX) <= 4) {\n return;\n }\n\n drag.hasMoved = true;\n\n // Positive offset means the content moves to the left.\n drag.currentOffset = drag.startOffset - deltaX;\n\n drag.renderedOffset = rubberBandOffset(\n drag.currentOffset,\n layout.maxOffset,\n viewport.clientWidth,\n );\n\n const now = performance.now();\n\n drag.samples.push({\n x: pointerEvent.clientX,\n time: now,\n });\n\n // Retain a short history for release-velocity estimation.\n while (drag.samples.length > 2 && now - drag.samples[1]!.time > 120) {\n drag.samples.shift();\n }\n\n writeTrackOffset(drag.renderedOffset);\n\n if (pointerEvent.cancelable) {\n pointerEvent.preventDefault();\n }\n };\n\n const cleanup = () => {\n ownerWindow.removeEventListener(\"pointermove\", handleWindowPointerMove);\n ownerWindow.removeEventListener(\"pointerup\", handleWindowPointerUp);\n ownerWindow.removeEventListener(\n \"pointercancel\",\n handleWindowPointerCancel,\n );\n ownerWindow.removeEventListener(\"blur\", handleWindowBlur);\n\n if (pointerCleanupRef.current === cleanup) {\n pointerCleanupRef.current = null;\n }\n };\n\n const settleCancelledDrag = () => {\n if (dragRef.current !== drag) {\n return;\n }\n\n cleanup();\n dragRef.current = null;\n setIsDragging(false);\n\n const targetPanel = getNearestPanelIndex(\n clampOffset(drag.currentOffset),\n );\n\n animateToPanel(drag.renderedOffset, targetPanel, 0);\n };\n\n const handleWindowPointerMove = (pointerEvent: PointerEvent) => {\n if (pointerEvent.pointerId !== drag.pointerId) {\n return;\n }\n\n updatePointerPosition(pointerEvent);\n };\n\n const handleWindowPointerUp = (pointerEvent: PointerEvent) => {\n if (pointerEvent.pointerId !== drag.pointerId) {\n return;\n }\n\n // Include the final pointer position, even if no final move fired.\n updatePointerPosition(pointerEvent);\n\n cleanup();\n dragRef.current = null;\n setIsDragging(false);\n\n if (!drag.hasMoved) {\n if (drag.interruptedAnimation) {\n const targetPanel = getNearestPanelIndex(\n clampOffset(drag.startOffset),\n );\n\n animateToPanel(drag.startOffset, targetPanel, 0);\n } else {\n track.style.removeProperty(\"transition\");\n writeTrackOffset(drag.startOffset);\n }\n\n return;\n }\n\n suppressClickRef.current = true;\n\n ownerWindow.setTimeout(() => {\n suppressClickRef.current = false;\n }, 0);\n\n const velocity = estimatePointerVelocity(drag.samples);\n\n /*\n * Project the release position forward. A fast swipe can skip\n * multiple items, while a slow drag generally snaps nearby.\n */\n const projectedOffset = clampOffset(\n clampOffset(drag.currentOffset) + velocity * 0.18,\n );\n\n const targetPanel = getNearestPanelIndex(projectedOffset);\n\n animateToPanel(drag.renderedOffset, targetPanel, velocity);\n };\n\n const handleWindowPointerCancel = (pointerEvent: PointerEvent) => {\n if (pointerEvent.pointerId === drag.pointerId) {\n settleCancelledDrag();\n }\n };\n\n const handleWindowBlur = () => {\n settleCancelledDrag();\n };\n\n pointerCleanupRef.current = cleanup;\n\n /*\n * Listen on window instead of relying on events staying inside the\n * viewport. Releasing outside the carousel still completes the drag.\n */\n ownerWindow.addEventListener(\"pointermove\", handleWindowPointerMove);\n ownerWindow.addEventListener(\"pointerup\", handleWindowPointerUp);\n ownerWindow.addEventListener(\"pointercancel\", handleWindowPointerCancel);\n ownerWindow.addEventListener(\"blur\", handleWindowBlur);\n },\n [\n animateToPanel,\n clampOffset,\n getNearestPanelIndex,\n layout.maxOffset,\n setIsAnimating,\n writeTrackOffset,\n ],\n );\n\n const handleClickCapture = useCallback(\n (event: ReactMouseEvent<HTMLDivElement>) => {\n if (!suppressClickRef.current) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n suppressClickRef.current = false;\n },\n [],\n );\n\n const isFirstPage = activePageIndex === 0;\n const isLastPage =\n layout.numberOfPages <= 1 || activePageIndex >= layout.numberOfPages - 1;\n\n const currentPage = layout.numberOfPages > 0 ? activePageIndex : 0;\n\n return (\n <div\n className={cn(\"puzzle-carousel\", className)}\n style={cssVariables}\n data-sizing={sizingMode}\n data-dragging={isDragging ? \"true\" : \"false\"}\n data-animating={isAnimating ? \"true\" : \"false\"}\n data-single-page={layout.numberOfPages <= 1 ? \"true\" : \"false\"}\n data-single-page-alignment={singlePageAlignment}\n data-button-mobile={buttonAlignments.mobile}\n data-button-tablet={buttonAlignments.tablet}\n data-button-desktop={buttonAlignments.desktop}\n data-pager-mobile={getPagerDataAttribute(pagerAlignments.mobile)}\n data-pager-tablet={getPagerDataAttribute(\n pagerAlignments.tablet ?? pagerAlignments.mobile,\n )}\n data-pager-desktop={getPagerDataAttribute(\n pagerAlignments.desktop ??\n pagerAlignments.tablet ??\n pagerAlignments.mobile,\n )}\n >\n <div className=\"puzzle-carousel__stage\">\n <div className=\"puzzle-carousel__navigation puzzle-carousel__navigation--previous\">\n {cloneNavigationButton(backButton, isFirstPage, goToPreviousPage)}\n </div>\n\n <div\n ref={viewportRef}\n className={cn(\"puzzle-carousel__viewport\", viewportClassName)}\n style={viewportStyle}\n role=\"group\"\n aria-roledescription=\"carousel\"\n aria-label={ariaLabel}\n onPointerDown={handlePointerDown}\n onClickCapture={handleClickCapture}\n onDragStart={(event) => event.preventDefault()}\n >\n <div\n ref={trackRef}\n className=\"puzzle-carousel__track\"\n style={{\n transform: `translate3d(${-activeOffset}px, 0, 0)`,\n }}\n >\n {items.map((item, index) => (\n <div\n key={getItemKey ? getItemKey(item, index) : index}\n ref={(node) => {\n itemRefs.current[index] = node;\n }}\n className=\"puzzle-carousel__item\"\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${index + 1} of ${items.length}`}\n >\n {renderItem(item, index)}\n </div>\n ))}\n </div>\n </div>\n\n <div className=\"puzzle-carousel__navigation puzzle-carousel__navigation--next\">\n {cloneNavigationButton(nextButton, isLastPage, goToNextPage)}\n </div>\n </div>\n\n {renderPager && layout.numberOfPages > 0 && (\n <div className=\"puzzle-carousel__pager\">\n {renderPager(currentPage, layout.numberOfPages)}\n </div>\n )}\n </div>\n );\n};\n\nexport {\n Carousel,\n type CarouselProps,\n type CarouselAlignment,\n type CarouselButtonAlignment,\n type CarouselPagerAlignment,\n type CarouselAlignmentConfig,\n type PagerAlignmentConfig,\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../src/components/navigation/carousel/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { cn, resolveResponsiveValue } from \"@puzzle-ui/core\";\nimport {\n cloneElement,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type Key,\n type MouseEvent as ReactMouseEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport { useCarouselContext } from \"./controller\";\nimport \"./carousel.css\";\nimport type { ResponsiveValue } from \"@puzzle-ui/core\";\n\ntype CarouselAlignment = \"left\" | \"center\" | \"right\";\n\ntype CarouselButtonAlignment = \"aside\" | \"over\";\n\ntype CarouselPagerAlignment = \"under\" | \"over\";\n\ntype CarouselAlignmentConfig = ResponsiveValue<CarouselButtonAlignment>;\n\ntype PagerAlignmentConfig = ResponsiveValue<CarouselPagerAlignment>;\n\ntype CarouselLayout = {\n itemsPerPage: number;\n numberOfPages: number;\n maxStartIndex: number;\n maxOffset: number;\n itemOffsets: number[];\n};\n\ntype DragSample = {\n x: number;\n time: number;\n};\n\ntype DragState = {\n pointerId: number;\n startX: number;\n startOffset: number;\n currentOffset: number;\n renderedOffset: number;\n hasMoved: boolean;\n interruptedAnimation: boolean;\n samples: DragSample[];\n};\n\ntype CarouselProps<T> = {\n renderItem: (item: T, index: number) => ReactNode;\n\n /** Returns a stable React key when items may be inserted, removed, or reordered. */\n getItemKey?: (item: T, index: number) => Key;\n\n panelsPerView?: ResponsiveValue<number>;\n itemWidth?: ResponsiveValue<CSSProperties[\"width\"]>;\n gap?: ResponsiveValue<CSSProperties[\"gap\"]>;\n\n className?: string;\n viewportClassName?: string;\n viewportStyle?: CSSProperties;\n ariaLabel?: string;\n\n singlePageAlignment?: CarouselAlignment;\n\n backButton?: ReactElement;\n nextButton?: ReactElement;\n\n buttonAlignment?: CarouselAlignmentConfig;\n pagerAlignment?: PagerAlignmentConfig;\n renderPager?: (currentPage: number, numberOfPages: number) => ReactNode;\n};\n\nconst defaultLayout: CarouselLayout = {\n itemsPerPage: 1,\n numberOfPages: 0,\n maxStartIndex: 0,\n maxOffset: 0,\n itemOffsets: [],\n};\n\nconst rubberBandDistance = (distance: number, dimension: number): number => {\n const size = Math.max(1, dimension);\n const resistance = 0.55;\n\n return (distance * resistance * size) / (size + resistance * distance);\n};\n\nconst rubberBandOffset = (\n offset: number,\n maxOffset: number,\n viewportWidth: number,\n): number => {\n if (offset < 0) {\n return -rubberBandDistance(-offset, viewportWidth);\n }\n\n if (offset > maxOffset) {\n return maxOffset + rubberBandDistance(offset - maxOffset, viewportWidth);\n }\n\n return offset;\n};\n\nconst readRenderedOffset = (element: HTMLElement, fallback: number): number => {\n const ownerWindow = element.ownerDocument.defaultView ?? window;\n const transform = ownerWindow.getComputedStyle(element).transform;\n\n if (!transform || transform === \"none\") {\n return fallback;\n }\n\n const isMatrix3d = transform.startsWith(\"matrix3d(\");\n const values = transform\n .slice(isMatrix3d ? 9 : 7, -1)\n .split(\",\")\n .map(Number);\n\n const translateX = values[isMatrix3d ? 12 : 4] ?? Number.NaN;\n\n return Number.isFinite(translateX) ? -translateX : fallback;\n};\n\nconst estimatePointerVelocity = (samples: DragSample[]): number => {\n if (samples.length < 2) {\n return 0;\n }\n\n const end = samples[samples.length - 1]!;\n const cutoff = end.time - 100;\n\n let start = samples[0]!;\n\n for (const sample of samples) {\n if (sample.time <= cutoff) {\n start = sample;\n } else {\n break;\n }\n }\n\n const elapsedSeconds = (end.time - start.time) / 1000;\n\n if (elapsedSeconds <= 0) {\n return 0;\n }\n\n // Convert pointer movement to content movement.\n return -(end.x - start.x) / elapsedSeconds;\n};\n\nconst toCssLength = (\n value: CSSProperties[\"width\"] | CSSProperties[\"gap\"] | undefined,\n): string => {\n if (typeof value === \"number\") {\n return `${value}px`;\n }\n\n return value ?? \"0px\";\n};\n\nconst getItemBasis = (itemsPerPage: number, gap: string): string => {\n const count = Math.max(1, Math.floor(itemsPerPage || 1));\n\n if (count === 1) {\n return \"100%\";\n }\n\n if (gap === \"\" || gap === \"0px\" || gap === \"0\") {\n return `calc((100% / ${count}) - (1px / ${count}))`;\n }\n\n const gapTerms = Array.from({ length: count - 1 }, () => `(${gap})`).join(\n \" + \",\n );\n\n return `calc((100% - (${gapTerms})) / ${count})`;\n};\n\nconst getPagerDataAttribute = (alignment: CarouselPagerAlignment) =>\n alignment === \"under\" ? \"under\" : \"over\";\n\nconst cloneNavigationButton = (\n button: ReactElement | undefined,\n disabled: boolean,\n onNavigate: () => void,\n): ReactNode => {\n if (!button) {\n return null;\n }\n\n const originalProps = button.props as {\n disabled?: boolean;\n onClick?: (event: ReactMouseEvent<HTMLElement>) => void;\n };\n\n const isDisabled = disabled || originalProps.disabled === true;\n\n return cloneElement(button as ReactElement<Record<string, unknown>>, {\n disabled: isDisabled,\n \"aria-disabled\": isDisabled,\n onClick: (event: ReactMouseEvent<HTMLElement>) => {\n if (isDisabled) {\n event.preventDefault();\n return;\n }\n\n originalProps.onClick?.(event);\n onNavigate();\n },\n });\n};\n\nconst Carousel = <T,>({\n renderItem,\n getItemKey,\n panelsPerView,\n itemWidth,\n gap = 0,\n className,\n viewportClassName,\n viewportStyle,\n ariaLabel = \"Carousel\",\n singlePageAlignment = \"left\",\n backButton,\n nextButton,\n buttonAlignment,\n pagerAlignment,\n renderPager,\n}: CarouselProps<T>) => {\n const { items, currentPanel, setPanel } = useCarouselContext<T>();\n\n const viewportRef = useRef<HTMLDivElement>(null);\n const trackRef = useRef<HTMLDivElement>(null);\n const itemRefs = useRef<Array<HTMLDivElement | null>>([]);\n\n const dragRef = useRef<DragState | null>(null);\n const pointerCleanupRef = useRef<(() => void) | null>(null);\n const animationFrameRef = useRef<number | null>(null);\n const visualOffsetRef = useRef(0);\n const suppressClickRef = useRef(false);\n\n const [isDragging, setIsDragging] = useState(false);\n const [isAnimating, setIsAnimating] = useState(false);\n\n const [layout, setLayout] = useState<CarouselLayout>(defaultLayout);\n\n const sizingMode = itemWidth !== undefined ? \"item-width\" : \"panels\";\n\n const panelCounts = resolveResponsiveValue(panelsPerView, 1);\n const itemWidths = resolveResponsiveValue(itemWidth, \"100%\");\n const gaps = resolveResponsiveValue(gap, 0);\n\n const buttonAlignments = resolveResponsiveValue<CarouselButtonAlignment>(\n buttonAlignment,\n \"over\",\n );\n\n const pagerAlignments = resolveResponsiveValue<CarouselPagerAlignment>(\n pagerAlignment,\n \"under\",\n );\n\n const cssVariables = {\n \"--puzzle-carousel-gap-mobile\": toCssLength(gaps.mobile),\n \"--puzzle-carousel-gap-tablet\": toCssLength(gaps.tablet),\n \"--puzzle-carousel-gap-desktop\": toCssLength(gaps.desktop),\n \"--puzzle-carousel-basis-mobile\": getItemBasis(\n panelCounts.mobile,\n toCssLength(gaps.mobile),\n ),\n \"--puzzle-carousel-basis-tablet\": getItemBasis(\n panelCounts.tablet ?? panelCounts.mobile,\n toCssLength(gaps.tablet),\n ),\n \"--puzzle-carousel-basis-desktop\": getItemBasis(\n panelCounts.desktop ?? panelCounts.tablet ?? panelCounts.mobile,\n toCssLength(gaps.desktop),\n ),\n \"--puzzle-carousel-item-width-mobile\": toCssLength(itemWidths.mobile),\n \"--puzzle-carousel-item-width-tablet\": toCssLength(itemWidths.tablet),\n \"--puzzle-carousel-item-width-desktop\": toCssLength(itemWidths.desktop),\n } as CSSProperties;\n\n /**\n * Read the actual rendered widths and positions of the items.\n * This supports both panelsPerView and itemWidth sizing.\n */\n const measureLayout = useCallback(() => {\n const viewport = viewportRef.current;\n const track = trackRef.current;\n\n if (!viewport || !track || items.length === 0) {\n setLayout(defaultLayout);\n return;\n }\n\n const viewportRect = viewport.getBoundingClientRect();\n const viewportWidth = viewportRect.width;\n const itemNodes = itemRefs.current\n .slice(0, items.length)\n .filter((item): item is HTMLDivElement => item !== null);\n\n if (viewportWidth <= 0 || itemNodes.length === 0) {\n return;\n }\n\n // Relative positions preserve fractional CSS pixels and cancel the\n // track's current translate transform.\n const trackRect = track.getBoundingClientRect();\n const itemRects = itemNodes.map((item) => item.getBoundingClientRect());\n\n const itemOffsets = itemRects.map((rect) => rect.left - trackRect.left);\n\n let itemsPerPage = 0;\n\n for (const rect of itemRects) {\n const itemEnd = rect.right - trackRect.left;\n\n if (itemEnd <= viewportWidth + 0.01) {\n itemsPerPage += 1;\n } else {\n break;\n }\n }\n\n itemsPerPage = Math.max(1, itemsPerPage);\n\n const numberOfPages = Math.ceil(items.length / itemsPerPage);\n const maxStartIndex = Math.max(0, items.length - itemsPerPage);\n const lastItemEnd = itemRects[itemRects.length - 1]!.right - trackRect.left;\n const maxOffset = Math.max(0, lastItemEnd - viewportWidth);\n\n setLayout((previous) => {\n const unchanged =\n previous.itemsPerPage === itemsPerPage &&\n previous.numberOfPages === numberOfPages &&\n previous.maxStartIndex === maxStartIndex &&\n previous.maxOffset === maxOffset &&\n previous.itemOffsets.length === itemOffsets.length &&\n previous.itemOffsets.every(\n (offset, index) => offset === itemOffsets[index],\n );\n\n if (unchanged) {\n return previous;\n }\n\n return {\n itemsPerPage,\n numberOfPages,\n maxStartIndex,\n maxOffset,\n itemOffsets,\n };\n });\n }, [items.length]);\n\n const itemOrderSignature = items\n .map((item, index) => String(getItemKey ? getItemKey(item, index) : index))\n .join(\"\\u0000\");\n\n useEffect(() => {\n const viewport = viewportRef.current;\n const track = trackRef.current;\n\n if (!viewport || !track) {\n return;\n }\n\n const ownerWindow = viewport.ownerDocument.defaultView ?? window;\n\n measureLayout();\n\n if (typeof ResizeObserver === \"undefined\") {\n ownerWindow.addEventListener(\"resize\", measureLayout);\n\n return () => {\n ownerWindow.removeEventListener(\"resize\", measureLayout);\n };\n }\n\n const observer = new ResizeObserver(measureLayout);\n\n observer.observe(viewport);\n observer.observe(track);\n\n itemRefs.current.slice(0, items.length).forEach((item) => {\n if (item) {\n observer.observe(item);\n }\n });\n\n return () => observer.disconnect();\n }, [\n items.length,\n itemOrderSignature,\n measureLayout,\n gaps.mobile,\n gaps.tablet,\n gaps.desktop,\n ]);\n\n /**\n * currentPanel is always an item index.\n *\n * Restrict it to the last position from which the viewport can be\n * completely filled without scrolling beyond the end of the track.\n */\n const activePanel = Math.min(\n Math.max(0, Math.floor(currentPanel)),\n layout.maxStartIndex,\n );\n\n /**\n * Logical page starts can overlap at the end.\n *\n * Example: 5 items with 3 visible per page:\n * [0, 2], not [0, 3].\n *\n * Example: 8 items with 3 visible per page:\n * [0, 3, 5].\n */\n const pageStartIndices = useMemo(\n () =>\n Array.from({ length: layout.numberOfPages }, (_, pageIndex) => {\n if (pageIndex === layout.numberOfPages - 1) {\n return layout.maxStartIndex;\n }\n\n return Math.min(pageIndex * layout.itemsPerPage, layout.maxStartIndex);\n }),\n [layout.itemsPerPage, layout.maxStartIndex, layout.numberOfPages],\n );\n\n /**\n * Map the active item to the nearest logical page.\n * This lets swipe-to-panel and page-based navigation coexist.\n */\n const activePageIndex = useMemo(() => {\n if (pageStartIndices.length === 0) {\n return 0;\n }\n\n return pageStartIndices.reduce((closestIndex, pageStart, index) => {\n const closestStart = pageStartIndices[closestIndex]!;\n\n return Math.abs(pageStart - activePanel) <\n Math.abs(closestStart - activePanel)\n ? index\n : closestIndex;\n }, 0);\n }, [activePanel, pageStartIndices]);\n\n const clampOffset = useCallback(\n (offset: number) => Math.min(layout.maxOffset, Math.max(0, offset)),\n [layout.maxOffset],\n );\n\n const activeOffset = clampOffset(layout.itemOffsets[activePanel] ?? 0);\n\n useEffect(() => {\n if (dragRef.current || animationFrameRef.current !== null) {\n return;\n }\n\n visualOffsetRef.current = activeOffset;\n }, [activeOffset]);\n\n useEffect(\n () => () => {\n pointerCleanupRef.current?.();\n\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n }\n },\n [],\n );\n\n /**\n * Only correct the controller after layout measurement has completed.\n * This avoids forcing currentPanel to 0 during the initial render.\n */\n useEffect(() => {\n if (layout.numberOfPages > 0 && currentPanel !== activePanel) {\n setPanel(activePanel);\n }\n }, [activePanel, currentPanel, layout.numberOfPages, setPanel]);\n\n const goToPage = useCallback(\n (pageIndex: number) => {\n const targetPage = Math.max(\n 0,\n Math.min(pageIndex, pageStartIndices.length - 1),\n );\n\n const targetPanel = pageStartIndices[targetPage];\n\n if (targetPanel !== undefined) {\n setPanel(targetPanel);\n }\n },\n [pageStartIndices, setPanel],\n );\n\n const goToPreviousPage = useCallback(() => {\n goToPage(activePageIndex - 1);\n }, [activePageIndex, goToPage]);\n\n const goToNextPage = useCallback(() => {\n goToPage(activePageIndex + 1);\n }, [activePageIndex, goToPage]);\n\n /**\n * Start tracking a swipe or mouse drag.\n *\n * Capture on the original element rather than always on the viewport,\n * so a normal click on an interactive child can still work.\n */\n\n const writeTrackOffset = useCallback((offset: number) => {\n visualOffsetRef.current = offset;\n\n if (trackRef.current) {\n trackRef.current.style.transform = `translate3d(${-offset}px, 0, 0)`;\n }\n }, []);\n\n const getNearestPanelIndex = useCallback(\n (offset: number) => {\n let nearestIndex = 0;\n let nearestDistance = Number.POSITIVE_INFINITY;\n\n for (let index = 0; index <= layout.maxStartIndex; index += 1) {\n const candidateOffset = clampOffset(layout.itemOffsets[index] ?? 0);\n\n const distance = Math.abs(candidateOffset - offset);\n\n if (distance < nearestDistance) {\n nearestDistance = distance;\n nearestIndex = index;\n }\n }\n\n return nearestIndex;\n },\n [clampOffset, layout.itemOffsets, layout.maxStartIndex],\n );\n\n /**\n * Animate to a panel using a damped spring.\n *\n * A little underdamping gives the motion a natural overshoot.\n * The target remains a real, reachable item offset.\n */\n const animateToPanel = useCallback(\n (fromOffset: number, targetPanel: number, initialVelocity: number) => {\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n const track = trackRef.current;\n const ownerWindow =\n viewportRef.current?.ownerDocument.defaultView ?? window;\n const targetOffset = clampOffset(layout.itemOffsets[targetPanel] ?? 0);\n\n if (\n !track ||\n ownerWindow.matchMedia(\"(prefers-reduced-motion: reduce)\").matches\n ) {\n writeTrackOffset(targetOffset);\n track?.style.removeProperty(\"transition\");\n setPanel(targetPanel);\n setIsDragging(false);\n setIsAnimating(false);\n return;\n }\n\n track.style.transition = \"none\";\n\n setIsDragging(false);\n setIsAnimating(true);\n\n let position = fromOffset;\n\n // Preserve some release momentum without allowing extreme fling speeds.\n let velocity = Math.max(-1800, Math.min(1800, initialVelocity * 0.4));\n\n let previousTime = performance.now();\n\n const stiffness = 230;\n const damping = 24;\n\n const animate = (now: number) => {\n const deltaSeconds = Math.min((now - previousTime) / 1000, 1 / 30);\n\n previousTime = now;\n\n const springAcceleration = (targetOffset - position) * stiffness;\n\n const dampingAcceleration = velocity * damping;\n\n velocity += (springAcceleration - dampingAcceleration) * deltaSeconds;\n\n position += velocity * deltaSeconds;\n\n const viewportWidth = viewportRef.current?.clientWidth ?? 1;\n\n writeTrackOffset(\n rubberBandOffset(position, layout.maxOffset, viewportWidth),\n );\n\n const positionSettled = Math.abs(targetOffset - position) < 0.5;\n\n const velocitySettled = Math.abs(velocity) < 10;\n\n if (positionSettled && velocitySettled) {\n animationFrameRef.current = null;\n\n writeTrackOffset(targetOffset);\n track.style.removeProperty(\"transition\");\n\n setPanel(targetPanel);\n setIsAnimating(false);\n\n return;\n }\n\n animationFrameRef.current = requestAnimationFrame(animate);\n };\n\n animationFrameRef.current = requestAnimationFrame(animate);\n },\n [\n clampOffset,\n layout.itemOffsets,\n layout.maxOffset,\n setPanel,\n writeTrackOffset,\n ],\n );\n\n const handlePointerDown = useCallback(\n (event: ReactPointerEvent<HTMLDivElement>) => {\n if (!event.isPrimary || dragRef.current) {\n return;\n }\n\n if (event.pointerType === \"mouse\" && event.button !== 0) {\n return;\n }\n\n if (layout.maxOffset <= 0) {\n return;\n }\n\n const track = trackRef.current;\n const viewport = viewportRef.current;\n const ownerWindow = viewport?.ownerDocument.defaultView ?? window;\n\n if (!track || !viewport || !ownerWindow) {\n return;\n }\n\n const interruptedAnimation = animationFrameRef.current !== null;\n\n if (animationFrameRef.current !== null) {\n cancelAnimationFrame(animationFrameRef.current);\n animationFrameRef.current = null;\n }\n\n /*\n * Read the currently displayed offset before disabling the CSS\n * transition. This also handles starting a drag during an animation.\n */\n const startOffset = readRenderedOffset(track, visualOffsetRef.current);\n\n track.style.transition = \"none\";\n writeTrackOffset(startOffset);\n\n setIsAnimating(false);\n setIsDragging(true);\n\n const startTime = performance.now();\n\n const drag: DragState = {\n pointerId: event.pointerId,\n startX: event.clientX,\n startOffset,\n currentOffset: startOffset,\n renderedOffset: startOffset,\n hasMoved: false,\n interruptedAnimation,\n samples: [\n {\n x: event.clientX,\n time: startTime,\n },\n ],\n };\n\n dragRef.current = drag;\n\n const updatePointerPosition = (pointerEvent: PointerEvent) => {\n const deltaX = pointerEvent.clientX - drag.startX;\n\n if (!drag.hasMoved && Math.abs(deltaX) <= 4) {\n return;\n }\n\n drag.hasMoved = true;\n\n // Positive offset means the content moves to the left.\n drag.currentOffset = drag.startOffset - deltaX;\n\n drag.renderedOffset = rubberBandOffset(\n drag.currentOffset,\n layout.maxOffset,\n viewport.clientWidth,\n );\n\n const now = performance.now();\n\n drag.samples.push({\n x: pointerEvent.clientX,\n time: now,\n });\n\n // Retain a short history for release-velocity estimation.\n while (drag.samples.length > 2 && now - drag.samples[1]!.time > 120) {\n drag.samples.shift();\n }\n\n writeTrackOffset(drag.renderedOffset);\n\n if (pointerEvent.cancelable) {\n pointerEvent.preventDefault();\n }\n };\n\n const cleanup = () => {\n ownerWindow.removeEventListener(\"pointermove\", handleWindowPointerMove);\n ownerWindow.removeEventListener(\"pointerup\", handleWindowPointerUp);\n ownerWindow.removeEventListener(\n \"pointercancel\",\n handleWindowPointerCancel,\n );\n ownerWindow.removeEventListener(\"blur\", handleWindowBlur);\n\n if (pointerCleanupRef.current === cleanup) {\n pointerCleanupRef.current = null;\n }\n };\n\n const settleCancelledDrag = () => {\n if (dragRef.current !== drag) {\n return;\n }\n\n cleanup();\n dragRef.current = null;\n setIsDragging(false);\n\n const targetPanel = getNearestPanelIndex(\n clampOffset(drag.currentOffset),\n );\n\n animateToPanel(drag.renderedOffset, targetPanel, 0);\n };\n\n const handleWindowPointerMove = (pointerEvent: PointerEvent) => {\n if (pointerEvent.pointerId !== drag.pointerId) {\n return;\n }\n\n updatePointerPosition(pointerEvent);\n };\n\n const handleWindowPointerUp = (pointerEvent: PointerEvent) => {\n if (pointerEvent.pointerId !== drag.pointerId) {\n return;\n }\n\n // Include the final pointer position, even if no final move fired.\n updatePointerPosition(pointerEvent);\n\n cleanup();\n dragRef.current = null;\n setIsDragging(false);\n\n if (!drag.hasMoved) {\n if (drag.interruptedAnimation) {\n const targetPanel = getNearestPanelIndex(\n clampOffset(drag.startOffset),\n );\n\n animateToPanel(drag.startOffset, targetPanel, 0);\n } else {\n track.style.removeProperty(\"transition\");\n writeTrackOffset(drag.startOffset);\n }\n\n return;\n }\n\n suppressClickRef.current = true;\n\n ownerWindow.setTimeout(() => {\n suppressClickRef.current = false;\n }, 0);\n\n const velocity = estimatePointerVelocity(drag.samples);\n\n /*\n * Project the release position forward. A fast swipe can skip\n * multiple items, while a slow drag generally snaps nearby.\n */\n const projectedOffset = clampOffset(\n clampOffset(drag.currentOffset) + velocity * 0.18,\n );\n\n const targetPanel = getNearestPanelIndex(projectedOffset);\n\n animateToPanel(drag.renderedOffset, targetPanel, velocity);\n };\n\n const handleWindowPointerCancel = (pointerEvent: PointerEvent) => {\n if (pointerEvent.pointerId === drag.pointerId) {\n settleCancelledDrag();\n }\n };\n\n const handleWindowBlur = () => {\n settleCancelledDrag();\n };\n\n pointerCleanupRef.current = cleanup;\n\n /*\n * Listen on window instead of relying on events staying inside the\n * viewport. Releasing outside the carousel still completes the drag.\n */\n ownerWindow.addEventListener(\"pointermove\", handleWindowPointerMove);\n ownerWindow.addEventListener(\"pointerup\", handleWindowPointerUp);\n ownerWindow.addEventListener(\"pointercancel\", handleWindowPointerCancel);\n ownerWindow.addEventListener(\"blur\", handleWindowBlur);\n },\n [\n animateToPanel,\n clampOffset,\n getNearestPanelIndex,\n layout.maxOffset,\n setIsAnimating,\n writeTrackOffset,\n ],\n );\n\n const handleClickCapture = useCallback(\n (event: ReactMouseEvent<HTMLDivElement>) => {\n if (!suppressClickRef.current) {\n return;\n }\n\n event.preventDefault();\n event.stopPropagation();\n\n suppressClickRef.current = false;\n },\n [],\n );\n\n const isFirstPage = activePageIndex === 0;\n const isLastPage =\n layout.numberOfPages <= 1 || activePageIndex >= layout.numberOfPages - 1;\n\n const currentPage = layout.numberOfPages > 0 ? activePageIndex : 0;\n\n return (\n <div\n className={cn(\"puzzle-carousel\", className)}\n style={cssVariables}\n data-sizing={sizingMode}\n data-dragging={isDragging ? \"true\" : \"false\"}\n data-animating={isAnimating ? \"true\" : \"false\"}\n data-single-page={layout.numberOfPages <= 1 ? \"true\" : \"false\"}\n data-single-page-alignment={singlePageAlignment}\n data-button-mobile={buttonAlignments.mobile}\n data-button-tablet={buttonAlignments.tablet}\n data-button-desktop={buttonAlignments.desktop}\n data-pager-mobile={getPagerDataAttribute(pagerAlignments.mobile)}\n data-pager-tablet={getPagerDataAttribute(\n pagerAlignments.tablet ?? pagerAlignments.mobile,\n )}\n data-pager-desktop={getPagerDataAttribute(\n pagerAlignments.desktop ??\n pagerAlignments.tablet ??\n pagerAlignments.mobile,\n )}\n >\n <div className=\"puzzle-carousel__stage\">\n <div className=\"puzzle-carousel__navigation puzzle-carousel__navigation--previous\">\n {cloneNavigationButton(backButton, isFirstPage, goToPreviousPage)}\n </div>\n\n <div\n ref={viewportRef}\n className={cn(\"puzzle-carousel__viewport\", viewportClassName)}\n style={viewportStyle}\n role=\"group\"\n aria-roledescription=\"carousel\"\n aria-label={ariaLabel}\n onPointerDown={handlePointerDown}\n onClickCapture={handleClickCapture}\n onDragStart={(event) => event.preventDefault()}\n >\n <div\n ref={trackRef}\n className=\"puzzle-carousel__track\"\n style={{\n transform: `translate3d(${-activeOffset}px, 0, 0)`,\n }}\n >\n {items.map((item, index) => (\n <div\n key={getItemKey ? getItemKey(item, index) : index}\n ref={(node) => {\n itemRefs.current[index] = node;\n }}\n className=\"puzzle-carousel__item\"\n role=\"group\"\n aria-roledescription=\"slide\"\n aria-label={`${index + 1} of ${items.length}`}\n >\n {renderItem(item, index)}\n </div>\n ))}\n </div>\n </div>\n\n <div className=\"puzzle-carousel__navigation puzzle-carousel__navigation--next\">\n {cloneNavigationButton(nextButton, isLastPage, goToNextPage)}\n </div>\n </div>\n\n {renderPager && layout.numberOfPages > 0 && (\n <div className=\"puzzle-carousel__pager\">\n {renderPager(currentPage, layout.numberOfPages)}\n </div>\n )}\n </div>\n );\n};\n\nexport {\n Carousel,\n type CarouselProps,\n type CarouselAlignment,\n type CarouselButtonAlignment,\n type CarouselPagerAlignment,\n type CarouselAlignmentConfig,\n type PagerAlignmentConfig,\n};\n"],"mappings":"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"}
@@ -24,7 +24,8 @@ type TabsProps = Omit<ComponentPropsWithoutRef<"div">, "children" | "onChange">
24
24
  children: ReactNode;
25
25
  };
26
26
  declare const TabsTab: ({ children }: TabsTabProps) => import("react").JSX.Element;
27
- declare const Tabs: (({ tabBarPosition, initialActiveId, activeId, onActivate, forceMountContent, children, className, ...rootProps }: TabsProps) => import("react").JSX.Element) & {
28
- Tab: ({ children }: TabsTabProps) => import("react").JSX.Element;
27
+ declare const TabsRoot: ({ tabBarPosition, initialActiveId, activeId, onActivate, forceMountContent, children, className, ...rootProps }: TabsProps) => import("react").JSX.Element;
28
+ declare const Tabs: typeof TabsRoot & {
29
+ Tab: typeof TabsTab;
29
30
  };
30
31
  export { Tabs, type TabsPosition, type TabsProps, TabsTab, type TabsTabProps };
@@ -24,7 +24,8 @@ type TabsProps = Omit<ComponentPropsWithoutRef<"div">, "children" | "onChange">
24
24
  children: ReactNode;
25
25
  };
26
26
  declare const TabsTab: ({ children }: TabsTabProps) => import("react").JSX.Element;
27
- declare const Tabs: (({ tabBarPosition, initialActiveId, activeId, onActivate, forceMountContent, children, className, ...rootProps }: TabsProps) => import("react").JSX.Element) & {
28
- Tab: ({ children }: TabsTabProps) => import("react").JSX.Element;
27
+ declare const TabsRoot: ({ tabBarPosition, initialActiveId, activeId, onActivate, forceMountContent, children, className, ...rootProps }: TabsProps) => import("react").JSX.Element;
28
+ declare const Tabs: typeof TabsRoot & {
29
+ Tab: typeof TabsTab;
29
30
  };
30
31
  export { Tabs, type TabsPosition, type TabsProps, TabsTab, type TabsTabProps };
@@ -0,0 +1,151 @@
1
+ .puzzle-context-menu__anchor {
2
+ z-index: -1;
3
+ pointer-events: none;
4
+ width: 1px;
5
+ height: 1px;
6
+ position: fixed;
7
+ }
8
+
9
+ .puzzle-context-menu__trigger {
10
+ touch-action: manipulation;
11
+ }
12
+
13
+ .puzzle-context-menu__trigger:focus-visible {
14
+ outline: 2px solid var(--puzzle-color-ring);
15
+ outline-offset: 2px;
16
+ }
17
+
18
+ .puzzle-popout__content.puzzle-context-menu__popover {
19
+ min-width: 11rem;
20
+ padding: var(--puzzle-space-1, .25rem);
21
+ scrollbar-width: thin;
22
+ overflow: hidden auto;
23
+ }
24
+
25
+ .puzzle-context-menu__content {
26
+ gap: var(--puzzle-space-1, .25rem);
27
+ overscroll-behavior: contain;
28
+ scrollbar-width: thin;
29
+ outline: none;
30
+ flex-direction: column;
31
+ min-width: 0;
32
+ max-width: min(20rem, 100vw - 1rem);
33
+ display: flex;
34
+ overflow: hidden auto;
35
+ }
36
+
37
+ .puzzle-context-menu__group {
38
+ gap: var(--puzzle-space-1, .25rem);
39
+ flex-direction: column;
40
+ min-width: 0;
41
+ display: flex;
42
+ }
43
+
44
+ .puzzle-context-menu__label {
45
+ padding: var(--puzzle-space-2, .5rem) var(--puzzle-space-3, .75rem);
46
+ color: var(--puzzle-color-text-muted);
47
+ font-size: var(--puzzle-font-size-xs, .75rem);
48
+ font-weight: var(--puzzle-font-weight-semibold, 600);
49
+ line-height: var(--puzzle-font-line-height-body2, 1.4);
50
+ }
51
+
52
+ .puzzle-context-menu__item {
53
+ box-sizing: border-box;
54
+ align-items: center;
55
+ gap: var(--puzzle-space-2, .5rem);
56
+ width: 100%;
57
+ min-width: 0;
58
+ min-height: 2.25rem;
59
+ padding: var(--puzzle-space-2, .5rem) var(--puzzle-space-3, .75rem);
60
+ border-radius: var(--puzzle-radius-sm, .25rem);
61
+ color: var(--puzzle-color-text);
62
+ font: inherit;
63
+ font-size: var(--puzzle-font-size-sm, .875rem);
64
+ line-height: var(--puzzle-font-line-height-body2, 1.4);
65
+ text-align: start;
66
+ cursor: pointer;
67
+ user-select: none;
68
+ touch-action: manipulation;
69
+ transition: color var(--puzzle-duration-fast, .15s) ease,
70
+ background-color var(--puzzle-duration-fast, .15s) ease,
71
+ border-color var(--puzzle-duration-fast, .15s) ease;
72
+ background: none;
73
+ border: 1px solid #0000;
74
+ text-decoration: none;
75
+ display: flex;
76
+ }
77
+
78
+ .puzzle-context-menu__item:hover:not(:disabled):not([aria-disabled="true"]), .puzzle-context-menu__item:focus-visible:not(:disabled):not([aria-disabled="true"]) {
79
+ background: var(--puzzle-color-surface-sunken);
80
+ outline: none;
81
+ }
82
+
83
+ .puzzle-context-menu__item:focus-visible:not(:disabled):not([aria-disabled="true"]) {
84
+ border-color: var(--puzzle-color-ring);
85
+ }
86
+
87
+ .puzzle-context-menu__item:disabled, .puzzle-context-menu__item[aria-disabled="true"] {
88
+ opacity: .5;
89
+ cursor: not-allowed;
90
+ }
91
+
92
+ .puzzle-context-menu__item--danger {
93
+ color: var(--puzzle-color-danger);
94
+ }
95
+
96
+ .puzzle-context-menu__item--danger:hover:not(:disabled):not([aria-disabled="true"]), .puzzle-context-menu__item--danger:focus-visible:not(:disabled):not([aria-disabled="true"]) {
97
+ background: color-mix(in srgb, var(--puzzle-color-danger), transparent 92%);
98
+ }
99
+
100
+ .puzzle-context-menu__item-label {
101
+ flex: auto;
102
+ min-width: 0;
103
+ }
104
+
105
+ .puzzle-context-menu__indicator {
106
+ width: 1rem;
107
+ min-height: 1rem;
108
+ color: var(--puzzle-color-primary);
109
+ font-size: .875rem;
110
+ font-weight: var(--puzzle-font-weight-semibold, 600);
111
+ flex: 0 0 1rem;
112
+ justify-content: center;
113
+ align-items: center;
114
+ display: inline-flex;
115
+ }
116
+
117
+ .puzzle-context-menu__submenu-indicator {
118
+ color: var(--puzzle-color-text-muted);
119
+ flex: none;
120
+ margin-inline-start: var(--puzzle-space-2, .5rem);
121
+ font-size: 1.125rem;
122
+ line-height: 1;
123
+ }
124
+
125
+ .puzzle-context-menu__separator {
126
+ height: 1px;
127
+ margin-block: var(--puzzle-space-1, .25rem);
128
+ background: var(--puzzle-color-border);
129
+ flex: none;
130
+ }
131
+
132
+ .puzzle-context-menu__submenu-popover {
133
+ z-index: 1;
134
+ }
135
+
136
+ @media (prefers-reduced-motion: reduce) {
137
+ .puzzle-context-menu__item, .puzzle-context-menu__trigger {
138
+ transition: none;
139
+ }
140
+ }
141
+
142
+ @media (forced-colors: active) {
143
+ .puzzle-context-menu__item:focus-visible:not(:disabled), .puzzle-context-menu__trigger:focus-visible {
144
+ outline-offset: 2px;
145
+ outline: 2px solid highlight;
146
+ }
147
+
148
+ .puzzle-context-menu__separator {
149
+ background: canvastext;
150
+ }
151
+ }
@@ -0,0 +1,69 @@
1
+ import { ButtonHTMLAttributes, CSSProperties, HTMLAttributes, MouseEvent, ReactNode, Ref } from "react";
2
+ export type ContextMenuItemVariant = "default" | "danger";
3
+ export type ContextMenuCheckedState = boolean | "indeterminate";
4
+ export type ContextMenuProps = {
5
+ children: ReactNode;
6
+ open?: boolean;
7
+ defaultOpen?: boolean;
8
+ onOpenChange?: (open: boolean) => void;
9
+ disabled?: boolean;
10
+ triggerClassName?: string;
11
+ contentClassName?: string;
12
+ };
13
+ export type ContextMenuTriggerProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
14
+ children: ReactNode;
15
+ asChild?: boolean;
16
+ disabled?: boolean;
17
+ ref?: Ref<HTMLElement>;
18
+ };
19
+ export type ContextMenuContentProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
20
+ children: ReactNode;
21
+ maxHeight?: CSSProperties["maxHeight"];
22
+ };
23
+ export type ContextMenuItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
24
+ children: ReactNode;
25
+ textValue?: string;
26
+ closeOnSelect?: boolean;
27
+ variant?: ContextMenuItemVariant;
28
+ onSelect?: (event: MouseEvent<HTMLElement>) => void;
29
+ };
30
+ export type ContextMenuGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
31
+ children: ReactNode;
32
+ };
33
+ export type ContextMenuLabelProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
34
+ children: ReactNode;
35
+ };
36
+ export type ContextMenuSeparatorProps = HTMLAttributes<HTMLDivElement>;
37
+ export type ContextMenuCheckboxItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
38
+ children: ReactNode;
39
+ checked?: ContextMenuCheckedState;
40
+ defaultChecked?: boolean;
41
+ onCheckedChange?: (checked: boolean) => void;
42
+ closeOnSelect?: boolean;
43
+ textValue?: string;
44
+ onSelect?: (event: MouseEvent<HTMLElement>) => void;
45
+ };
46
+ export type ContextMenuRadioGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
47
+ children: ReactNode;
48
+ value?: string;
49
+ defaultValue?: string;
50
+ onValueChange?: (value: string) => void;
51
+ };
52
+ export type ContextMenuRadioItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
53
+ children: ReactNode;
54
+ value: string;
55
+ closeOnSelect?: boolean;
56
+ textValue?: string;
57
+ onSelect?: (event: MouseEvent<HTMLElement>) => void;
58
+ };
59
+ export type ContextMenuSubProps = {
60
+ children: ReactNode;
61
+ defaultOpen?: boolean;
62
+ };
63
+ export type ContextMenuSubTriggerProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
64
+ children: ReactNode;
65
+ };
66
+ export type ContextMenuSubContentProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
67
+ children: ReactNode;
68
+ maxHeight?: CSSProperties["maxHeight"];
69
+ };
@@ -0,0 +1,69 @@
1
+ import { ButtonHTMLAttributes, CSSProperties, HTMLAttributes, MouseEvent, ReactNode, Ref } from "react";
2
+ export type ContextMenuItemVariant = "default" | "danger";
3
+ export type ContextMenuCheckedState = boolean | "indeterminate";
4
+ export type ContextMenuProps = {
5
+ children: ReactNode;
6
+ open?: boolean;
7
+ defaultOpen?: boolean;
8
+ onOpenChange?: (open: boolean) => void;
9
+ disabled?: boolean;
10
+ triggerClassName?: string;
11
+ contentClassName?: string;
12
+ };
13
+ export type ContextMenuTriggerProps = Omit<HTMLAttributes<HTMLElement>, "children"> & {
14
+ children: ReactNode;
15
+ asChild?: boolean;
16
+ disabled?: boolean;
17
+ ref?: Ref<HTMLElement>;
18
+ };
19
+ export type ContextMenuContentProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
20
+ children: ReactNode;
21
+ maxHeight?: CSSProperties["maxHeight"];
22
+ };
23
+ export type ContextMenuItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
24
+ children: ReactNode;
25
+ textValue?: string;
26
+ closeOnSelect?: boolean;
27
+ variant?: ContextMenuItemVariant;
28
+ onSelect?: (event: MouseEvent<HTMLElement>) => void;
29
+ };
30
+ export type ContextMenuGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
31
+ children: ReactNode;
32
+ };
33
+ export type ContextMenuLabelProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
34
+ children: ReactNode;
35
+ };
36
+ export type ContextMenuSeparatorProps = HTMLAttributes<HTMLDivElement>;
37
+ export type ContextMenuCheckboxItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
38
+ children: ReactNode;
39
+ checked?: ContextMenuCheckedState;
40
+ defaultChecked?: boolean;
41
+ onCheckedChange?: (checked: boolean) => void;
42
+ closeOnSelect?: boolean;
43
+ textValue?: string;
44
+ onSelect?: (event: MouseEvent<HTMLElement>) => void;
45
+ };
46
+ export type ContextMenuRadioGroupProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
47
+ children: ReactNode;
48
+ value?: string;
49
+ defaultValue?: string;
50
+ onValueChange?: (value: string) => void;
51
+ };
52
+ export type ContextMenuRadioItemProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children" | "onSelect"> & {
53
+ children: ReactNode;
54
+ value: string;
55
+ closeOnSelect?: boolean;
56
+ textValue?: string;
57
+ onSelect?: (event: MouseEvent<HTMLElement>) => void;
58
+ };
59
+ export type ContextMenuSubProps = {
60
+ children: ReactNode;
61
+ defaultOpen?: boolean;
62
+ };
63
+ export type ContextMenuSubTriggerProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> & {
64
+ children: ReactNode;
65
+ };
66
+ export type ContextMenuSubContentProps = Omit<HTMLAttributes<HTMLDivElement>, "children"> & {
67
+ children: ReactNode;
68
+ maxHeight?: CSSProperties["maxHeight"];
69
+ };
@@ -0,0 +1,46 @@
1
+ // src/components/overlays/context-menu/context-menu.utils.ts
2
+ const normalizeMenuText = (value) => value.normalize("NFKD").replace(/\p{Diacritic}/gu, "").trim().toLocaleLowerCase();
3
+ const findNextMenuItemIndex = (items, currentIndex, direction, loop = true) => {
4
+ if (items.length === 0) return -1;
5
+ for (let step = 1; step <= items.length; step += 1) {
6
+ let index = currentIndex + step * direction;
7
+ if (loop) index = (index + items.length) % items.length;
8
+ else if (index < 0 || index >= items.length) return -1;
9
+ if (!items[index].disabled) return index;
10
+ }
11
+ return -1;
12
+ };
13
+ const findMenuItemIndexByText = (items, query, currentIndex = -1) => {
14
+ const normalizedQuery = normalizeMenuText(query);
15
+ if (!normalizedQuery || items.length === 0) return -1;
16
+ for (let step = 1; step <= items.length; step += 1) {
17
+ const index = (currentIndex + step + items.length) % items.length;
18
+ const item = items[index];
19
+ if (!item.disabled && normalizeMenuText(item.text ?? "").startsWith(normalizedQuery)) return index;
20
+ }
21
+ return -1;
22
+ };
23
+ const setRef = (ref, value) => {
24
+ if (typeof ref === "function") ref(value);
25
+ else if (ref) ref.current = value;
26
+ };
27
+ const getEnabledMenuItems = (menu) => Array.from(menu.querySelectorAll("[data-puzzle-context-menu-item=\"true\"]")).filter((item) => item.getAttribute("aria-disabled") !== "true" && !("disabled" in item && Boolean(item.disabled)) && !item.hasAttribute("hidden") && !item.closest("[hidden], [inert], [aria-hidden='true']"));
28
+ const focusElement = (element) => {
29
+ if (!element) return;
30
+ try {
31
+ element.focus({ preventScroll: true });
32
+ } catch {
33
+ element.focus();
34
+ }
35
+ element.scrollIntoView?.({ block: "nearest" });
36
+ };
37
+ const getMenuItemText = (item) => item.dataset.textValue ?? item.querySelector(".puzzle-context-menu__item-label")?.textContent ?? item.textContent ?? "";
38
+ exports.findMenuItemIndexByText = findMenuItemIndexByText;
39
+ exports.findNextMenuItemIndex = findNextMenuItemIndex;
40
+ exports.focusElement = focusElement;
41
+ exports.getEnabledMenuItems = getEnabledMenuItems;
42
+ exports.getMenuItemText = getMenuItemText;
43
+ exports.normalizeMenuText = normalizeMenuText;
44
+ exports.setRef = setRef;
45
+
46
+ //# sourceMappingURL=context-menu.utils.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-menu.utils.cjs","names":[],"sources":["../../../../src/components/overlays/context-menu/context-menu.utils.ts"],"sourcesContent":["import type { MutableRefObject, Ref } from \"react\";\n\nexport type MenuItemLike = {\n disabled?: boolean;\n text?: string;\n};\n\nconst normalizeMenuText = (value: string): string =>\n value\n .normalize(\"NFKD\")\n .replace(/\\p{Diacritic}/gu, \"\")\n .trim()\n .toLocaleLowerCase();\n\nconst findNextMenuItemIndex = <T extends MenuItemLike>(\n items: readonly T[],\n currentIndex: number,\n direction: -1 | 1,\n loop = true,\n): number => {\n if (items.length === 0) {\n return -1;\n }\n\n for (let step = 1; step <= items.length; step += 1) {\n let index = currentIndex + step * direction;\n\n if (loop) {\n index = (index + items.length) % items.length;\n } else if (index < 0 || index >= items.length) {\n return -1;\n }\n\n if (!items[index]!.disabled) {\n return index;\n }\n }\n\n return -1;\n};\n\nconst findMenuItemIndexByText = <T extends MenuItemLike>(\n items: readonly T[],\n query: string,\n currentIndex = -1,\n): number => {\n const normalizedQuery = normalizeMenuText(query);\n\n if (!normalizedQuery || items.length === 0) {\n return -1;\n }\n\n for (let step = 1; step <= items.length; step += 1) {\n const index = (currentIndex + step + items.length) % items.length;\n const item = items[index]!;\n\n if (\n !item.disabled &&\n normalizeMenuText(item.text ?? \"\").startsWith(normalizedQuery)\n ) {\n return index;\n }\n }\n\n return -1;\n};\n\nconst setRef = <T>(ref: Ref<T> | undefined, value: T | null): void => {\n if (typeof ref === \"function\") {\n ref(value);\n } else if (ref) {\n (ref as MutableRefObject<T | null>).current = value;\n }\n};\n\nconst getEnabledMenuItems = (menu: HTMLElement): HTMLElement[] =>\n Array.from(\n menu.querySelectorAll<HTMLElement>(\n '[data-puzzle-context-menu-item=\"true\"]',\n ),\n ).filter(\n (item) =>\n item.getAttribute(\"aria-disabled\") !== \"true\" &&\n !(\"disabled\" in item && Boolean((item as HTMLButtonElement).disabled)) &&\n !item.hasAttribute(\"hidden\") &&\n !item.closest(\"[hidden], [inert], [aria-hidden='true']\"),\n );\n\nconst focusElement = (element: HTMLElement | undefined): void => {\n if (!element) {\n return;\n }\n\n try {\n element.focus({ preventScroll: true });\n } catch {\n element.focus();\n }\n\n element.scrollIntoView?.({ block: \"nearest\" });\n};\n\nconst getMenuItemText = (item: HTMLElement): string =>\n item.dataset.textValue ??\n item.querySelector(\".puzzle-context-menu__item-label\")?.textContent ??\n item.textContent ??\n \"\";\n\nexport {\n findMenuItemIndexByText,\n findNextMenuItemIndex,\n focusElement,\n getEnabledMenuItems,\n getMenuItemText,\n normalizeMenuText,\n setRef,\n};\n"],"mappings":";AAOA,MAAM,qBAAqB,UACzB,MACG,UAAU,MAAM,CAAC,CACjB,QAAQ,mBAAmB,EAAE,CAAC,CAC9B,KAAK,CAAC,CACN,kBAAkB;AAEvB,MAAM,yBACJ,OACA,cACA,WACA,OAAO,SACI;CACX,IAAI,MAAM,WAAW,GACnB,OAAO;CAGT,KAAK,IAAI,OAAO,GAAG,QAAQ,MAAM,QAAQ,QAAQ,GAAG;EAClD,IAAI,QAAQ,eAAe,OAAO;EAElC,IAAI,MACF,SAAS,QAAQ,MAAM,UAAU,MAAM;OAClC,IAAI,QAAQ,KAAK,SAAS,MAAM,QACrC,OAAO;EAGT,IAAI,CAAC,MAAM,MAAM,CAAE,UACjB,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,MAAM,2BACJ,OACA,OACA,eAAe,OACJ;CACX,MAAM,kBAAkB,kBAAkB,KAAK;CAE/C,IAAI,CAAC,mBAAmB,MAAM,WAAW,GACvC,OAAO;CAGT,KAAK,IAAI,OAAO,GAAG,QAAQ,MAAM,QAAQ,QAAQ,GAAG;EAClD,MAAM,SAAS,eAAe,OAAO,MAAM,UAAU,MAAM;EAC3D,MAAM,OAAO,MAAM;EAEnB,IACE,CAAC,KAAK,YACN,kBAAkB,KAAK,QAAQ,EAAE,CAAC,CAAC,WAAW,eAAe,GAE7D,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,MAAM,UAAa,KAAyB,UAA0B;CACpE,IAAI,OAAO,QAAQ,YACjB,IAAI,KAAK;MACJ,IAAI,KACT,IAAoC,UAAU;AAElD;AAEA,MAAM,uBAAuB,SAC3B,MAAM,KACJ,KAAK,iBACH,0CACF,CACF,CAAC,CAAC,QACC,SACC,KAAK,aAAa,eAAe,MAAM,UACvC,EAAE,cAAc,QAAQ,QAAS,KAA2B,QAAQ,MACpE,CAAC,KAAK,aAAa,QAAQ,KAC3B,CAAC,KAAK,QAAQ,yCAAyC,CAC3D;AAEF,MAAM,gBAAgB,YAA2C;CAC/D,IAAI,CAAC,SACH;CAGF,IAAI;EACF,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;CACvC,QAAQ;EACN,QAAQ,MAAM;CAChB;CAEA,QAAQ,iBAAiB,EAAE,OAAO,UAAU,CAAC;AAC/C;AAEA,MAAM,mBAAmB,SACvB,KAAK,QAAQ,aACb,KAAK,cAAc,kCAAkC,CAAC,EAAE,eACxD,KAAK,eACL"}
@@ -0,0 +1,40 @@
1
+ // src/components/overlays/context-menu/context-menu.utils.ts
2
+ const normalizeMenuText = (value) => value.normalize("NFKD").replace(/\p{Diacritic}/gu, "").trim().toLocaleLowerCase();
3
+ const findNextMenuItemIndex = (items, currentIndex, direction, loop = true) => {
4
+ if (items.length === 0) return -1;
5
+ for (let step = 1; step <= items.length; step += 1) {
6
+ let index = currentIndex + step * direction;
7
+ if (loop) index = (index + items.length) % items.length;
8
+ else if (index < 0 || index >= items.length) return -1;
9
+ if (!items[index].disabled) return index;
10
+ }
11
+ return -1;
12
+ };
13
+ const findMenuItemIndexByText = (items, query, currentIndex = -1) => {
14
+ const normalizedQuery = normalizeMenuText(query);
15
+ if (!normalizedQuery || items.length === 0) return -1;
16
+ for (let step = 1; step <= items.length; step += 1) {
17
+ const index = (currentIndex + step + items.length) % items.length;
18
+ const item = items[index];
19
+ if (!item.disabled && normalizeMenuText(item.text ?? "").startsWith(normalizedQuery)) return index;
20
+ }
21
+ return -1;
22
+ };
23
+ const setRef = (ref, value) => {
24
+ if (typeof ref === "function") ref(value);
25
+ else if (ref) ref.current = value;
26
+ };
27
+ const getEnabledMenuItems = (menu) => Array.from(menu.querySelectorAll("[data-puzzle-context-menu-item=\"true\"]")).filter((item) => item.getAttribute("aria-disabled") !== "true" && !("disabled" in item && Boolean(item.disabled)) && !item.hasAttribute("hidden") && !item.closest("[hidden], [inert], [aria-hidden='true']"));
28
+ const focusElement = (element) => {
29
+ if (!element) return;
30
+ try {
31
+ element.focus({ preventScroll: true });
32
+ } catch {
33
+ element.focus();
34
+ }
35
+ element.scrollIntoView?.({ block: "nearest" });
36
+ };
37
+ const getMenuItemText = (item) => item.dataset.textValue ?? item.querySelector(".puzzle-context-menu__item-label")?.textContent ?? item.textContent ?? "";
38
+ export { findMenuItemIndexByText, findNextMenuItemIndex, focusElement, getEnabledMenuItems, getMenuItemText, normalizeMenuText, setRef };
39
+
40
+ //# sourceMappingURL=context-menu.utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context-menu.utils.js","names":[],"sources":["../../../../src/components/overlays/context-menu/context-menu.utils.ts"],"sourcesContent":["import type { MutableRefObject, Ref } from \"react\";\n\nexport type MenuItemLike = {\n disabled?: boolean;\n text?: string;\n};\n\nconst normalizeMenuText = (value: string): string =>\n value\n .normalize(\"NFKD\")\n .replace(/\\p{Diacritic}/gu, \"\")\n .trim()\n .toLocaleLowerCase();\n\nconst findNextMenuItemIndex = <T extends MenuItemLike>(\n items: readonly T[],\n currentIndex: number,\n direction: -1 | 1,\n loop = true,\n): number => {\n if (items.length === 0) {\n return -1;\n }\n\n for (let step = 1; step <= items.length; step += 1) {\n let index = currentIndex + step * direction;\n\n if (loop) {\n index = (index + items.length) % items.length;\n } else if (index < 0 || index >= items.length) {\n return -1;\n }\n\n if (!items[index]!.disabled) {\n return index;\n }\n }\n\n return -1;\n};\n\nconst findMenuItemIndexByText = <T extends MenuItemLike>(\n items: readonly T[],\n query: string,\n currentIndex = -1,\n): number => {\n const normalizedQuery = normalizeMenuText(query);\n\n if (!normalizedQuery || items.length === 0) {\n return -1;\n }\n\n for (let step = 1; step <= items.length; step += 1) {\n const index = (currentIndex + step + items.length) % items.length;\n const item = items[index]!;\n\n if (\n !item.disabled &&\n normalizeMenuText(item.text ?? \"\").startsWith(normalizedQuery)\n ) {\n return index;\n }\n }\n\n return -1;\n};\n\nconst setRef = <T>(ref: Ref<T> | undefined, value: T | null): void => {\n if (typeof ref === \"function\") {\n ref(value);\n } else if (ref) {\n (ref as MutableRefObject<T | null>).current = value;\n }\n};\n\nconst getEnabledMenuItems = (menu: HTMLElement): HTMLElement[] =>\n Array.from(\n menu.querySelectorAll<HTMLElement>(\n '[data-puzzle-context-menu-item=\"true\"]',\n ),\n ).filter(\n (item) =>\n item.getAttribute(\"aria-disabled\") !== \"true\" &&\n !(\"disabled\" in item && Boolean((item as HTMLButtonElement).disabled)) &&\n !item.hasAttribute(\"hidden\") &&\n !item.closest(\"[hidden], [inert], [aria-hidden='true']\"),\n );\n\nconst focusElement = (element: HTMLElement | undefined): void => {\n if (!element) {\n return;\n }\n\n try {\n element.focus({ preventScroll: true });\n } catch {\n element.focus();\n }\n\n element.scrollIntoView?.({ block: \"nearest\" });\n};\n\nconst getMenuItemText = (item: HTMLElement): string =>\n item.dataset.textValue ??\n item.querySelector(\".puzzle-context-menu__item-label\")?.textContent ??\n item.textContent ??\n \"\";\n\nexport {\n findMenuItemIndexByText,\n findNextMenuItemIndex,\n focusElement,\n getEnabledMenuItems,\n getMenuItemText,\n normalizeMenuText,\n setRef,\n};\n"],"mappings":";AAOA,MAAM,qBAAqB,UACzB,MACG,UAAU,MAAM,CAAC,CACjB,QAAQ,mBAAmB,EAAE,CAAC,CAC9B,KAAK,CAAC,CACN,kBAAkB;AAEvB,MAAM,yBACJ,OACA,cACA,WACA,OAAO,SACI;CACX,IAAI,MAAM,WAAW,GACnB,OAAO;CAGT,KAAK,IAAI,OAAO,GAAG,QAAQ,MAAM,QAAQ,QAAQ,GAAG;EAClD,IAAI,QAAQ,eAAe,OAAO;EAElC,IAAI,MACF,SAAS,QAAQ,MAAM,UAAU,MAAM;OAClC,IAAI,QAAQ,KAAK,SAAS,MAAM,QACrC,OAAO;EAGT,IAAI,CAAC,MAAM,MAAM,CAAE,UACjB,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,MAAM,2BACJ,OACA,OACA,eAAe,OACJ;CACX,MAAM,kBAAkB,kBAAkB,KAAK;CAE/C,IAAI,CAAC,mBAAmB,MAAM,WAAW,GACvC,OAAO;CAGT,KAAK,IAAI,OAAO,GAAG,QAAQ,MAAM,QAAQ,QAAQ,GAAG;EAClD,MAAM,SAAS,eAAe,OAAO,MAAM,UAAU,MAAM;EAC3D,MAAM,OAAO,MAAM;EAEnB,IACE,CAAC,KAAK,YACN,kBAAkB,KAAK,QAAQ,EAAE,CAAC,CAAC,WAAW,eAAe,GAE7D,OAAO;CAEX;CAEA,OAAO;AACT;AAEA,MAAM,UAAa,KAAyB,UAA0B;CACpE,IAAI,OAAO,QAAQ,YACjB,IAAI,KAAK;MACJ,IAAI,KACT,IAAoC,UAAU;AAElD;AAEA,MAAM,uBAAuB,SAC3B,MAAM,KACJ,KAAK,iBACH,0CACF,CACF,CAAC,CAAC,QACC,SACC,KAAK,aAAa,eAAe,MAAM,UACvC,EAAE,cAAc,QAAQ,QAAS,KAA2B,QAAQ,MACpE,CAAC,KAAK,aAAa,QAAQ,KAC3B,CAAC,KAAK,QAAQ,yCAAyC,CAC3D;AAEF,MAAM,gBAAgB,YAA2C;CAC/D,IAAI,CAAC,SACH;CAGF,IAAI;EACF,QAAQ,MAAM,EAAE,eAAe,KAAK,CAAC;CACvC,QAAQ;EACN,QAAQ,MAAM;CAChB;CAEA,QAAQ,iBAAiB,EAAE,OAAO,UAAU,CAAC;AAC/C;AAEA,MAAM,mBAAmB,SACvB,KAAK,QAAQ,aACb,KAAK,cAAc,kCAAkC,CAAC,EAAE,eACxD,KAAK,eACL"}