/ Examples
Source
// Horizontal Scrolling — Vue implementation with useVList composable
// Demonstrates orientation: 'horizontal' with item.width

import { createApp, ref } from "vue";
import { scrollbar } from "vlist";
import { useVList, useVListEvent } from "vlist/vue";
import {
  items,
  itemTemplate,
  calculateStats,
  ITEM_HEIGHT,
  ITEM_WIDTH,
} from "../shared.js";

// =============================================================================
// App Component
// =============================================================================

const App = {
  setup() {
    // State
    const stats = ref({
      domNodes: 0,
      saved: 0,
    });

    // Initialize vlist
    const { containerRef, instance } = useVList({
      orientation: "horizontal",
      scroll: { wheel: true },
      ariaLabel: "Horizontal card carousel",
      item: {
        height: ITEM_HEIGHT,
        width: ITEM_WIDTH,
        template: itemTemplate,
      },
      items,
    }, [scrollbar()]);

    // Update stats
    const updateStats = () => {
      const domNodes = document.querySelectorAll(".vlist-item").length;
      const { saved } = calculateStats(domNodes, items.length);
      stats.value = { domNodes, saved };
    };

    // Track scroll and range changes
    useVListEvent(instance, "scroll", updateStats);
    useVListEvent(instance, "range:change", updateStats);

    // Track item clicks
    useVListEvent(instance, "item:click", ({ item, index }) => {
      console.log(`Clicked: ${item.title} at index ${index}`);
    });

    // Navigation handlers
    const scrollToStart = () => {
      instance.value?.scrollToIndex(0);
    };

    const scrollToCenter = () => {
      instance.value?.scrollToIndex(5000, "center");
    };

    const scrollToEnd = () => {
      instance.value?.scrollToIndex(items.length - 1, "end");
    };

    const smoothScroll = () => {
      const current = instance.value?.getScrollPosition() || 0;
      const targetIndex = current < 100 ? 500 : 0;
      instance.value?.scrollToIndex(targetIndex, {
        align: "start",
        behavior: "smooth",
        duration: 800,
      });
    };

    return {
      containerRef,
      stats,
      items,
      scrollToStart,
      scrollToCenter,
      scrollToEnd,
      smoothScroll,
    };
  },

  template: `
    <div class="container">
      <header>
        <h1>Horizontal Scrolling</h1>
        <p class="description">
          Vue implementation with <code>useVList</code> composable. A horizontal
          virtual list rendering 10,000 cards with
          <code>orientation: 'horizontal'</code> and
          <code>scroll.wheel: true</code>. Scroll with mouse wheel, trackpad
          swipe, or the buttons below. Only visible items are in the DOM.
        </p>
      </header>

      <div class="stats">
        <span>
          <strong>Total:</strong> {{ items.length.toLocaleString() }}
        </span>
        <span>
          <strong>DOM nodes:</strong> {{ stats.domNodes }}
        </span>
        <span>
          <strong>Memory saved:</strong> {{ stats.saved }}%
        </span>
      </div>

      <div ref="containerRef" id="list-container" />

      <div class="controls">
        <button @click="scrollToStart">⏮ Start</button>
        <button @click="scrollToCenter">⏺ Center (5000)</button>
        <button @click="scrollToEnd">⏭ End</button>
        <button @click="smoothScroll">🎞 Smooth Scroll</button>
      </div>

      <footer>
        <p>
          Uses <code>orientation: 'horizontal'</code> with
          <code>item.width</code> for the main axis and
          <code>item.height</code> for the cross axis.
          <code>scroll.wheel: true</code> maps vertical mouse wheel to
          horizontal scroll. Custom scrollbar renders at the bottom
          automatically. 💚
        </p>
      </footer>
    </div>
  `,
};

// =============================================================================
// Mount
// =============================================================================

createApp(App).mount("#vue-root");
<div id="vue-root"></div>
// Shared data and utilities for horizontal example variants
// This file is imported by all framework implementations to avoid duplication

// =============================================================================
// Constants
// =============================================================================

export const ITEM_COUNT = 10_000;
export const ITEM_HEIGHT = 220;
export const ITEM_WIDTH = 180;

