{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "marquee",
  "title": "Marquee",
  "type": "registry:ui",
  "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"
  ],
  "files": [
    {
      "path": "packages/elements-lit/src/components/marquee/marquee.ts",
      "content": "import { LitElement, css, html, isServer } from 'lit'\nimport { cn } from '../../lib/utils'\nimport { tailwind } from '../../lib/styles'\nimport { ThemeController } from '../../lib/theme'\n\nconst marqueeStyles = css`\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\nexport type MarqueeOrientation = 'horizontal' | 'vertical'\nexport type MarqueeDirection = 'left' | 'right' | 'up' | 'down'\n\n/**\n * <uip-marquee> — infinitely scrolling marquee banner.\n */\nexport class UipMarquee extends LitElement {\n  static styles = [\n    tailwind,\n    marqueeStyles,\n    css`\n      :host {\n        display: block;\n        overflow: hidden;\n      }\n    `,\n  ]\n\n  static properties = {\n    orientation: { reflect: true },\n    direction: { reflect: true },\n    speed: { type: Number, reflect: true },\n    pauseOnHover: { type: Boolean, attribute: 'pause-on-hover', reflect: true },\n    gap: { type: Number, reflect: true },\n    repeat: { type: Number, reflect: true },\n    paused: { type: Boolean, reflect: true },\n  }\n\n  orientation: MarqueeOrientation = 'horizontal'\n  direction: MarqueeDirection = 'left'\n  speed = 20\n  pauseOnHover = false\n  gap = 16\n  repeat = 2\n  paused = false\n\n  constructor() {\n    super()\n    new ThemeController(this)\n  }\n\n  connectedCallback() {\n    super.connectedCallback()\n    this.setAttribute('data-uipkge', '')\n    this.setAttribute('data-slot', 'marquee')\n    this.setAttribute('role', 'region')\n    this.setAttribute('aria-roledescription', 'marquee')\n  }\n\n  willUpdate() {\n    this.setAttribute('data-orientation', this.orientation)\n    this.setAttribute('data-direction', this.direction)\n  }\n\n  protected updated(changedProps: Map<string, unknown>) {\n    super.updated(changedProps)\n    if (changedProps.has('repeat')) {\n      this.cloneNodes()\n    }\n  }\n\n  private onSlotChange(e: Event) {\n    const slot = e.target as HTMLSlotElement\n    const assigned = slot.assignedNodes({ flatten: true })\n    this.cloneNodes(assigned)\n  }\n\n  private cloneNodes(nodes?: Node[]) {\n    if (isServer) return\n    if (!nodes) {\n      const slot = this.renderRoot.querySelector<HTMLSlotElement>('slot')\n      if (!slot) return\n      nodes = slot.assignedNodes({ flatten: true })\n    }\n    const cloneContainers = this.renderRoot.querySelectorAll<HTMLElement>('[data-clone]')\n    cloneContainers.forEach((container) => {\n      container.replaceChildren(...nodes!.map((n) => n.cloneNode(true)))\n    })\n  }\n\n  render() {\n    const isVertical = this.orientation === 'vertical'\n    const reverse = this.direction === 'right' || this.direction === 'down'\n\n    const containerClass = cn(\n      'group flex overflow-hidden',\n      isVertical ? 'flex-col' : 'flex-row',\n      this.pauseOnHover && 'hover:[&>[data-slot=marquee-track]]:[animation-play-state:paused]',\n    )\n\n    const trackClass = cn(\n      'flex shrink-0',\n      isVertical ? 'flex-col' : 'flex-row',\n      this.paused && '![animation-play-state:paused]',\n    )\n\n    const trackStyle = `gap: var(--marquee-gap); animation-name: ${\n      isVertical ? 'uipkge-marquee-y' : 'uipkge-marquee-x'\n    }; animation-duration: ${this.speed}s; animation-timing-function: linear; animation-iteration-count: infinite; animation-direction: ${\n      reverse ? 'reverse' : 'normal'\n    };`\n\n    return html`\n      <div\n        part=\"base\"\n        data-slot=\"marquee\"\n        data-orientation=${this.orientation}\n        data-direction=${this.direction}\n        class=${containerClass}\n        style=\"--marquee-gap: ${this.gap}px;\"\n      >\n        <div data-slot=\"marquee-track\" class=${trackClass} style=${trackStyle}>\n          <slot @slotchange=${this.onSlotChange}></slot>\n        </div>\n        ${Array.from({ length: Math.max(0, this.repeat - 1) }, (_, i) => html`\n          <div\n            data-slot=\"marquee-track\"\n            data-clone=${i + 1}\n            class=${trackClass}\n            style=${trackStyle}\n            aria-hidden=\"true\"\n          ></div>\n        `)}\n      </div>\n    `\n  }\n}\n\ncustomElements.get('uip-marquee') || customElements.define('uip-marquee', UipMarquee)\n\ndeclare global {\n  interface HTMLElementTagNameMap {\n    'uip-marquee': UipMarquee\n  }\n}\n",
      "type": "registry:file",
      "target": "src/uip/components/marquee/marquee.ts"
    }
  ],
  "dependencies": [
    "lit"
  ],
  "registryDependencies": [
    "https://uipkge.dev/r/lit/lit-core.json"
  ]
}
