{
  "name": "select",
  "type": "registry:component",
  "files": [
    {
      "name": "select.component.ts",
      "content": "import {\n  type ConnectedPosition,\n  Overlay,\n  OverlayModule,\n  OverlayPositionBuilder,\n  type OverlayRef,\n} from '@angular/cdk/overlay';\nimport { TemplatePortal } from '@angular/cdk/portal';\nimport { isPlatformBrowser } from '@angular/common';\nimport {\n  afterNextRender,\n  booleanAttribute,\n  ChangeDetectionStrategy,\n  Component,\n  computed,\n  contentChildren,\n  DestroyRef,\n  effect,\n  ElementRef,\n  forwardRef,\n  inject,\n  Injector,\n  input,\n  linkedSignal,\n  model,\n  numberAttribute,\n  type OnDestroy,\n  output,\n  PLATFORM_ID,\n  runInInjectionContext,\n  signal,\n  type TemplateRef,\n  viewChild,\n  ViewContainerRef,\n  ViewEncapsulation,\n} from '@angular/core';\nimport { takeUntilDestroyed } from '@angular/core/rxjs-interop';\nimport { type ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';\n\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { lucideChevronDown, lucideChevronUp } from '@ng-icons/lucide';\nimport type { ClassValue } from 'clsx';\nimport { filter } from 'rxjs';\n\nimport { ZardBadgeComponent } from '@/shared/components/badge';\nimport { ZardSelectGroupComponent } from '@/shared/components/select/select-group.component';\nimport { ZardSelectItemComponent } from '@/shared/components/select/select-item.component';\nimport {\n  selectContentVariants,\n  selectScrollButtonVariants,\n  selectTriggerVariants,\n  selectVariants,\n  selectViewportVariants,\n  type ZardSelectAlignVariants,\n  type ZardSelectPositionVariants,\n} from '@/shared/components/select/select.variants';\nimport { mergeClasses } from '@/shared/utils/merge-classes';\n\ntype OnTouchedType = () => void;\ntype OnChangeType = (value: string | string[]) => void;\n\nconst COMPACT_MODE_WIDTH_THRESHOLD = 100;\nlet nextSelectId = 0;\n\n@Component({\n  selector: 'z-select, [z-select]',\n  imports: [OverlayModule, ZardBadgeComponent, NgIcon],\n  template: `\n    <button\n      type=\"button\"\n      role=\"combobox\"\n      data-slot=\"select-trigger\"\n      [class]=\"triggerClasses()\"\n      [disabled]=\"disabledState()\"\n      [attr.aria-controls]=\"isOpen() ? listboxId : null\"\n      [attr.aria-expanded]=\"isOpen()\"\n      [attr.aria-haspopup]=\"'listbox'\"\n      [attr.aria-invalid]=\"zInvalid() ? 'true' : null\"\n      [attr.aria-label]=\"triggerAriaLabel()\"\n      [attr.aria-disabled]=\"disabledState()\"\n      [attr.data-placeholder]=\"!hasValue() ? '' : null\"\n      (blur)=\"!isOpen() && isFocus.set(false)\"\n      (click)=\"toggle()\"\n      (focus)=\"onFocus()\"\n    >\n      <span data-slot=\"select-value\" [class]=\"valueClasses()\">\n        @for (label of selectedLabels(); track $index) {\n          @if (zMultiple()) {\n            <z-badge zType=\"secondary\" class=\"max-w-full shrink\">\n              <span class=\"truncate\">{{ label }}</span>\n            </z-badge>\n          } @else {\n            <span class=\"truncate\">{{ label }}</span>\n          }\n        } @empty {\n          <span class=\"text-muted-foreground truncate\">{{ zPlaceholder() }}</span>\n        }\n      </span>\n      <ng-icon name=\"lucideChevronDown\" class=\"text-muted-foreground size-4!\" />\n    </button>\n\n    <ng-template #dropdownTemplate>\n      <div\n        data-slot=\"select-content\"\n        [id]=\"listboxId\"\n        [class]=\"contentClasses()\"\n        role=\"listbox\"\n        [attr.data-state]=\"'open'\"\n        [attr.data-side]=\"overlaySide()\"\n        [attr.data-position]=\"zPosition()\"\n        [attr.data-align-trigger]=\"zPosition() === 'item-aligned' ? 'true' : null\"\n        [attr.aria-multiselectable]=\"zMultiple() ? 'true' : null\"\n        [style.--z-select-trigger-height]=\"triggerHeightStyle()\"\n        [style.--z-select-trigger-width]=\"triggerWidthStyle()\"\n        (keydown.{arrowdown,arrowup,enter,space,escape,home,end,pageup,pagedown}.prevent)=\"onDropdownKeydown($event)\"\n        (wheel)=\"stopScrollOptions()\"\n        (touchmove)=\"stopScrollOptions()\"\n        tabindex=\"-1\"\n      >\n        @if (showScrollUpButton()) {\n          <div\n            data-slot=\"select-scroll-up-button\"\n            [class]=\"scrollButtonClasses()\"\n            aria-hidden=\"true\"\n            style=\"flex-shrink: 0\"\n            (pointerdown)=\"startScrollOptions(-1)\"\n            (pointermove)=\"moveOverScrollButton(-1)\"\n            (pointerleave)=\"stopScrollOptions(-1)\"\n            (pointerup)=\"stopScrollOptions(-1)\"\n            (pointercancel)=\"stopScrollOptions(-1)\"\n          >\n            <ng-icon name=\"lucideChevronUp\" class=\"size-4!\" />\n          </div>\n        }\n\n        <div\n          #optionsViewport\n          [class]=\"viewportClasses()\"\n          data-slot=\"select-viewport\"\n          role=\"presentation\"\n          [attr.data-position]=\"zPosition()\"\n          (scroll)=\"updateScrollableState()\"\n        >\n          <ng-content />\n        </div>\n\n        @if (showScrollDownButton()) {\n          <div\n            data-slot=\"select-scroll-down-button\"\n            [class]=\"scrollButtonClasses()\"\n            aria-hidden=\"true\"\n            style=\"flex-shrink: 0\"\n            (pointerdown)=\"startScrollOptions(1)\"\n            (pointermove)=\"moveOverScrollButton(1)\"\n            (pointerleave)=\"stopScrollOptions(1)\"\n            (pointerup)=\"stopScrollOptions(1)\"\n            (pointercancel)=\"stopScrollOptions(1)\"\n          >\n            <ng-icon name=\"lucideChevronDown\" class=\"size-4!\" />\n          </div>\n        }\n      </div>\n    </ng-template>\n  `,\n  providers: [\n    {\n      provide: NG_VALUE_ACCESSOR,\n      useExisting: forwardRef(() => ZardSelectComponent),\n      multi: true,\n    },\n  ],\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  encapsulation: ViewEncapsulation.None,\n  viewProviders: [provideIcons({ lucideChevronDown, lucideChevronUp })],\n  host: {\n    'data-slot': 'select',\n    tabindex: '-1',\n    '[attr.data-active]': 'isFocus() ? \"\" : null',\n    '[attr.data-disabled]': 'disabledState() ? \"\" : null',\n    '[attr.data-invalid]': 'zInvalid() ? \"\" : null',\n    '[attr.data-state]': 'isOpen() ? \"open\" : \"closed\"',\n    '[class]': 'classes()',\n    '(focus)': 'onHostFocus($event)',\n    '(keydown.{enter,space,arrowdown,arrowup,escape}.prevent)': 'onTriggerKeydown($event)',\n  },\n  exportAs: 'zSelect',\n})\nexport class ZardSelectComponent implements ControlValueAccessor, OnDestroy {\n  private readonly destroyRef = inject(DestroyRef);\n  private readonly elementRef = inject(ElementRef<HTMLElement>);\n  private readonly injector = inject(Injector);\n  private readonly overlay = inject(Overlay);\n  private readonly overlayPositionBuilder = inject(OverlayPositionBuilder);\n  private readonly viewContainerRef = inject(ViewContainerRef);\n  private readonly platformId = inject(PLATFORM_ID);\n\n  readonly dropdownTemplate = viewChild.required<TemplateRef<void>>('dropdownTemplate');\n  readonly optionsViewport = viewChild<ElementRef<HTMLElement>>('optionsViewport');\n  readonly selectGroups = contentChildren(ZardSelectGroupComponent, { descendants: true });\n  readonly selectItems = contentChildren(ZardSelectItemComponent, { descendants: true });\n\n  private overlayRef?: OverlayRef;\n  private portal?: TemplatePortal;\n\n  readonly class = input<ClassValue>('');\n  readonly zAlign = input<ZardSelectAlignVariants>('center');\n  readonly zDisabled = input(false, { transform: booleanAttribute });\n  readonly zInvalid = input(false, { transform: booleanAttribute });\n  readonly zLabel = input<string>('');\n  readonly zMaxLabelCount = input(1, { transform: numberAttribute });\n  readonly zMultiple = input(false, { transform: booleanAttribute });\n  readonly zPlaceholder = input<string>('Select an option...');\n  readonly zPosition = input<ZardSelectPositionVariants>('item-aligned');\n  readonly zValue = model<string | string[]>(this.zMultiple() ? [] : '');\n\n  readonly zSelectionChange = output<string | string[]>();\n\n  readonly isOpen = signal(false);\n  readonly focusedIndex = signal<number>(-1);\n  protected readonly isFocus = signal(false);\n  protected readonly isCompact = signal(false);\n  protected readonly hasScrollableContent = signal(false);\n  protected readonly canScrollUp = signal(false);\n  protected readonly canScrollDown = signal(false);\n  protected readonly overlaySide = signal<'top' | 'bottom' | 'left' | 'right'>('bottom');\n  protected readonly triggerHeight = signal(0);\n  protected readonly triggerWidth = signal(0);\n  protected readonly disabledState = linkedSignal(() => this.zDisabled());\n  protected readonly listboxId = `z-select-listbox-${nextSelectId++}`;\n  private scrollTimer: number | null = null;\n  private scrollDirection: -1 | 1 | null = null;\n\n  constructor() {\n    effect(() => {\n      if (this.disabledState() && this.isOpen()) {\n        this.close(false);\n      }\n    });\n\n    effect(() => this.updateItems(this.selectItems()));\n  }\n\n  protected readonly hasValue = computed(() => {\n    const value = this.zValue();\n    return Array.isArray(value) ? value.length > 0 : value !== '';\n  });\n\n  protected readonly triggerHeightStyle = computed(() => `${this.triggerHeight()}px`);\n  protected readonly triggerWidthStyle = computed(() => `${this.triggerWidth()}px`);\n  protected readonly showScrollUpButton = computed(() => this.canScrollUp());\n  protected readonly showScrollDownButton = computed(() => this.canScrollDown());\n\n  protected onFocus(): void {\n    if (this.isCompact()) {\n      this.isFocus.set(!this.hasValue());\n    }\n  }\n\n  protected onHostFocus(event: FocusEvent): void {\n    if (event.target === this.elementRef.nativeElement) {\n      this.focusButton();\n      this.open();\n    }\n  }\n\n  // Compute the label based on selected value\n  readonly selectedLabels = computed<string[]>(() => {\n    const selectedValue = this.zValue();\n    if (this.zMultiple() && Array.isArray(selectedValue)) {\n      return this.provideLabelsForMultiselectMode(selectedValue);\n    }\n\n    return this.provideLabelForSingleSelectMode(selectedValue as string);\n  });\n\n  protected readonly triggerAriaLabel = computed(() => this.selectedLabels().join(', ') || this.zPlaceholder());\n\n  private onChange: OnChangeType = (_value: string | string[]) => {\n    // ControlValueAccessor onChange callback\n  };\n\n  private onTouched: OnTouchedType = () => {\n    // ControlValueAccessor onTouched callback\n  };\n\n  protected readonly classes = computed(() => mergeClasses(selectVariants(), this.class()));\n  protected readonly contentClasses = computed(() =>\n    mergeClasses(selectContentVariants({ zPosition: this.zPosition() })),\n  );\n\n  protected readonly viewportClasses = computed(() =>\n    mergeClasses(selectViewportVariants({ zPosition: this.zPosition() })),\n  );\n\n  protected readonly scrollButtonClasses = computed(() => mergeClasses(selectScrollButtonVariants()));\n  protected readonly valueClasses = computed(() =>\n    mergeClasses(\n      'flex min-w-0 flex-1 items-center gap-2',\n      this.zMultiple() ? 'flex-wrap overflow-visible' : 'overflow-hidden',\n    ),\n  );\n\n  protected readonly triggerClasses = computed(() =>\n    mergeClasses(selectTriggerVariants({}), this.zMultiple() && 'h-auto min-h-8 py-1'),\n  );\n\n  ngOnDestroy() {\n    this.stopScrollOptions();\n    this.destroyOverlay();\n  }\n\n  onTriggerKeydown(event: Event) {\n    if (this.disabledState()) {\n      return;\n    }\n\n    const { key } = event as KeyboardEvent;\n    switch (key) {\n      case 'Enter':\n      case ' ':\n      case 'ArrowDown':\n      case 'ArrowUp':\n        if (!this.isOpen()) {\n          this.open();\n        }\n        break;\n      case 'Escape':\n        if (this.isOpen()) {\n          this.close();\n        }\n        break;\n    }\n  }\n\n  onDropdownKeydown(e: Event) {\n    const { key } = e as KeyboardEvent;\n    const items = this.getSelectItems();\n\n    switch (key) {\n      case 'ArrowDown':\n        this.navigateItems(1, items);\n        break;\n      case 'ArrowUp':\n        this.navigateItems(-1, items);\n        break;\n      case 'Enter':\n      case ' ':\n        this.selectFocusedItem(items);\n        break;\n      case 'Escape':\n        this.close();\n        this.focusButton();\n        break;\n      case 'Home':\n        this.focusFirstItem(items);\n        break;\n      case 'End':\n        this.focusLastItem(items);\n        break;\n      case 'PageDown':\n        this.navigateItems(5, items);\n        break;\n      case 'PageUp':\n        this.navigateItems(-5, items);\n        break;\n    }\n  }\n\n  toggle() {\n    if (this.disabledState()) {\n      return;\n    }\n\n    if (this.isOpen()) {\n      this.close();\n    } else {\n      this.open();\n    }\n  }\n\n  selectItem(value: string, label: string) {\n    if (this.disabledState()) {\n      return;\n    }\n\n    if (value === undefined || value === null || value === '') {\n      console.warn('Attempted to select item with invalid value:', { value, label });\n      return;\n    }\n\n    this.zValue.update(selectedValues => {\n      if (Array.isArray(selectedValues)) {\n        return selectedValues.includes(value) ? selectedValues.filter(v => v !== value) : [...selectedValues, value];\n      }\n\n      return value;\n    });\n    const selectedValue = this.zValue();\n    this.onChange(selectedValue);\n    this.zSelectionChange.emit(selectedValue);\n\n    if (this.zMultiple()) {\n      // in multiple mode it can happen that button changes size because of selection badges,\n      // which requires overlay position to update\n      this.updateOverlayPosition();\n    } else {\n      this.close();\n\n      setTimeout(() => {\n        this.blurButton();\n      }, 0);\n    }\n  }\n\n  private updateItems(items: readonly ZardSelectItemComponent[]): void {\n    const hostWidth = this.elementRef.nativeElement.offsetWidth || 0;\n    const isCompact = hostWidth <= COMPACT_MODE_WIDTH_THRESHOLD;\n    this.isCompact.set(isCompact);\n    // Setup select host reference for each item\n    for (const [index, item] of items.entries()) {\n      item.setSelectHost({\n        selectedValue: () => (this.zMultiple() ? (this.zValue() as string[]) : [this.zValue() as string]),\n        selectItem: (value: string, label: string) => this.selectItem(value, label),\n        navigateTo: () => this.navigateTo(item, index),\n      });\n      item.zMode.set(isCompact ? 'compact' : 'normal');\n    }\n  }\n\n  private navigateTo(element: ZardSelectItemComponent, index: number): void {\n    this.focusedIndex.set(index);\n    this.updateItemFocus(this.getSelectItems(true), index);\n  }\n\n  private updateOverlayPosition(): void {\n    setTimeout(() => {\n      this.overlayRef?.updatePosition();\n      this.updateScrollableState();\n    }, 0);\n  }\n\n  protected scrollOptions(direction: -1 | 1): void {\n    const viewport = this.optionsViewport()?.nativeElement;\n    if (!viewport) {\n      return;\n    }\n\n    const maxScroll = Math.max(viewport.scrollHeight - viewport.clientHeight, 0);\n    const nextScrollTop = Math.min(Math.max(viewport.scrollTop + direction * this.getScrollStep(), 0), maxScroll);\n    viewport.scrollTop = nextScrollTop;\n    this.updateScrollableState();\n\n    if ((direction === -1 && !this.canScrollUp()) || (direction === 1 && !this.canScrollDown())) {\n      this.stopScrollOptions(direction);\n    }\n  }\n\n  protected startScrollOptions(direction: -1 | 1): void {\n    if (!isPlatformBrowser(this.platformId)) {\n      return;\n    }\n\n    if (this.scrollTimer !== null) {\n      if (this.scrollDirection === direction) {\n        return;\n      }\n\n      this.stopScrollOptions();\n    }\n\n    this.scrollDirection = direction;\n    this.scrollTimer = window.setInterval(() => this.scrollOptions(direction), 50);\n  }\n\n  protected moveOverScrollButton(direction: -1 | 1): void {\n    this.clearItemFocus();\n    this.startScrollOptions(direction);\n  }\n\n  protected stopScrollOptions(direction?: -1 | 1): void {\n    if (direction !== undefined && this.scrollDirection !== direction) {\n      return;\n    }\n\n    if (this.scrollTimer === null || !isPlatformBrowser(this.platformId)) {\n      this.scrollDirection = null;\n      return;\n    }\n\n    window.clearInterval(this.scrollTimer);\n    this.scrollTimer = null;\n    this.scrollDirection = null;\n  }\n\n  protected updateScrollableState(): void {\n    const viewport = this.optionsViewport()?.nativeElement;\n    const maxScroll = viewport ? viewport.scrollHeight - viewport.clientHeight : 0;\n    const scrollTop = viewport?.scrollTop ?? 0;\n    const hasScrollableContent = !!viewport && maxScroll > 1;\n    const previousCanScrollUp = this.canScrollUp();\n    const previousCanScrollDown = this.canScrollDown();\n    const nextCanScrollUp = hasScrollableContent && scrollTop > 0;\n    const nextCanScrollDown = hasScrollableContent && Math.ceil(scrollTop) < maxScroll;\n\n    this.hasScrollableContent.set(hasScrollableContent);\n    this.canScrollUp.set(nextCanScrollUp);\n    this.canScrollDown.set(nextCanScrollDown);\n\n    if ((nextCanScrollUp && !previousCanScrollUp) || (nextCanScrollDown && !previousCanScrollDown)) {\n      this.scrollFocusedItemIntoView();\n    }\n  }\n\n  private getScrollStep(): number {\n    const items = this.getSelectItems();\n    const focusedItem = this.focusedIndex() >= 0 ? items[this.focusedIndex()] : undefined;\n    const selectedItem = items.find(item => item.getAttribute('value') === this.getPrimarySelectedValue());\n    const itemHeight = (selectedItem ?? focusedItem ?? items[0])?.offsetHeight ?? 0;\n\n    return itemHeight > 0 ? itemHeight : 32;\n  }\n\n  private provideLabelsForMultiselectMode(selectedValue: string[]): string[] {\n    const labelsToShowCount = selectedValue.length - this.zMaxLabelCount();\n    const labels = [];\n    let index = 0;\n    for (const value of selectedValue) {\n      const matchingItem = this.getMatchingItem(value);\n      if (matchingItem) {\n        labels.push(matchingItem.label());\n        index++;\n      }\n      if (labelsToShowCount && this.zMaxLabelCount() && index === this.zMaxLabelCount()) {\n        labels.push(`${labelsToShowCount} more item${labelsToShowCount > 1 ? 's' : ''} selected`);\n        break;\n      }\n    }\n    return labels;\n  }\n\n  private provideLabelForSingleSelectMode(selectedValue: string): string[] {\n    const manualLabel = this.zLabel();\n    if (manualLabel) {\n      return [manualLabel];\n    }\n\n    const matchingItem = this.getMatchingItem(selectedValue);\n    if (matchingItem) {\n      return [matchingItem.label()];\n    }\n\n    return selectedValue ? [selectedValue] : [];\n  }\n\n  private open() {\n    if (this.isOpen() || this.disabledState()) {\n      return;\n    }\n\n    // Create overlay if it doesn't exist\n    if (!this.overlayRef) {\n      this.createOverlay();\n    }\n\n    if (!this.overlayRef) {\n      return;\n    }\n\n    const hostWidth = this.elementRef.nativeElement.offsetWidth || 0;\n    const trigger = this.elementRef.nativeElement.querySelector('button');\n    const triggerHeight = trigger?.offsetHeight ?? 0;\n    this.triggerWidth.set(hostWidth);\n    this.triggerHeight.set(triggerHeight);\n\n    if (this.overlayRef.hasAttached()) {\n      this.overlayRef.detach();\n    }\n\n    this.overlayRef.updatePositionStrategy(this.createPositionStrategy());\n    this.portal = new TemplatePortal(this.dropdownTemplate(), this.viewContainerRef);\n\n    this.overlayRef.attach(this.portal);\n    this.overlayRef.updateSize(this.zPosition() === 'popper' ? { minWidth: hostWidth } : { width: hostWidth });\n    this.isOpen.set(true);\n    this.updateFocusWhenNormalMode();\n\n    this.determinePortalWidthOnOpen(hostWidth);\n  }\n\n  private setFocusOnOpen(): void {\n    this.focusDropdown();\n    this.focusSelectedItem();\n  }\n\n  private close(shouldTouch = true) {\n    this.stopScrollOptions();\n    if (this.overlayRef?.hasAttached()) {\n      this.overlayRef.detach();\n    }\n    this.isOpen.set(false);\n    this.hasScrollableContent.set(false);\n    this.canScrollUp.set(false);\n    this.canScrollDown.set(false);\n    this.focusedIndex.set(-1);\n    if (shouldTouch) {\n      this.onTouched();\n    }\n    this.updateFocusWhenNormalMode();\n  }\n\n  private updateFocusWhenNormalMode(): void {\n    if (this.hasValue()) {\n      this.isFocus.set(false);\n      return;\n    }\n\n    if (!this.isCompact()) {\n      this.isFocus.set(!this.isOpen());\n    }\n  }\n\n  private getMatchingItem(value: string): ZardSelectItemComponent | undefined {\n    return this.selectItems()?.find(item => item.zValue() === value);\n  }\n\n  private determinePortalWidthOnOpen(portalWidth: number): void {\n    runInInjectionContext(this.injector, () => {\n      afterNextRender(() => {\n        if (!this.overlayRef || !this.overlayRef.hasAttached()) {\n          return;\n        }\n\n        if (this.zPosition() === 'popper') {\n          this.updateScrollableState();\n          this.setFocusOnOpen();\n          return;\n        }\n\n        this.alignSelectedItemToTrigger();\n\n        const overlayPaneElement = this.overlayRef.overlayElement;\n        const textElements = Array.from(\n          overlayPaneElement.querySelectorAll<HTMLElement>(\n            'z-select-item > [data-slot=\"select-item-text\"], [z-select-item] > [data-slot=\"select-item-text\"]',\n          ),\n        );\n        let isOverflow = false;\n        for (const textElement of textElements) {\n          if (textElement.scrollWidth > textElement.clientWidth + 1) {\n            isOverflow = true;\n            break;\n          }\n        }\n\n        if (!isOverflow) {\n          this.updateScrollableState();\n          this.setFocusOnOpen();\n          return;\n        }\n\n        const selectItems = this.selectItems();\n        let itemMaxWidth = 0;\n        for (const item of selectItems) {\n          itemMaxWidth = Math.max(itemMaxWidth, item.elementRef.nativeElement.scrollWidth);\n        }\n\n        const [selectItem] = selectItems;\n        if (isOverflow && selectItem) {\n          const elementStyles = getComputedStyle(selectItem.elementRef.nativeElement);\n          const leftPadding = Number.parseFloat(elementStyles.getPropertyValue('padding-left')) || 0;\n          const rightPadding = Number.parseFloat(elementStyles.getPropertyValue('padding-right')) || 0;\n          itemMaxWidth += leftPadding + rightPadding;\n        }\n\n        itemMaxWidth = Math.max(itemMaxWidth, portalWidth);\n        this.overlayRef.updateSize({ width: itemMaxWidth });\n        this.alignSelectedItemToTrigger();\n\n        this.updateScrollableState();\n        this.setFocusOnOpen();\n      });\n    });\n  }\n\n  private alignSelectedItemToTrigger(): void {\n    if (this.zPosition() !== 'item-aligned' || !this.overlayRef?.hasAttached()) {\n      return;\n    }\n\n    const itemAlignedOffset = this.getItemAlignedOffset();\n    if (!itemAlignedOffset) {\n      return;\n    }\n\n    this.overlayRef.updatePositionStrategy(this.createPositionStrategy(itemAlignedOffset));\n  }\n\n  private getItemAlignedOffset(): { bottom: number; top: number } | null {\n    const content = this.overlayRef?.overlayElement.querySelector<HTMLElement>('[data-slot=\"select-content\"]');\n    const selectedItem =\n      this.getSelectItems(true).find(item => item.getAttribute('value') === this.getPrimarySelectedValue()) ??\n      this.getSelectItems()[0];\n    const trigger = (this.elementRef.nativeElement as HTMLElement).querySelector<HTMLElement>(\n      '[data-slot=\"select-trigger\"]',\n    );\n\n    if (!content || !selectedItem || !trigger) {\n      return null;\n    }\n\n    const triggerHeight = trigger.offsetHeight || trigger.getBoundingClientRect().height || this.triggerHeight();\n    const itemHeight = selectedItem.offsetHeight || selectedItem.getBoundingClientRect().height || triggerHeight;\n    const contentHeight = content.offsetHeight || content.getBoundingClientRect().height;\n    const selectedItemOffsetTop = selectedItem.offsetTop;\n    const itemCenterOffset = (triggerHeight - itemHeight) / 2;\n    const bottom = Math.round(-triggerHeight - selectedItemOffsetTop + itemCenterOffset);\n    const top = Math.round(contentHeight - selectedItemOffsetTop + itemCenterOffset);\n\n    return { bottom, top };\n  }\n\n  private createPositionStrategy(itemAlignedOffset?: { bottom: number; top: number }) {\n    return this.overlayPositionBuilder\n      .flexibleConnectedTo(this.elementRef)\n      .withPositions(this.connectedPositions(itemAlignedOffset))\n      .withPush(false);\n  }\n\n  private connectedPositions(itemAlignedOffset?: { bottom: number; top: number }): ConnectedPosition[] {\n    const originX = this.zAlign();\n    const overlayX = this.zAlign();\n    const bottomOffsetY = itemAlignedOffset?.bottom ?? 4;\n    const topOffsetY = itemAlignedOffset?.top ?? -4;\n\n    return [\n      {\n        originX,\n        originY: 'bottom',\n        overlayX,\n        overlayY: 'top',\n        offsetY: bottomOffsetY,\n      },\n      {\n        originX,\n        originY: 'top',\n        overlayX,\n        overlayY: 'bottom',\n        offsetY: topOffsetY,\n      },\n    ];\n  }\n\n  private createOverlay() {\n    if (this.overlayRef) {\n      return;\n    } // Already created\n\n    if (isPlatformBrowser(this.platformId)) {\n      try {\n        const positionStrategy = this.createPositionStrategy();\n\n        this.overlayRef = this.overlay.create({\n          positionStrategy,\n          hasBackdrop: false,\n          scrollStrategy: this.overlay.scrollStrategies.reposition(),\n          maxHeight: 384, // max-h-96 equivalent\n        });\n        this.overlayRef\n          .outsidePointerEvents()\n          .pipe(\n            filter(event => !this.elementRef.nativeElement.contains(event.target)),\n            takeUntilDestroyed(this.destroyRef),\n          )\n          .subscribe(() => {\n            this.isFocus.set(false);\n            this.close();\n          });\n      } catch (error) {\n        console.error('Error creating overlay:', error);\n      }\n    }\n  }\n\n  private destroyOverlay() {\n    if (this.overlayRef) {\n      this.overlayRef.dispose();\n      this.overlayRef = undefined;\n    }\n  }\n\n  private getSelectItems(ignoreFilter = false): HTMLElement[] {\n    if (!this.overlayRef?.hasAttached()) {\n      return [];\n    }\n    const dropdownElement = this.overlayRef.overlayElement;\n    return Array.from(dropdownElement.querySelectorAll<HTMLElement>('z-select-item, [z-select-item]')).filter(\n      item => ignoreFilter || item.dataset['disabled'] === undefined,\n    );\n  }\n\n  private navigateItems(direction: number, items: HTMLElement[]) {\n    if (items.length === 0) {\n      return;\n    }\n\n    const currentIndex = this.focusedIndex();\n    let nextIndex = currentIndex === -1 ? (direction > 0 ? 0 : items.length - 1) : currentIndex + direction;\n\n    nextIndex %= items.length;\n    if (nextIndex < 0) {\n      nextIndex += items.length;\n    }\n\n    this.focusedIndex.set(nextIndex);\n    this.updateItemFocus(items, nextIndex);\n  }\n\n  private selectFocusedItem(items: HTMLElement[]) {\n    const currentIndex = this.focusedIndex();\n    if (currentIndex >= 0 && currentIndex < items.length) {\n      const item = items[currentIndex];\n      const value = item.getAttribute('value');\n      const label = item.textContent?.trim() ?? '';\n\n      if (value === null || value === undefined) {\n        console.warn('No value attribute found on selected item:', item);\n        return;\n      }\n\n      this.selectItem(value, label);\n    }\n  }\n\n  private focusFirstItem(items: HTMLElement[]) {\n    if (items.length > 0) {\n      this.focusedIndex.set(0);\n      this.updateItemFocus(items, 0);\n    }\n  }\n\n  private focusLastItem(items: HTMLElement[]) {\n    if (items.length > 0) {\n      const lastIndex = items.length - 1;\n      this.focusedIndex.set(lastIndex);\n      this.updateItemFocus(items, lastIndex);\n    }\n  }\n\n  private updateItemFocus(items: HTMLElement[], focusedIndex: number) {\n    for (let index = 0; index < items.length; index++) {\n      const item = items[index];\n      if (index === focusedIndex) {\n        item.focus();\n        item.setAttribute('data-highlighted', '');\n      } else {\n        item.removeAttribute('data-highlighted');\n      }\n    }\n    this.updateScrollableState();\n  }\n\n  private clearItemFocus(): void {\n    this.focusedIndex.set(-1);\n    for (const item of this.getSelectItems(true)) {\n      item.removeAttribute('data-highlighted');\n    }\n    this.focusDropdown();\n  }\n\n  private scrollFocusedItemIntoView(): void {\n    const focusedItem = this.getSelectItems(true).find(item => item === document.activeElement);\n    focusedItem?.scrollIntoView?.({ block: 'nearest' });\n  }\n\n  private focusDropdown() {\n    if (this.overlayRef?.hasAttached()) {\n      const dropdownElement = this.overlayRef.overlayElement.querySelector(\n        '[data-slot=\"select-content\"]',\n      ) as HTMLElement;\n      if (dropdownElement) {\n        dropdownElement.focus();\n      }\n    }\n  }\n\n  private focusButton() {\n    const button = this.elementRef.nativeElement.querySelector('button');\n    if (button) {\n      button.focus();\n    }\n  }\n\n  private blurButton() {\n    const button = this.elementRef.nativeElement.querySelector('button');\n    if (button) {\n      button.blur();\n    }\n  }\n\n  private focusSelectedItem() {\n    const items = this.getSelectItems();\n    if (items.length === 0) {\n      return;\n    }\n\n    let selectedIndex = items.findIndex(item => item.getAttribute('value') === this.getPrimarySelectedValue());\n\n    // If no item is selected, focus the first item\n    if (selectedIndex === -1) {\n      selectedIndex = 0;\n    }\n\n    this.focusedIndex.set(selectedIndex);\n    this.updateItemFocus(items, selectedIndex);\n  }\n\n  private getPrimarySelectedValue(): string {\n    const selectedValue = this.zValue();\n    if (Array.isArray(selectedValue)) {\n      return selectedValue[0] ?? '';\n    }\n\n    return selectedValue;\n  }\n\n  // ControlValueAccessor implementation\n  writeValue(value: string | string[] | null): void {\n    if (this.zMultiple()) {\n      this.zValue.set(Array.isArray(value) ? value : value ? [value] : []);\n    } else {\n      this.zValue.set(value ?? '');\n    }\n  }\n\n  registerOnChange(fn: (value: string | string[]) => void): void {\n    this.onChange = fn;\n  }\n\n  registerOnTouched(fn: () => void): void {\n    this.onTouched = fn;\n  }\n\n  setDisabledState(isDisabled: boolean): void {\n    this.disabledState.set(isDisabled);\n    if (isDisabled && this.isOpen()) {\n      this.close(false);\n    }\n  }\n}\n"
    },
    {
      "name": "select-item.component.ts",
      "content": "import {\n  booleanAttribute,\n  ChangeDetectionStrategy,\n  Component,\n  computed,\n  ElementRef,\n  inject,\n  input,\n  linkedSignal,\n  signal,\n  ViewEncapsulation,\n} from '@angular/core';\n\nimport { NgIcon, provideIcons } from '@ng-icons/core';\nimport { lucideCheck } from '@ng-icons/lucide';\nimport type { ClassValue } from 'clsx';\n\nimport {\n  selectItemIconVariants,\n  selectItemStateVariants,\n  selectItemVariants,\n  type ZardSelectItemModeVariants,\n} from '@/shared/components/select/select.variants';\nimport { mergeClasses, noopFn } from '@/shared/utils/merge-classes';\n\n// Interface to avoid circular dependency\ninterface SelectHost {\n  selectedValue(): string[];\n  selectItem(value: string, label: string): void;\n  navigateTo(): void;\n}\n\n@Component({\n  selector: 'z-select-item, [z-select-item]',\n  imports: [NgIcon],\n  template: `\n    <span data-slot=\"select-item-indicator\" [class]=\"iconClasses()\">\n      @if (isSelected()) {\n        <ng-icon\n          name=\"lucideCheck\"\n          class=\"size-4! text-current\"\n          [strokeWidth]=\"strokeWidth()\"\n          aria-hidden=\"true\"\n          data-testid=\"check-icon\"\n        />\n      }\n    </span>\n    <span data-slot=\"select-item-text\" class=\"truncate\">\n      <ng-content />\n    </span>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  encapsulation: ViewEncapsulation.None,\n  viewProviders: [provideIcons({ lucideCheck })],\n  host: {\n    role: 'option',\n    tabindex: '-1',\n    'data-slot': 'select-item',\n    '[class]': 'classes()',\n    '[attr.value]': 'zValue()',\n    '[attr.data-disabled]': 'zDisabled() ? \"\" : null',\n    '[attr.data-selected]': 'isSelected() ? \"\" : null',\n    '[attr.aria-disabled]': 'zDisabled()',\n    '[attr.aria-selected]': 'isSelected()',\n    '(click)': 'onClick()',\n    '(mouseenter)': 'onMouseEnter()',\n    '(keydown.{tab}.prevent)': 'noopFn',\n  },\n})\nexport class ZardSelectItemComponent {\n  readonly elementRef = inject(ElementRef<HTMLElement>);\n\n  readonly zValue = input.required<string>();\n  readonly zDisabled = input(false, { transform: booleanAttribute });\n  readonly class = input<ClassValue>('');\n\n  private readonly select = signal<SelectHost | null>(null);\n  noopFn = noopFn;\n\n  readonly label = linkedSignal<string>(() => {\n    const element = this.elementRef.nativeElement;\n    return (element.textContent ?? element.innerText)?.trim() ?? '';\n  });\n\n  readonly zMode = signal<ZardSelectItemModeVariants>('normal');\n\n  protected readonly classes = computed(() =>\n    mergeClasses(selectItemVariants({ zMode: this.zMode() }), selectItemStateVariants(), this.class()),\n  );\n\n  protected readonly iconClasses = computed(() => mergeClasses(selectItemIconVariants({ zMode: this.zMode() })));\n\n  protected readonly strokeWidth = computed(() => (this.zMode() === 'compact' ? 3 : 2));\n\n  protected readonly isSelected = computed(() => this.select()?.selectedValue().includes(this.zValue()) ?? false);\n\n  setSelectHost(selectHost: SelectHost) {\n    this.select.set(selectHost);\n  }\n\n  onMouseEnter() {\n    if (this.zDisabled()) {\n      return;\n    }\n    this.select()?.navigateTo();\n  }\n\n  onClick() {\n    if (this.zDisabled()) {\n      return;\n    }\n    this.select()?.selectItem(this.zValue(), this.label());\n  }\n}\n"
    },
    {
      "name": "select-group.component.ts",
      "content": "import { ChangeDetectionStrategy, Component, computed, input, ViewEncapsulation } from '@angular/core';\n\nimport type { ClassValue } from 'clsx';\n\nimport {\n  selectGroupVariants,\n  selectLabelVariants,\n  selectSeparatorVariants,\n} from '@/shared/components/select/select.variants';\nimport { mergeClasses } from '@/shared/utils/merge-classes';\n\n@Component({\n  selector: 'z-select-group, [z-select-group]',\n  template: '<ng-content />',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  encapsulation: ViewEncapsulation.None,\n  host: {\n    'data-slot': 'select-group',\n    role: 'group',\n    '[class]': 'classes()',\n  },\n  exportAs: 'zSelectGroup',\n})\nexport class ZardSelectGroupComponent {\n  readonly class = input<ClassValue>('');\n\n  protected readonly classes = computed(() => mergeClasses(selectGroupVariants(), this.class()));\n}\n\n@Component({\n  selector: 'z-select-label, [z-select-label]',\n  template: '<ng-content />',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  encapsulation: ViewEncapsulation.None,\n  host: {\n    'data-slot': 'select-label',\n    '[class]': 'classes()',\n  },\n  exportAs: 'zSelectLabel',\n})\nexport class ZardSelectLabelComponent {\n  readonly class = input<ClassValue>('');\n\n  protected readonly classes = computed(() => mergeClasses(selectLabelVariants(), this.class()));\n}\n\n@Component({\n  selector: 'z-select-separator, [z-select-separator]',\n  template: '',\n  changeDetection: ChangeDetectionStrategy.OnPush,\n  encapsulation: ViewEncapsulation.None,\n  host: {\n    'data-slot': 'select-separator',\n    role: 'separator',\n    '[class]': 'classes()',\n  },\n  exportAs: 'zSelectSeparator',\n})\nexport class ZardSelectSeparatorComponent {\n  readonly class = input<ClassValue>('');\n\n  protected readonly classes = computed(() => mergeClasses(selectSeparatorVariants(), this.class()));\n}\n"
    },
    {
      "name": "select.variants.ts",
      "content": "import { cva, type VariantProps } from 'class-variance-authority';\n\nimport { mergeClasses } from '@/shared/utils/merge-classes';\n\nexport const selectVariants = cva(\n  mergeClasses(\n    'relative inline-block w-full rounded-lg group',\n    '[&_button]:focus-visible:border [&_button]:focus-visible:border-ring [&_button]:focus-visible:ring-ring/50 [&_button]:focus-visible:ring-[3px]',\n  ),\n);\n\nexport const selectTriggerVariants = cva(\n  mergeClasses(\n    'flex h-8 px-3 py-2 w-full items-center justify-between gap-2 rounded-lg border border-input bg-transparent',\n    'text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed',\n    'disabled:opacity-50 data-[placeholder]:text-muted-foreground [&_svg:not([class*=\"text-\"])]:text-muted-foreground',\n    'dark:bg-input/30 dark:hover:bg-input/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40',\n    'aria-invalid:border-destructive [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\"size-\"])]:size-4',\n    '*:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2',\n  ),\n  {\n    variants: {},\n  },\n);\n\nexport const selectContentVariants = cva(\n  mergeClasses(\n    'relative z-50 flex max-h-96 w-full min-w-[8rem] origin-(--z-select-content-transform-origin) flex-col overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md',\n    'data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95',\n    'data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95',\n    'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',\n    'data-[align-trigger=true]:animate-none!',\n  ),\n  {\n    variants: {\n      zPosition: {\n        'item-aligned': '',\n        popper:\n          'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',\n      },\n    },\n    defaultVariants: {\n      zPosition: 'popper',\n    },\n  },\n);\n\nexport const selectViewportVariants = cva('min-h-0 flex-1 box-border overflow-x-hidden overflow-y-auto p-1', {\n  variants: {\n    zPosition: {\n      'item-aligned': '',\n      popper: 'w-full min-w-(--z-select-trigger-width) scroll-my-1',\n    },\n  },\n  defaultVariants: {\n    zPosition: 'popper',\n  },\n});\n\nexport const selectScrollButtonVariants = cva(\n  'z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*=\"size-\"])]:size-4',\n);\n\nexport const selectGroupVariants = cva('');\n\nexport const selectLabelVariants = cva('px-2 py-1.5 text-xs text-muted-foreground');\n\nexport const selectSeparatorVariants = cva('pointer-events-none -mx-1 block my-1 h-px bg-border');\n\nexport const selectItemVariants = cva(\n  'relative flex w-full cursor-default items-center gap-2 rounded-md outline-hidden select-none',\n  {\n    variants: {\n      zSize: {\n        sm: 'py-1 text-xs',\n        default: 'py-1 text-sm',\n        lg: 'py-2 text-base',\n      },\n      zMode: {\n        normal: 'pr-8 pl-2',\n        compact: 'pr-8 pl-2',\n      },\n    },\n    defaultVariants: {\n      zSize: 'default',\n      zMode: 'normal',\n    },\n  },\n);\n\nexport const selectItemStateVariants = cva(\n  mergeClasses(\n    'focus:bg-accent focus:text-accent-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground',\n    'data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-disabled:opacity-50',\n    '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=\"size-\"])]:size-4 [&_svg:not([class*=\"text-\"])]:text-muted-foreground',\n    '*:data-[slot=select-item-text]:flex *:data-[slot=select-item-text]:items-center *:data-[slot=select-item-text]:gap-2',\n  ),\n);\n\nexport const selectItemIconVariants = cva('absolute flex size-3.5 items-center justify-center', {\n  variants: {\n    // zSize variants are placeholders for compound variant matching\n    zSize: {\n      sm: '',\n      default: '',\n      lg: '',\n    },\n    zMode: {\n      normal: 'right-2',\n      compact: 'right-2',\n    },\n  },\n  defaultVariants: {\n    zSize: 'default',\n    zMode: 'normal',\n  },\n});\n\nexport type ZardSelectPositionVariants = NonNullable<VariantProps<typeof selectContentVariants>['zPosition']>;\nexport type ZardSelectItemModeVariants = NonNullable<VariantProps<typeof selectItemVariants>['zMode']>;\nexport type ZardSelectAlignVariants = 'start' | 'center' | 'end';\n"
    },
    {
      "name": "select.imports.ts",
      "content": "import {\n  ZardSelectGroupComponent,\n  ZardSelectLabelComponent,\n  ZardSelectSeparatorComponent,\n} from '@/shared/components/select/select-group.component';\nimport { ZardSelectItemComponent } from '@/shared/components/select/select-item.component';\nimport { ZardSelectComponent } from '@/shared/components/select/select.component';\n\nexport const ZardSelectImports = [\n  ZardSelectComponent,\n  ZardSelectGroupComponent,\n  ZardSelectItemComponent,\n  ZardSelectLabelComponent,\n  ZardSelectSeparatorComponent,\n] as const;\n"
    },
    {
      "name": "index.ts",
      "content": "export * from '@/shared/components/select/select.component';\nexport * from '@/shared/components/select/select-group.component';\nexport * from '@/shared/components/select/select-item.component';\nexport * from '@/shared/components/select/select.imports';\nexport * from '@/shared/components/select/select.variants';\n"
    }
  ],
  "registryDependencies": [
    "badge"
  ],
  "docs": {
    "overview": "# Select\n\nDisplays a list of options for the user to pick from, triggered by a button.\n\nUse `z-select-item` for options, `z-select-group` with `z-select-label` and `z-select-separator` for grouped lists, and `zInvalid` when the select is rendered inside a validation state.\n\n## Composition\n\nUse the following composition to build a `z-select`:\n\n```text\nz-select\n├── z-select-label\n├── z-select-item\n├── z-select-group\n│   ├── z-select-label\n│   ├── z-select-item\n│   └── z-select-item\n├── z-select-separator\n└── z-select-group\n    ├── z-select-label\n    ├── z-select-item\n    └── z-select-item\n```\n",
    "api": "# API\n\n### z-select\n\n> A customizable select component that supports single and multiple value selection.\n\n| Input              | Description                                      | Type                           | Default                 |\n| ------------------ | ------------------------------------------------ | ------------------------------ | ----------------------- |\n| `[class]`          | Custom CSS classes                               | `ClassValue`                   | `''`                    |\n| `[zAlign]`         | Overlay alignment relative to the trigger        | `'start' \\| 'center' \\| 'end'` | `'center'`              |\n| `[zDisabled]`      | Disables the select                              | `boolean`                      | `false`                 |\n| `[zInvalid]`       | Applies invalid ARIA state and destructive style | `boolean`                      | `false`                 |\n| `[zLabel]`         | Optional manual display label                    | `string`                       | `''`                    |\n| `[zMaxLabelCount]` | Limits visible labels in multiselect mode        | `number`                       | `1`                     |\n| `[zMultiple]`      | Enables multiselect mode                         | `boolean`                      | `false`                 |\n| `[zPlaceholder]`   | Placeholder text                                 | `string`                       | `'Select an option...'` |\n| `[zPosition]`      | Overlay positioning mode                         | `'item-aligned' \\| 'popper'`   | `'popper'`              |\n| `[zSize]`          | Trigger and item size                            | `'sm' \\| 'default' \\| 'lg'`    | `'default'`             |\n| `[(zValue)]`       | Selected value                                   | `string \\| string[]`           | `'' \\| []`              |\n\n| Output               | Description                             | Payload              |\n| -------------------- | --------------------------------------- | -------------------- |\n| `(zSelectionChange)` | Emitted when the selected value changes | `string \\| string[]` |\n\n### z-select-item\n\n> Represents an individual item inside a `z-select` component.\n\n| Input         | Description                         | Type         | Default |\n| ------------- | ----------------------------------- | ------------ | ------- |\n| `[class]`     | Custom CSS classes                  | `ClassValue` | `''`    |\n| `[zValue]`    | The value associated with this item | `string`     | `''`    |\n| `[zDisabled]` | Disables selection for this item    | `boolean`    | `false` |\n\n### z-select-group\n\n> Groups related select items.\n\n| Input     | Description        | Type         | Default |\n| --------- | ------------------ | ------------ | ------- |\n| `[class]` | Custom CSS classes | `ClassValue` | `''`    |\n\n### z-select-label\n\n> Displays a non-selectable label inside a select group.\n\n| Input     | Description        | Type         | Default |\n| --------- | ------------------ | ------------ | ------- |\n| `[class]` | Custom CSS classes | `ClassValue` | `''`    |\n\n### z-select-separator\n\n> Displays a separator between select groups.\n\n| Input     | Description        | Type         | Default |\n| --------- | ------------------ | ------------ | ------- |\n| `[class]` | Custom CSS classes | `ClassValue` | `''`    |\n"
  },
  "demos": [
    {
      "name": "align-item.ts",
      "content": "import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';\n\nimport { ZardFieldImports } from '@/shared/components/field/field.imports';\nimport { ZardSelectImports, type ZardSelectPositionVariants } from '@/shared/components/select';\nimport { ZardSwitchComponent } from '@/shared/components/switch';\n\n@Component({\n  selector: 'z-demo-select-align-item',\n  imports: [ZardSelectImports, ZardSwitchComponent, ...ZardFieldImports],\n  template: `\n    <div z-field-group class=\"w-full min-w-xs\">\n      <div z-field zOrientation=\"horizontal\">\n        <div z-field-content>\n          <label z-field-label for=\"align-item\">Align Item</label>\n          <p z-field-description>Toggle to align the item with the trigger.</p>\n        </div>\n        <z-switch id=\"align-item\" [(zChecked)]=\"alignItem\" />\n      </div>\n\n      <div z-field>\n        <z-select [zPosition]=\"position()\" [(zValue)]=\"selectedFruit\">\n          <z-select-group>\n            <z-select-item zValue=\"apple\">Apple</z-select-item>\n            <z-select-item zValue=\"banana\">Banana</z-select-item>\n            <z-select-item zValue=\"blueberry\">Blueberry</z-select-item>\n            <z-select-item zValue=\"grapes\">Grapes</z-select-item>\n            <z-select-item zValue=\"pineapple\">Pineapple</z-select-item>\n          </z-select-group>\n        </z-select>\n      </div>\n    </div>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoSelectAlignItemComponent {\n  readonly alignItem = signal(true);\n  readonly selectedFruit = signal('banana');\n\n  protected readonly position = computed<ZardSelectPositionVariants>(() =>\n    this.alignItem() ? 'item-aligned' : 'popper',\n  );\n}\n"
    },
    {
      "name": "default.ts",
      "content": "import { ChangeDetectionStrategy, Component, signal } from '@angular/core';\n\nimport { ZardSelectImports } from '@/shared/components/select/select.imports';\n\n@Component({\n  selector: 'z-demo-select-default',\n  imports: [ZardSelectImports],\n  template: `\n    <z-select class=\"w-full min-w-48\" zPlaceholder=\"Select a fruit\" [(zValue)]=\"selectedFruit\">\n      <z-select-label>Fruits</z-select-label>\n      <z-select-item zValue=\"apple\">Apple</z-select-item>\n      <z-select-item zValue=\"banana\">Banana</z-select-item>\n      <z-select-item zValue=\"blueberry\">Blueberry</z-select-item>\n      <z-select-item zValue=\"grapes\">Grapes</z-select-item>\n      <z-select-item zValue=\"pineapple\">Pineapple</z-select-item>\n    </z-select>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoSelectDefaultComponent {\n  readonly selectedFruit = signal('');\n}\n"
    },
    {
      "name": "disabled.ts",
      "content": "import { ChangeDetectionStrategy, Component, signal } from '@angular/core';\n\nimport { ZardSelectImports } from '@/shared/components/select/select.imports';\n\n@Component({\n  selector: 'z-demo-select-disabled',\n  imports: [ZardSelectImports],\n  template: `\n    <z-select class=\"w-full min-w-48\" zPlaceholder=\"Select a fruit\" [(zValue)]=\"selectedFruit\" zDisabled>\n      <z-select-item zValue=\"apple\">Apple</z-select-item>\n      <z-select-item zValue=\"banana\">Banana</z-select-item>\n      <z-select-item zValue=\"blueberry\">Blueberry</z-select-item>\n      <z-select-item zValue=\"grapes\">Grapes</z-select-item>\n      <z-select-item zValue=\"pineapple\">Pineapple</z-select-item>\n    </z-select>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoSelectDisabledComponent {\n  readonly selectedFruit = signal('');\n}\n"
    },
    {
      "name": "groups.ts",
      "content": "import { ChangeDetectionStrategy, Component, signal } from '@angular/core';\n\nimport { ZardSelectImports } from '@/shared/components/select/select.imports';\n\n@Component({\n  selector: 'z-demo-select-groups',\n  imports: [ZardSelectImports],\n  template: `\n    <z-select class=\"w-full min-w-48\" zPlaceholder=\"Select a fruit\" [(zValue)]=\"selectedFood\">\n      <z-select-group>\n        <z-select-label>Fruits</z-select-label>\n        <z-select-item zValue=\"apple\">Apple</z-select-item>\n        <z-select-item zValue=\"banana\">Banana</z-select-item>\n        <z-select-item zValue=\"blueberry\">Blueberry</z-select-item>\n        <z-select-item zValue=\"grapes\">Grapes</z-select-item>\n      </z-select-group>\n      <z-select-separator />\n      <z-select-group>\n        <z-select-label>Vegetables</z-select-label>\n        <z-select-item zValue=\"aubergine\">Aubergine</z-select-item>\n        <z-select-item zValue=\"broccoli\">Broccoli</z-select-item>\n        <z-select-item zValue=\"carrot\">Carrot</z-select-item>\n        <z-select-item zValue=\"courgette\">Courgette</z-select-item>\n      </z-select-group>\n    </z-select>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoSelectGroupsComponent {\n  readonly selectedFood = signal('');\n}\n"
    },
    {
      "name": "invalid.ts",
      "content": "import { ChangeDetectionStrategy, Component, signal } from '@angular/core';\n\nimport { ZardFieldImports } from '@/shared/components/field/field.imports';\nimport { ZardSelectImports } from '@/shared/components/select/select.imports';\n\n@Component({\n  selector: 'z-demo-select-invalid',\n  imports: [...ZardFieldImports, ZardSelectImports],\n  template: `\n    <div z-field class=\"w-full min-w-48\" data-invalid=\"true\">\n      <label z-field-label for=\"select-invalid\">Fruit</label>\n      <z-select id=\"select-invalid\" zPlaceholder=\"Select a fruit\" zInvalid [(zValue)]=\"selectedFruit\">\n        <z-select-item zValue=\"apple\">Apple</z-select-item>\n        <z-select-item zValue=\"banana\">Banana</z-select-item>\n        <z-select-item zValue=\"blueberry\">Blueberry</z-select-item>\n        <z-select-item zValue=\"grapes\">Grapes</z-select-item>\n        <z-select-item zValue=\"pineapple\">Pineapple</z-select-item>\n      </z-select>\n      <z-field-error>Please select a fruit.</z-field-error>\n    </div>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoSelectInvalidComponent {\n  readonly selectedFruit = signal('');\n}\n"
    },
    {
      "name": "multi-select.ts",
      "content": "import { ChangeDetectionStrategy, Component, signal } from '@angular/core';\n\nimport { ZardSelectImports } from '@/shared/components/select/select.imports';\n\n@Component({\n  selector: 'z-demo-multi-select-basic',\n  imports: [ZardSelectImports],\n  template: `\n    <div class=\"flex h-100 w-75 flex-col gap-4\">\n      <p class=\"text-muted-foreground text-sm\">Selected fruits: {{ selectedValues().join(', ') }}</p>\n      <z-select\n        zPlaceholder=\"Select multiple fruits\"\n        [zMultiple]=\"true\"\n        [zMaxLabelCount]=\"3\"\n        [(zValue)]=\"selectedValues\"\n      >\n        <z-select-item zValue=\"apple\">Apple</z-select-item>\n        <z-select-item zValue=\"banana\">Banana</z-select-item>\n        <z-select-item zValue=\"blueberry\">Blueberry</z-select-item>\n        <z-select-item zValue=\"grapes\">Grapes</z-select-item>\n        <z-select-item zValue=\"pineapple\">Pineapple</z-select-item>\n        <z-select-item zValue=\"strawberry\">Strawberry</z-select-item>\n        <z-select-item zValue=\"watermelon\">Watermelon</z-select-item>\n        <z-select-item zValue=\"kiwi\">Kiwi</z-select-item>\n        <z-select-item zValue=\"mango\">Mango</z-select-item>\n      </z-select>\n    </div>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoMultiSelectBasicComponent {\n  readonly selectedValues = signal<string[]>([]);\n}\n"
    },
    {
      "name": "scrollable.ts",
      "content": "import { ChangeDetectionStrategy, Component, signal } from '@angular/core';\n\nimport { ZardSelectImports } from '@/shared/components/select/select.imports';\n\n@Component({\n  selector: 'z-demo-select-scrollable',\n  imports: [ZardSelectImports],\n  template: `\n    <z-select class=\"w-full min-w-64\" zPlaceholder=\"Select a timezone\" zPosition=\"popper\" [(zValue)]=\"selectedTimezone\">\n      <z-select-group>\n        <z-select-label>North America</z-select-label>\n        <z-select-item zValue=\"est\">Eastern Standard Time (EST)</z-select-item>\n        <z-select-item zValue=\"cst\">Central Standard Time (CST)</z-select-item>\n        <z-select-item zValue=\"mst\">Mountain Standard Time (MST)</z-select-item>\n        <z-select-item zValue=\"pst\">Pacific Standard Time (PST)</z-select-item>\n        <z-select-item zValue=\"akst\">Alaska Standard Time (AKST)</z-select-item>\n        <z-select-item zValue=\"hst\">Hawaii Standard Time (HST)</z-select-item>\n      </z-select-group>\n      <z-select-separator />\n      <z-select-group>\n        <z-select-label>Europe & Africa</z-select-label>\n        <z-select-item zValue=\"gmt\">Greenwich Mean Time (GMT)</z-select-item>\n        <z-select-item zValue=\"cet\">Central European Time (CET)</z-select-item>\n        <z-select-item zValue=\"eet\">Eastern European Time (EET)</z-select-item>\n        <z-select-item zValue=\"west\">Western European Summer Time (WEST)</z-select-item>\n        <z-select-item zValue=\"cat\">Central Africa Time (CAT)</z-select-item>\n        <z-select-item zValue=\"eat\">East Africa Time (EAT)</z-select-item>\n      </z-select-group>\n      <z-select-separator />\n      <z-select-group>\n        <z-select-label>Asia</z-select-label>\n        <z-select-item zValue=\"msk\">Moscow Time (MSK)</z-select-item>\n        <z-select-item zValue=\"ist\">India Standard Time (IST)</z-select-item>\n        <z-select-item zValue=\"cst_china\">China Standard Time (CST)</z-select-item>\n        <z-select-item zValue=\"jst\">Japan Standard Time (JST)</z-select-item>\n        <z-select-item zValue=\"kst\">Korea Standard Time (KST)</z-select-item>\n        <z-select-item zValue=\"ist_indonesia\">Indonesia Central Standard Time (WITA)</z-select-item>\n      </z-select-group>\n      <z-select-separator />\n      <z-select-group>\n        <z-select-label>Australia & Pacific</z-select-label>\n        <z-select-item zValue=\"awst\">Australian Western Standard Time (AWST)</z-select-item>\n        <z-select-item zValue=\"acst\">Australian Central Standard Time (ACST)</z-select-item>\n        <z-select-item zValue=\"aest\">Australian Eastern Standard Time (AEST)</z-select-item>\n        <z-select-item zValue=\"nzst\">New Zealand Standard Time (NZST)</z-select-item>\n        <z-select-item zValue=\"fjt\">Fiji Time (FJT)</z-select-item>\n      </z-select-group>\n    </z-select>\n  `,\n  changeDetection: ChangeDetectionStrategy.OnPush,\n})\nexport class ZardDemoSelectScrollableComponent {\n  readonly selectedTimezone = signal('');\n}\n"
    }
  ]
}