// Color palette for cards
export const colors = [
  ["#667eea", "#764ba2"],
  ["#f093fb", "#f5576c"],
  ["#4facfe", "#00f2fe"],
  ["#43e97b", "#38f9d7"],
  ["#fa709a", "#fee140"],
  ["#a18cd1", "#fbc2eb"],
  ["#fccb90", "#d57eeb"],
  ["#e0c3fc", "#8ec5fc"],
  ["#f5576c", "#ff9a9e"],
  ["#667eea", "#00f2fe"],
];

export const emojis = ["🎵", "🎶", "🎸", "🥁", "🎹", "🎺", "🎻", "🎷", "🪗", "🪘"];

// =============================================================================
// Data Generation
// =============================================================================

// Generate card items
export const items = Array.from({ length: ITEM_COUNT }, (_, i) => {
  const color = colors[i % colors.length];
  return {
    id: i + 1,
    title: `Card ${i + 1}`,
    subtitle: `Item #${(i + 1).toLocaleString()}`,
    emoji: emojis[i % emojis.length],
    gradientStart: color[0],
    gradientEnd: color[1],
  };
});

// =============================================================================
// Templates
// =============================================================================

// Card template
export const itemTemplate = (item) => `
  <div class="card" style="background: linear-gradient(135deg, ${item.gradientStart} 0%, ${item.gradientEnd} 100%);">
    <div class="card-emoji">${item.emoji}</div>
    <div class="card-title">${item.title}</div>
    <div class="card-subtitle">${item.subtitle}</div>
  </div>
`;

// =============================================================================
// Utilities
// =============================================================================

// Calculate stats
export function calculateStats(domNodes, total) {
  const saved = Math.round((1 - domNodes / total) * 100);
  return { saved };
}

// Format stats HTML
export function formatStatsHtml(domNodes, total) {
  const { saved } = calculateStats(domNodes, total);
  return `
    <span><strong>Total:</strong> ${total.toLocaleString()}</span>
    <span><strong>DOM nodes:</strong> ${domNodes}</span>
    <span><strong>Memory saved:</strong> ${saved}%</span>
  `;
}
/* Horizontal Scrolling Example — example-specific styles only
   Common styles (.container, h1, .description, .stats, footer)
   are provided by example/example.css using shell.css design tokens. */

/* List container - full width, responsive height */
#list-container {
    width: 100%;
    max-width: 100%;
    min-width: 0;
    height: 220px;
    overflow: hidden;
}

/* Override vlist defaults for horizontal cards */
#list-container .vlist {
    border: none;
    border-radius: 12px;
    background: transparent;
    max-width: 100%;
    width: 100%;
    box-sizing: border-box;
    overflow-x: auto;
}

#list-container .vlist-viewport {
    max-width: 100%;
    width: 100% !important;
    overflow-x: auto;
}

#list-container .vlist-item {
    padding: 8px;
    border-right: none;
    cursor: pointer;
}

/* Card styles */
.card {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    height: 100%;
    border-radius: 12px;
    color: #fff;
    text-align: center;
    padding: 16px 12px;
    transition:
        transform 0.15s ease-out,
        box-shadow 0.15s ease-out;
}

.vlist-item:hover .card {
    transform: scale(1.03);
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
}

.card-emoji {
    font-size: 36px;
    line-height: 1;
}

.card-title {
    font-weight: var(--fw-bold);
    font-size: 15px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 100%;
}

.card-subtitle {
    font-size: 12px;
    opacity: 0.85;
    white-space: nowrap;
}

/* Bottom controls */
.controls {
    margin-top: 16px;
}

/* ============================================================================
   Responsive
   ============================================================================ */

@media (max-width: 900px) {
    #list-container {
        height: 190px;
    }

    #list-container .vlist-item {
        padding: 6px;
    }

    .card {
        padding: 10px 8px;
        border-radius: 10px;
    }

    .card-emoji {
        font-size: 30px;
    }

    .card-title {
        font-size: 14px;
    }
}

@media (max-width: 720px) {
    #list-container {
        height: 160px;
    }

    #list-container .vlist-item {
        padding: 4px;
    }

    .card {
        padding: 8px 6px;
        gap: 4px;
        border-radius: 8px;
    }

    .card-emoji {
        font-size: 24px;
    }

    .card-title {
        font-size: 12px;
    }

    .card-subtitle {
        font-size: 10px;
    }

    .controls {
        flex-direction: column;
        gap: 8px;
    }

    .controls button {
        width: 100%;
        font-size: 13px;
    }
}