{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "marquee",
  "title": "Marquee",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/marquee/marquee.component.ts",
      "content": "import {\n  AfterViewInit,\n  Component,\n  ContentChild,\n  Directive,\n  ElementRef,\n  Input,\n  OnDestroy,\n  TemplateRef,\n  ViewChildren,\n  ViewEncapsulation,\n  QueryList,\n  booleanAttribute,\n  inject,\n  numberAttribute,\n  ChangeDetectionStrategy,\n} from '@angular/core'\nimport { UiRenderTemplateDirective } from '@/ui/popper/popper'\nimport { cn } from '@/lib/utils'\n\n// Copied verbatim from the React marquee (injected there as a global <style>). Unscoped\n// (ViewEncapsulation.None) so the inline animationName resolves the @keyframes.\nconst MARQUEE_MOTION_STYLES = `\n@keyframes uipkge-marquee-x {\n  from {\n    transform: translateX(0);\n  }\n  to {\n    transform: translateX(-100%);\n  }\n}\n\n@keyframes uipkge-marquee-y {\n  from {\n    transform: translateY(0);\n  }\n  to {\n    transform: translateY(-100%);\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  [data-slot='marquee-track'] {\n    animation: none !important;\n  }\n}\n`\n\n/**\n * Marks the repeated content of a marquee: `<ng-template uiMarqueeItem>…</ng-template>`. Each\n * track stamps a live copy of it (React renders `children` once per track). Plain projected\n * content also works: it fills the first track and the others get static DOM clones.\n */\n@Directive({ selector: 'ng-template[uiMarqueeItem]', standalone: true })\nexport class UiMarqueeItemDirective {\n  readonly templateRef = inject<TemplateRef<unknown>>(TemplateRef)\n}\n\n/**\n * Continuously auto-scrolling content (React `Marquee`): horizontal or vertical, configurable\n * speed / gap / repeat, pause on hover or hard pause. The content is repeated `repeat` times\n * (copies 2..n aria-hidden) for a seamless loop.\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-marquee, [ui-marquee]',\n  standalone: true,\n  encapsulation: ViewEncapsulation.None,\n  styles: [MARQUEE_MOTION_STYLES],\n  imports: [UiRenderTemplateDirective],\n  host: {\n    'data-uipkge': '',\n    'data-slot': 'marquee',\n    role: 'region',\n    'aria-roledescription': 'marquee',\n    '[attr.data-orientation]': 'orientation',\n    '[attr.data-direction]': 'direction',\n    '[class]': 'hostClass',\n    '[style.--marquee-gap]': 'gap + \"px\"',\n  },\n  template: `\n    @for (i of tracks; track i) {\n      <div\n        #track\n        data-slot=\"marquee-track\"\n        [class]=\"trackClass\"\n        [style]=\"trackStyle\"\n        [attr.aria-hidden]=\"i > 0 ? 'true' : null\"\n      >\n        @if (item) {\n          <ng-container [uiRenderTemplate]=\"item.templateRef\" />\n        } @else if (i === 0) {\n          <ng-content />\n        }\n      </div>\n    }\n  `,\n})\nexport class UiMarqueeComponent implements AfterViewInit, OnDestroy {\n  /** Scroll axis. */\n  @Input() orientation: 'horizontal' | 'vertical' = 'horizontal'\n  /** Travel direction. */\n  @Input() direction: 'left' | 'right' | 'up' | 'down' = 'left'\n  /** Animation duration in seconds. Lower = faster. */\n  @Input({ transform: numberAttribute }) speed = 20\n  /** Pause the animation on hover. */\n  @Input({ transform: booleanAttribute }) pauseOnHover = false\n  /** Gap between repeated content groups (px). */\n  @Input({ transform: numberAttribute }) gap = 16\n  /** Number of times the content is duplicated for a seamless loop. */\n  @Input({ transform: numberAttribute }) repeat = 2\n  /** Hard pause the animation. */\n  @Input({ transform: booleanAttribute }) paused = false\n  @Input('class') className?: string\n\n  @ContentChild(UiMarqueeItemDirective) item?: UiMarqueeItemDirective\n  @ViewChildren('track') private trackEls!: QueryList<ElementRef<HTMLElement>>\n  private observer?: MutationObserver\n\n  get isVertical(): boolean {\n    return this.orientation === 'vertical'\n  }\n\n  get tracks(): number[] {\n    return Array.from({ length: Math.max(0, this.repeat) }, (_, i) => i)\n  }\n\n  get hostClass(): string {\n    return cn(\n      'group flex overflow-hidden',\n      this.isVertical ? 'flex-col' : 'flex-row',\n      this.pauseOnHover ? 'hover:[&>[data-slot=marquee-track]]:[animation-play-state:paused]' : '',\n      this.className,\n    )\n  }\n\n  get trackClass(): string {\n    return cn(\n      'flex shrink-0',\n      this.isVertical ? 'flex-col' : 'flex-row',\n      this.paused ? '![animation-play-state:paused]' : '',\n    )\n  }\n\n  get trackStyle(): Record<string, string> {\n    const reverse = this.direction === 'right' || this.direction === 'down'\n    return {\n      gap: 'var(--marquee-gap)',\n      'animation-name': this.isVertical ? 'uipkge-marquee-y' : 'uipkge-marquee-x',\n      'animation-duration': `${this.speed}s`,\n      'animation-timing-function': 'linear',\n      'animation-iteration-count': 'infinite',\n      'animation-direction': reverse ? 'reverse' : 'normal',\n    }\n  }\n\n  ngAfterViewInit(): void {\n    if (this.item) return\n    // Plain projected content: mirror track 0 into the aria-hidden copies, and keep them in sync.\n    this.syncClones()\n    this.trackEls.changes.subscribe(() => this.syncClones())\n    const first = this.trackEls.first?.nativeElement\n    if (first && typeof MutationObserver !== 'undefined') {\n      this.observer = new MutationObserver(() => this.syncClones())\n      this.observer.observe(first, { childList: true, subtree: true, characterData: true, attributes: true })\n    }\n  }\n\n  ngOnDestroy(): void {\n    this.observer?.disconnect()\n  }\n\n  private syncClones(): void {\n    const [first, ...rest] = this.trackEls.map((t) => t.nativeElement)\n    if (!first) return\n    for (const el of rest) el.replaceChildren(...Array.from(first.childNodes, (n) => n.cloneNode(true)))\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/marquee/marquee.component.ts"
    },
    {
      "path": "packages/registry-angular/components/marquee/index.ts",
      "content": "export { UiMarqueeComponent, UiMarqueeItemDirective } from './marquee.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/marquee/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/popper.json"
  ],
  "description": "Continuously auto-scrolling content. Scrolls horizontally or vertically in any direction, with configurable speed, gap, repeat count for a seamless loop, pause-on-hover, and a hard pause prop. Respects prefers-reduced-motion.",
  "categories": [
    "display"
  ]
}