{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "carousel",
  "dependencies": [
    "embla-carousel"
  ],
  "registryDependencies": [
    "@fulldev/button",
    "@fulldev/icon"
  ],
  "files": [
    {
      "path": "src/components/ui/carousel/carousel.astro",
      "content": "---\nimport type { HTMLAttributes } from \"astro/types\"\nimport type { EmblaOptionsType } from \"embla-carousel\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype Props = HTMLAttributes<\"div\"> & {\n  opts?: EmblaOptionsType\n  orientation?: \"horizontal\" | \"vertical\"\n}\n\nconst {\n  class: className,\n  opts,\n  orientation = \"horizontal\",\n  ...props\n} = Astro.props\n\nconst resolvedOpts: EmblaOptionsType = {\n  ...opts,\n  axis: orientation === \"horizontal\" ? \"x\" : \"y\",\n}\n---\n\n<div\n  data-slot=\"carousel\"\n  data-orientation={orientation}\n  data-carousel-options={JSON.stringify(resolvedOpts)}\n  class={cn(\"group/carousel relative\", className)}\n  role=\"region\"\n  aria-roledescription=\"carousel\"\n  {...props}\n>\n  <slot />\n</div>\n\n<script>\n  import EmblaCarousel, {\n    type EmblaCarouselType,\n    type EmblaOptionsType,\n  } from \"embla-carousel\"\n\n  type CarouselRoot = HTMLElement & {\n    __fulldevCarouselApi?: EmblaCarouselType\n  }\n\n  type CarouselInstance = {\n    cleanup: () => void\n  }\n\n  type CarouselStore = Map<CarouselRoot, CarouselInstance>\n\n  declare global {\n    interface Window {\n      __fulldevCarouselInitialized?: boolean\n      __fulldevCarouselStore?: CarouselStore\n    }\n  }\n\n  if (!window.__fulldevCarouselInitialized) {\n    window.__fulldevCarouselInitialized = true\n    window.__fulldevCarouselStore ??= new Map<CarouselRoot, CarouselInstance>()\n\n    const store = window.__fulldevCarouselStore\n\n    const getDetails = (api: EmblaCarouselType) => ({\n      api,\n      canScrollPrev: api.canScrollPrev(),\n      canScrollNext: api.canScrollNext(),\n      selectedScrollSnap: api.selectedScrollSnap(),\n      scrollSnapList: api.scrollSnapList(),\n    })\n\n    const emit = (\n      root: CarouselRoot,\n      type: \"carousel:init\" | \"carousel:select\",\n      api: EmblaCarouselType\n    ) => {\n      root.dispatchEvent(\n        new CustomEvent(type, {\n          detail: getDetails(api),\n        })\n      )\n    }\n\n    const parseOptions = (root: CarouselRoot): EmblaOptionsType => {\n      const rawOptions = root.getAttribute(\"data-carousel-options\")\n\n      if (!rawOptions) {\n        return {}\n      }\n\n      try {\n        return JSON.parse(rawOptions) as EmblaOptionsType\n      } catch {\n        return {}\n      }\n    }\n\n    const syncButton = (button: Element | null, enabled: boolean) => {\n      if (!(button instanceof HTMLButtonElement)) {\n        return\n      }\n\n      if (button.dataset.manualDisabled === \"true\") {\n        button.disabled = true\n        return\n      }\n\n      button.disabled = !enabled\n    }\n\n    const cleanupDisconnectedCarousels = () => {\n      for (const [root, instance] of store.entries()) {\n        if (document.contains(root)) {\n          continue\n        }\n\n        instance.cleanup()\n        store.delete(root)\n      }\n    }\n\n    const initCarousel = (root: Element) => {\n      if (!(root instanceof HTMLElement) || store.has(root)) {\n        return\n      }\n\n      const carouselRoot = root as CarouselRoot\n      const viewport = carouselRoot.querySelector(\n        '[data-slot=\"carousel-content\"]'\n      )\n      const prevButton = carouselRoot.querySelector(\n        '[data-slot=\"carousel-previous\"]'\n      )\n      const nextButton = carouselRoot.querySelector(\n        '[data-slot=\"carousel-next\"]'\n      )\n\n      if (!(viewport instanceof HTMLElement)) {\n        return\n      }\n\n      const api = EmblaCarousel(viewport, parseOptions(carouselRoot))\n\n      const sync = () => {\n        const details = getDetails(api)\n\n        carouselRoot.dataset.canScrollPrev = details.canScrollPrev\n          ? \"true\"\n          : \"false\"\n        carouselRoot.dataset.canScrollNext = details.canScrollNext\n          ? \"true\"\n          : \"false\"\n        carouselRoot.dataset.selectedScrollSnap = String(\n          details.selectedScrollSnap\n        )\n\n        syncButton(prevButton, details.canScrollPrev)\n        syncButton(nextButton, details.canScrollNext)\n\n        emit(carouselRoot, \"carousel:select\", api)\n      }\n\n      const onPrevClick = () => {\n        api.scrollPrev()\n      }\n\n      const onNextClick = () => {\n        api.scrollNext()\n      }\n\n      const onKeyDown = (event: KeyboardEvent) => {\n        const isVertical = carouselRoot.dataset.orientation === \"vertical\"\n\n        if (!isVertical && event.key === \"ArrowLeft\") {\n          event.preventDefault()\n          api.scrollPrev()\n        }\n\n        if (!isVertical && event.key === \"ArrowRight\") {\n          event.preventDefault()\n          api.scrollNext()\n        }\n\n        if (isVertical && event.key === \"ArrowUp\") {\n          event.preventDefault()\n          api.scrollPrev()\n        }\n\n        if (isVertical && event.key === \"ArrowDown\") {\n          event.preventDefault()\n          api.scrollNext()\n        }\n      }\n\n      if (prevButton instanceof HTMLElement) {\n        prevButton.addEventListener(\"click\", onPrevClick)\n      }\n\n      if (nextButton instanceof HTMLElement) {\n        nextButton.addEventListener(\"click\", onNextClick)\n      }\n\n      carouselRoot.addEventListener(\"keydown\", onKeyDown, true)\n\n      api.on(\"reInit\", sync)\n      api.on(\"select\", sync)\n\n      carouselRoot.__fulldevCarouselApi = api\n\n      sync()\n      requestAnimationFrame(() => emit(carouselRoot, \"carousel:init\", api))\n\n      store.set(carouselRoot, {\n        cleanup: () => {\n          api.off(\"reInit\", sync)\n          api.off(\"select\", sync)\n\n          if (prevButton instanceof HTMLElement) {\n            prevButton.removeEventListener(\"click\", onPrevClick)\n          }\n\n          if (nextButton instanceof HTMLElement) {\n            nextButton.removeEventListener(\"click\", onNextClick)\n          }\n\n          carouselRoot.removeEventListener(\"keydown\", onKeyDown, true)\n          delete carouselRoot.__fulldevCarouselApi\n          api.destroy()\n        },\n      })\n    }\n\n    const initCarousels = () => {\n      cleanupDisconnectedCarousels()\n\n      document\n        .querySelectorAll('[data-slot=\"carousel\"]')\n        .forEach((root) => initCarousel(root))\n    }\n\n    initCarousels()\n    document.addEventListener(\"astro:page-load\", initCarousels)\n  }\n</script>\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/carousel/carousel-content.astro",
      "content": "---\nimport type { HTMLAttributes } from \"astro/types\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype Props = HTMLAttributes<\"div\">\n\nconst { class: className, ...props } = Astro.props\n---\n\n<div class=\"overflow-hidden\" data-slot=\"carousel-content\">\n  <div\n    data-slot=\"carousel-container\"\n    class={cn(\n      \"flex [touch-action:pan-y_pinch-zoom]\",\n      \"group-data-[orientation=vertical]/carousel:-mt-4 group-data-[orientation=vertical]/carousel:[touch-action:pan-x_pinch-zoom] group-data-[orientation=vertical]/carousel:flex-col\",\n      \"group-data-[orientation=horizontal]/carousel:-ml-4\",\n      className\n    )}\n    {...props}\n  >\n    <slot />\n  </div>\n</div>\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/carousel/carousel-item.astro",
      "content": "---\nimport type { HTMLAttributes } from \"astro/types\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype Props = HTMLAttributes<\"div\">\n\nconst { class: className, ...props } = Astro.props\n---\n\n<div\n  data-slot=\"carousel-item\"\n  role=\"group\"\n  aria-roledescription=\"slide\"\n  class={cn(\n    \"min-w-0 shrink-0 grow-0 basis-full\",\n    \"group-data-[orientation=horizontal]/carousel:pl-4\",\n    \"group-data-[orientation=vertical]/carousel:pt-4\",\n    className\n  )}\n  {...props}\n>\n  <slot />\n</div>\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/carousel/carousel-next.astro",
      "content": "---\nimport type { HTMLAttributes } from \"astro/types\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants, type ButtonVariantProps } from \"@/components/ui/button\"\nimport { Icon } from \"@/components/ui/icon\"\n\ntype Props = Omit<HTMLAttributes<\"button\">, \"type\"> & ButtonVariantProps\n\nconst {\n  class: className,\n  variant = \"outline\",\n  size = \"icon-sm\",\n  disabled,\n  ...props\n} = Astro.props\n---\n\n<button\n  type=\"button\"\n  data-slot=\"carousel-next\"\n  data-manual-disabled={disabled ? \"true\" : undefined}\n  disabled={disabled}\n  class={cn(\n    buttonVariants({ variant, size }),\n    \"absolute top-1/2 -right-12 z-10 -translate-y-1/2 touch-manipulation rounded-full\",\n    \"group-data-[orientation=vertical]/carousel:right-auto group-data-[orientation=vertical]/carousel:-bottom-12 group-data-[orientation=vertical]/carousel:left-1/2 group-data-[orientation=vertical]/carousel:-translate-x-1/2 group-data-[orientation=vertical]/carousel:translate-y-0 group-data-[orientation=vertical]/carousel:rotate-90\",\n    className\n  )}\n  {...props}\n>\n  <slot>\n    <Icon name=\"chevron-right\" class=\"size-4\" aria-hidden=\"true\" />\n    <span class=\"sr-only\">Next slide</span>\n  </slot>\n</button>\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/carousel/carousel-previous.astro",
      "content": "---\nimport type { HTMLAttributes } from \"astro/types\"\n\nimport { cn } from \"@/lib/utils\"\nimport { buttonVariants, type ButtonVariantProps } from \"@/components/ui/button\"\nimport { Icon } from \"@/components/ui/icon\"\n\ntype Props = Omit<HTMLAttributes<\"button\">, \"type\"> & ButtonVariantProps\n\nconst {\n  class: className,\n  variant = \"outline\",\n  size = \"icon-sm\",\n  disabled,\n  ...props\n} = Astro.props\n---\n\n<button\n  type=\"button\"\n  data-slot=\"carousel-previous\"\n  data-manual-disabled={disabled ? \"true\" : undefined}\n  disabled={disabled}\n  class={cn(\n    buttonVariants({ variant, size }),\n    \"absolute top-1/2 -left-12 z-10 -translate-y-1/2 touch-manipulation rounded-full\",\n    \"group-data-[orientation=vertical]/carousel:-top-12 group-data-[orientation=vertical]/carousel:left-1/2 group-data-[orientation=vertical]/carousel:-translate-x-1/2 group-data-[orientation=vertical]/carousel:translate-y-0 group-data-[orientation=vertical]/carousel:rotate-90\",\n    className\n  )}\n  {...props}\n>\n  <slot>\n    <Icon name=\"chevron-left\" class=\"size-4\" aria-hidden=\"true\" />\n    <span class=\"sr-only\">Previous slide</span>\n  </slot>\n</button>\n",
      "type": "registry:ui"
    },
    {
      "path": "src/components/ui/carousel/index.ts",
      "content": "export type {\n  EmblaCarouselType as CarouselApi,\n  EmblaOptionsType as CarouselOptions,\n  EmblaPluginType as CarouselPlugin,\n} from \"embla-carousel\"\n\nexport { default as Carousel } from \"./carousel.astro\"\nexport { default as CarouselContent } from \"./carousel-content.astro\"\nexport { default as CarouselItem } from \"./carousel-item.astro\"\nexport { default as CarouselPrevious } from \"./carousel-previous.astro\"\nexport { default as CarouselNext } from \"./carousel-next.astro\"\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}