{
  "name": "carousel",
  "type": "registry:component",
  "files": [
    {
      "name": "carousel.component.ts",
      "content": "import {\n  ChangeDetectionStrategy,\n  Component,\n  input,\n  signal,\n  ViewEncapsulation,\n  output,\n  computed,\n  viewChild,\n  type InputSignal,\n  type Signal,\n} from '@angular/core';\n\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { lucideChevronLeft, lucideChevronRight, lucideCircleSmall } from '@ng-icons/lucide';\nimport type { ClassValue } from 'clsx';\nimport type { EmblaCarouselType, EmblaEventType, EmblaOptionsType, EmblaPluginType } from 'embla-carousel';\nimport { EmblaCarouselDirective } from 'embla-carousel-angular';\n\nimport { ZardButtonComponent } from '@/shared/components/button';\nimport {\n  carouselNextButtonVariants,\n  carouselPreviousButtonVariants,\n  carouselVariants,\n  type ZardCarouselControlsVariants,\n  type ZardCarouselOrientationVariants,\n} from '@/shared/components/carousel/carousel.variants';\nimport { mergeClasses } from '@/shared/utils/merge-classes';\n\n@Component({\n  selector: 'z-carousel',\n  imports: [EmblaCarouselDirective, ZardButtonComponent, NgIcon],\n  template: `\n    <div class=\"relative\">\n      <div\n        emblaCarousel\n        #emblaRef=\"emblaCarousel\"\n        [class]=\"classes()\"\n        [options]=\"options()\"\n        [plugins]=\"zPlugins()\"\n        [subscribeToEvents]=\"subscribeToEvents\"\n        (emblaChange)=\"onEmblaChange($event, emblaRef.emblaApi!)\"\n        aria-roledescription=\"carousel\"\n        role=\"region\"\n        tabindex=\"0\"\n      >\n        <ng-content />\n\n        @let controls = zControls();\n        @if (controls === 'button') {\n          <button\n            type=\"button\"\n            z-button\n            zType=\"outline\"\n            [class]=\"prevBtnClasses()\"\n            [zDisabled]=\"!canScrollPrev()\"\n            (click)=\"slidePrevious()\"\n            aria-label=\"Previous slide\"\n          >\n            <ng-icon name=\"lucideChevronLeft\" class=\"size-4!\" />\n          </button>\n          <button\n            type=\"button\"\n            z-button\n            zType=\"outline\"\n            [class]=\"nextBtnClasses()\"\n            [zDisabled]=\"!canScrollNext()\"\n            (click)=\"slideNext()\"\n            aria-label=\"Next slide\"\n          >\n            <ng-icon name=\"lucideChevronRight\" class=\"size-4!\" />\n          </button>\n        } @else if (controls === 'dot') {\n          <div class=\"mt-2 flex justify-center gap-1\">\n            @for (dot of dots(); track $index) {\n              <button\n                type=\"button\"\n                [class]=\"\n                  'block size-4 border-0 bg-transparent p-0 ' +\n                  ($index === selectedIndex() ? 'cursor-default' : 'cursor-pointer')\n                \"\n                (click)=\"goTo($index)\"\n                [attr.aria-current]=\"$index === selectedIndex() ? 'true' : null\"\n                [aria-label]=\"'Go to slide ' + ($index + 1)\"\n              >\n                <ng-icon\n                  name=\"lucideCircleSmall\"\n                  [strokeWidth]=\"0\"\n                  [class]=\"$index === selectedIndex() ? '[&_svg]:fill-primary' : '[&_svg]:fill-border'\"\n                />\n              </button>\n            }\n          </div>\n        }\n      </div>\n    </div>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  encapsulation: ViewEncapsulation.None,\n  viewProviders: [\n    provideIcons({\n      lucideChevronLeft,\n      lucideChevronRight,\n      lucideCircleSmall,\n    }),\n  ],\n  host: {\n    '(keydown.arrowleft.prevent)': 'slidePrevious()',\n    '(keydown.arrowright.prevent)': 'slideNext()',\n  },\n})\nexport class ZardCarouselComponent {\n  protected readonly emblaRef = viewChild(EmblaCarouselDirective);\n\n  readonly class = input<ClassValue>('');\n  readonly zOptions: InputSignal<EmblaOptionsType> = input<EmblaOptionsType>({ loop: false });\n  readonly zPlugins: InputSignal<EmblaPluginType[]> = input<EmblaPluginType[]>([]);\n  readonly zOrientation = input<ZardCarouselOrientationVariants>('horizontal');\n  readonly zControls = input<ZardCarouselControlsVariants>('button');\n  readonly zInited = output<EmblaCarouselType>();\n  readonly zSelected = output<void>();\n\n  protected readonly selectedIndex = signal<number>(0);\n  protected readonly canScrollPrev = signal<boolean>(false);\n  protected readonly canScrollNext = signal<boolean>(false);\n  protected readonly scrollSnaps = signal<number[]>([]);\n  protected readonly subscribeToEvents: EmblaEventType[] = ['init', 'select', 'reInit'];\n  protected readonly options: Signal<EmblaOptionsType> = computed(() => ({\n    ...this.zOptions(),\n    axis: this.zOrientation() === 'horizontal' ? 'x' : 'y',\n  }));\n\n  protected readonly dots = computed(() => new Array<string>(this.scrollSnaps().length).fill('.'));\n\n  #index = -1;\n\n  onEmblaChange(type: EmblaEventType, emblaApi: EmblaCarouselType): void {\n    if (type === 'init' || type === 'reInit') {\n      this.scrollSnaps.set(emblaApi.scrollSnapList());\n      this.checkNavigation(emblaApi);\n      if (type === 'init') {\n        this.zInited.emit(emblaApi);\n      }\n      return;\n    }\n\n    if (type === 'select' && emblaApi.selectedScrollSnap() !== this.#index) {\n      this.checkNavigation(emblaApi);\n      this.zSelected.emit();\n    }\n  }\n\n  slidePrevious(): void {\n    const emblaRef = this.emblaRef();\n    if (emblaRef) {\n      emblaRef.scrollPrev();\n    }\n  }\n\n  slideNext(): void {\n    const emblaRef = this.emblaRef();\n    if (emblaRef) {\n      emblaRef.scrollNext();\n    }\n  }\n\n  goTo(index: number): void {\n    const emblaRef = this.emblaRef();\n    if (emblaRef) {\n      emblaRef.scrollTo(index);\n    }\n  }\n\n  private checkNavigation(emblaApi: EmblaCarouselType): void {\n    this.#index = emblaApi.selectedScrollSnap();\n    this.selectedIndex.set(emblaApi.selectedScrollSnap());\n    this.canScrollPrev.set(emblaApi.canScrollPrev());\n    this.canScrollNext.set(emblaApi.canScrollNext());\n  }\n\n  protected readonly classes = computed(() =>\n    mergeClasses(carouselVariants({ zOrientation: this.zOrientation() }), this.class()),\n  );\n\n  protected readonly prevBtnClasses = computed(() =>\n    mergeClasses(carouselPreviousButtonVariants({ zOrientation: this.zOrientation() })),\n  );\n\n  protected readonly nextBtnClasses = computed(() =>\n    mergeClasses(carouselNextButtonVariants({ zOrientation: this.zOrientation() })),\n  );\n}\n"
    },
    {
      "name": "carousel.variants.ts",
      "content": "import { cva, type VariantProps } from 'class-variance-authority';\n\nexport const carouselVariants = cva('overflow-hidden', {\n  variants: {\n    zOrientation: {\n      horizontal: '',\n      vertical: 'h-full',\n    },\n    zControls: {\n      none: '',\n      button: '',\n      dot: '',\n    },\n  },\n  defaultVariants: {\n    zOrientation: 'horizontal',\n  },\n});\n\nexport const carouselContentVariants = cva('flex', {\n  variants: {\n    zOrientation: {\n      horizontal: '-ml-4',\n      vertical: '-mt-4 flex-col',\n    },\n  },\n  defaultVariants: {\n    zOrientation: 'horizontal',\n  },\n});\n\nexport const carouselItemVariants = cva('min-w-0 shrink-0 grow-0 basis-full', {\n  variants: {\n    zOrientation: {\n      horizontal: 'pl-4',\n      vertical: 'pt-4',\n    },\n  },\n  defaultVariants: {\n    zOrientation: 'horizontal',\n  },\n});\n\nexport const carouselPreviousButtonVariants = cva('absolute size-8 touch-manipulation rounded-full px-0', {\n  variants: {\n    zOrientation: {\n      horizontal: 'top-1/2 -left-12 -translate-y-1/2',\n      vertical: '-top-12 left-1/2 -translate-x-1/2 rotate-90',\n    },\n  },\n  defaultVariants: {\n    zOrientation: 'horizontal',\n  },\n});\n\nexport const carouselNextButtonVariants = cva('absolute size-8 touch-manipulation rounded-full px-0', {\n  variants: {\n    zOrientation: {\n      horizontal: 'top-1/2 -right-12 -translate-y-1/2',\n      vertical: '-bottom-12 left-1/2 -translate-x-1/2 rotate-90',\n    },\n  },\n  defaultVariants: {\n    zOrientation: 'horizontal',\n  },\n});\n\nexport type ZardCarouselOrientationVariants = NonNullable<VariantProps<typeof carouselVariants>['zOrientation']>;\nexport type ZardCarouselControlsVariants = NonNullable<VariantProps<typeof carouselVariants>['zControls']>;\n"
    },
    {
      "name": "carousel-content.component.ts",
      "content": "import { ChangeDetectionStrategy, Component, ViewEncapsulation, computed, inject, input } from '@angular/core';\n\nimport { type ClassValue } from 'clsx';\n\nimport { ZardCarouselComponent } from '@/shared/components/carousel/carousel.component';\nimport { carouselContentVariants } from '@/shared/components/carousel/carousel.variants';\nimport { mergeClasses } from '@/shared/utils/merge-classes';\n\n@Component({\n  selector: 'z-carousel-content',\n  imports: [],\n  template: `\n    <ng-content />\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  encapsulation: ViewEncapsulation.None,\n  host: {\n    '[class]': 'classes()',\n  },\n})\nexport class ZardCarouselContentComponent {\n  readonly #parent = inject(ZardCarouselComponent);\n  readonly #orientation = computed<'horizontal' | 'vertical'>(() => this.#parent.zOrientation());\n  readonly class = input<ClassValue>('');\n  protected readonly classes = computed(() =>\n    mergeClasses(carouselContentVariants({ zOrientation: this.#orientation() }), this.class()),\n  );\n}\n"
    },
    {
      "name": "carousel-item.component.ts",
      "content": "import { ChangeDetectionStrategy, Component, ViewEncapsulation, computed, inject, input } from '@angular/core';\n\nimport { type ClassValue } from 'clsx';\n\nimport { ZardCarouselComponent } from '@/shared/components/carousel/carousel.component';\nimport { carouselItemVariants } from '@/shared/components/carousel/carousel.variants';\nimport { mergeClasses } from '@/shared/utils/merge-classes';\n\n@Component({\n  selector: 'z-carousel-item',\n  template: `\n    <ng-content />\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  encapsulation: ViewEncapsulation.None,\n  host: {\n    '[class]': 'classes()',\n    role: 'group',\n    'aria-roledescription': 'slide',\n  },\n})\nexport class ZardCarouselItemComponent {\n  readonly #parent = inject(ZardCarouselComponent);\n\n  readonly #orientation = computed<'horizontal' | 'vertical'>(() => this.#parent.zOrientation());\n  readonly class = input<ClassValue>('');\n  protected readonly classes = computed(() =>\n    mergeClasses(carouselItemVariants({ zOrientation: this.#orientation() }), this.class()),\n  );\n}\n"
    },
    {
      "name": "carousel-plugins.service.ts",
      "content": "import { Injectable } from '@angular/core';\n\nimport type { EmblaPluginType } from 'embla-carousel';\n\n/**\n * Service to create and manage Embla Carousel plugins\n */\n@Injectable({\n  providedIn: 'root',\n})\nexport class ZardCarouselPluginsService {\n  /**\n   * Creates an autoplay plugin for the carousel\n   */\n  async createAutoplayPlugin(\n    options: {\n      delay?: number; // ms\n      jump?: boolean;\n      stopOnInteraction?: boolean;\n      stopOnMouseEnter?: boolean;\n      playOnInit?: boolean;\n      rootNode?: (emblaRoot: HTMLElement) => HTMLElement | null;\n    } = {},\n  ) {\n    try {\n      const AutoplayModule = await import('embla-carousel-autoplay');\n      const Autoplay = AutoplayModule.default;\n      return Autoplay(options);\n    } catch (err) {\n      console.error('Error loading Autoplay plugin:', err);\n      throw new Error('Make sure embla-carousel-autoplay is installed.');\n    }\n  }\n\n  /**\n   * Helper method to create autoplay plugin with HTMLElement\n   * Converts HTMLElement to the function format expected by Embla\n   */\n  async createAutoplayPluginWithElement(\n    options: {\n      delay?: number;\n      jump?: boolean;\n      stopOnInteraction?: boolean;\n      stopOnMouseEnter?: boolean;\n      playOnInit?: boolean;\n      rootElement?: HTMLElement;\n    } = {},\n  ) {\n    const { rootElement, ...restOptions } = options;\n    const autoplayOptions = {\n      ...restOptions,\n      ...(rootElement && {\n        rootNode: () => rootElement,\n      }),\n    };\n\n    return this.createAutoplayPlugin(autoplayOptions);\n  }\n\n  /**\n   * Creates a class names plugin for the carousel\n   */\n  async createClassNamesPlugin(\n    options: {\n      selected?: string;\n      dragging?: string;\n      draggable?: string;\n    } = {},\n  ): Promise<EmblaPluginType> {\n    try {\n      const ClassNamesModule = await import('embla-carousel-class-names');\n      const ClassNames = ClassNamesModule.default;\n      return ClassNames(options);\n    } catch (err) {\n      console.error('Error loading ClassNames plugin:', err);\n      throw new Error('Make sure embla-carousel-class-names is installed.');\n    }\n  }\n\n  /**\n   * Creates a wheel gestures plugin for the carousel\n   */\n  async createWheelGesturesPlugin(\n    options: {\n      wheelDraggingClass?: string;\n      forceWheelAxis?: 'x' | 'y';\n      target?: Element;\n    } = {},\n  ) {\n    try {\n      const { WheelGesturesPlugin } = await import('embla-carousel-wheel-gestures');\n      return WheelGesturesPlugin(options);\n    } catch (err) {\n      console.error('Error loading WheelGestures plugin:', err);\n      throw new Error('Make sure embla-carousel-wheel-gestures is installed.');\n    }\n  }\n}\n"
    },
    {
      "name": "carousel.imports.ts",
      "content": "import { ZardCarouselContentComponent } from '@/shared/components/carousel/carousel-content.component';\nimport { ZardCarouselItemComponent } from '@/shared/components/carousel/carousel-item.component';\nimport { ZardCarouselComponent } from '@/shared/components/carousel/carousel.component';\n\nexport const ZardCarouselImports = [\n  ZardCarouselComponent,\n  ZardCarouselContentComponent,\n  ZardCarouselItemComponent,\n] as const;\n"
    },
    {
      "name": "index.ts",
      "content": "export * from '@/shared/components/carousel/carousel.component';\nexport * from '@/shared/components/carousel/carousel-content.component';\nexport * from '@/shared/components/carousel/carousel-item.component';\nexport * from '@/shared/components/carousel/carousel-plugins.service';\nexport * from '@/shared/components/carousel/carousel.variants';\nexport * from '@/shared/components/carousel/carousel.imports';\n"
    }
  ],
  "dependencies": [
    "embla-carousel-angular",
    "embla-carousel-autoplay",
    "embla-carousel-class-names",
    "embla-carousel-wheel-gestures"
  ],
  "registryDependencies": [
    "button"
  ],
  "demos": [
    {
      "name": "orientation.ts",
      "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\n\nimport { ZardCardImports } from '@/shared/components/card/card.imports';\nimport { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';\n\n@Component({\n  imports: [ZardCarouselImports, ZardCardImports],\n  template: `\n    <div class=\"w-full min-w-xs\">\n      <z-carousel [zOptions]=\"{ align: 'start' }\" zOrientation=\"vertical\">\n        <z-carousel-content class=\"-mt-1 h-[270px]\">\n          @for (slide of slides; track slide) {\n            <z-carousel-item class=\"basis-1/2 pt-1\">\n              <div class=\"p-1\">\n                <z-card>\n                  <z-card-content class=\"flex items-center justify-center p-6\">\n                    <span class=\"text-3xl font-semibold\">{{ slide }}</span>\n                  </z-card-content>\n                </z-card>\n              </div>\n            </z-carousel-item>\n          }\n        </z-carousel-content>\n      </z-carousel>\n    </div>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoCarouselOrientationComponent {\n  protected slides = ['1', '2', '3', '4', '5'];\n}\n"
    },
    {
      "name": "preview.ts",
      "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\n\nimport { ZardCardImports } from '@/shared/components/card/card.imports';\nimport { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';\n\n@Component({\n  imports: [ZardCarouselImports, ZardCardImports],\n  template: `\n    <div class=\"w-full max-w-[12rem] sm:max-w-xs\">\n      <z-carousel>\n        <z-carousel-content>\n          @for (slide of slides; track slide) {\n            <z-carousel-item>\n              <div class=\"p-1\">\n                <z-card>\n                  <z-card-content class=\"flex aspect-square items-center justify-center p-6\">\n                    <span class=\"text-4xl font-semibold\">{{ slide }}</span>\n                  </z-card-content>\n                </z-card>\n              </div>\n            </z-carousel-item>\n          }\n        </z-carousel-content>\n      </z-carousel>\n    </div>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoCarouselPreviewComponent {\n  protected slides = ['1', '2', '3', '4', '5'];\n}\n"
    },
    {
      "name": "sizes.ts",
      "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\n\nimport { ZardCardImports } from '@/shared/components/card/card.imports';\nimport { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';\n\n@Component({\n  imports: [ZardCarouselImports, ZardCardImports],\n  template: `\n    <div class=\"w-full max-w-[12rem] sm:max-w-xs md:max-w-sm\">\n      <z-carousel [zOptions]=\"{ align: 'start' }\">\n        <z-carousel-content>\n          @for (slide of slides; track slide) {\n            <z-carousel-item class=\"basis-1/2 lg:basis-1/3\">\n              <div class=\"p-1\">\n                <z-card>\n                  <z-card-content class=\"flex aspect-square items-center justify-center p-6\">\n                    <span class=\"text-3xl font-semibold\">{{ slide }}</span>\n                  </z-card-content>\n                </z-card>\n              </div>\n            </z-carousel-item>\n          }\n        </z-carousel-content>\n      </z-carousel>\n    </div>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoCarouselSizeComponent {\n  protected slides = ['1', '2', '3', '4', '5'];\n}\n"
    },
    {
      "name": "spacing.ts",
      "content": "import { ChangeDetectionStrategy, Component } from '@angular/core';\n\nimport { ZardCardImports } from '@/shared/components/card/card.imports';\nimport { ZardCarouselImports } from '@/shared/components/carousel/carousel.imports';\n\n@Component({\n  imports: [ZardCarouselImports, ZardCardImports],\n  template: `\n    <div class=\"w-full max-w-[12rem] sm:max-w-xs md:max-w-sm\">\n      <z-carousel>\n        <z-carousel-content class=\"-ml-1\">\n          @for (slide of slides; track slide) {\n            <z-carousel-item class=\"basis-1/2 pl-1 lg:basis-1/3\">\n              <div class=\"p-1\">\n                <z-card>\n                  <z-card-content class=\"flex aspect-square items-center justify-center p-6\">\n                    <span class=\"text-2xl font-semibold\">{{ slide }}</span>\n                  </z-card-content>\n                </z-card>\n              </div>\n            </z-carousel-item>\n          }\n        </z-carousel-content>\n      </z-carousel>\n    </div>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoCarouselSpacingComponent {\n  protected slides = ['1', '2', '3', '4', '5'];\n}\n"
    }
  ]
}
