{"_id":"@arcmantle/infinite-scroller","name":"@arcmantle/infinite-scroller","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@arcmantle/infinite-scroller","version":"1.0.0","description":"Infinite scroller base component.","license":"Apache-2.0","author":{"name":"Kristoffer Roen-Lie"},"sideEffects":false,"type":"module","exports":{".":"./dist/infinite-scroller.js"},"main":"./dist/infinite-scroller.js","types":"./dist/infinite-scroller.d.ts","dependencies":{"@arcmantle/library":"^1.0.0","lit":"^3.3.0","tslib":"^2.8.1"},"devDependencies":{"@arcmantle/tsconfig":"^1.0.6","@arcmantle/vite-lib-config":"^1.0.0","@types/node":"^24.0.14","rimraf":"^6.0.1","typescript":"^5.8.3","vite":"^7.0.5"},"scripts":{"build":"pnpm run --sequential \"/^build::.*/\"","build::js":"vite build","build::ts":"tsc --project ./src/tsconfig.json","dev":"vite --config ./demo/vite.config.ts"},"_id":"@arcmantle/infinite-scroller@1.0.0","_integrity":"sha512-z7n4JF6SqyWK9/TgUomMcTEQfnzfjNJr+hcqcx4CmcYkZ6Z0Sw2oNjTp6DNkmTV1B0KBQHHWWZ555HDkLPifvQ==","_resolved":"/tmp/c8a038ddba83010463bde8e254274d0e/arcmantle-infinite-scroller-1.0.0.tgz","_from":"file:arcmantle-infinite-scroller-1.0.0.tgz","_nodeVersion":"24.4.1","_npmVersion":"11.4.2","dist":{"integrity":"sha512-z7n4JF6SqyWK9/TgUomMcTEQfnzfjNJr+hcqcx4CmcYkZ6Z0Sw2oNjTp6DNkmTV1B0KBQHHWWZ555HDkLPifvQ==","shasum":"70bddb2c6e50bce7d2436912763176deeab96d0c","tarball":"https://registry.npmjs.org/@arcmantle/infinite-scroller/-/infinite-scroller-1.0.0.tgz","fileCount":8,"unpackedSize":57520,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICrk9RrWEzKnz2g3D0E4LIN8lGnZmd/FVxTCwDheSGRlAiAb3LjvGapH8o702XGLyXhnQNKFhSuNCNHCzz9VkOGTQg=="}]},"_npmUser":{"name":"roenlie","email":"kristofferroenlie@gmail.com"},"directories":{},"maintainers":[{"name":"roenlie","email":"kristofferroenlie@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/infinite-scroller_1.0.0_1752759218516_0.8788302480562173"},"_hasShrinkwrap":false}},"time":{"created":"2025-07-17T13:33:38.441Z","1.0.0":"2025-07-17T13:33:38.735Z","modified":"2025-07-17T13:33:39.334Z"},"maintainers":[{"name":"roenlie","email":"kristofferroenlie@gmail.com"}],"description":"Infinite scroller base component.","author":{"name":"Kristoffer Roen-Lie"},"license":"Apache-2.0","readme":"# @arcmantle/infinite-scroller\n\nA high-performance infinite scrolling component that virtualizes large lists by only rendering visible items. Built with Lit and optimized for smooth scrolling with thousands of items.\n\n## Features\n\n- **🚀 Virtual Scrolling**: Only renders items that are visible in the viewport\n- **⚡ High Performance**: Handles thousands of items with minimal memory usage\n- **🔄 Dynamic Buffering**: Automatically calculates optimal buffer sizes based on viewport\n- **📱 Responsive**: Adapts to container size changes automatically\n- **🎯 Smooth Scrolling**: Uses CSS transforms and optimized rendering for 60fps performance\n- **🛠️ Abstract Base Class**: Extend and customize for your specific use cases\n- **🎨 Styleable**: Full CSS customization with CSS custom properties\n\n## Installation\n\n```bash\nnpm install @arcmantle/infinite-scroller\n\npnpm add @arcmantle/infinite-scroller\n\nyarn add @arcmantle/infinite-scroller\n```\n\n## Basic Usage\n\nThe `InfiniteScroller` is an abstract base class that you extend to create your own infinite scrolling components:\n\n```typescript\nimport { InfiniteScroller } from '@arcmantle/infinite-scroller';\nimport { customElement } from 'lit/decorators.js';\n\n@customElement('my-list')\nexport class MyListComponent extends InfiniteScroller {\n\n  constructor() {\n    super();\n    this.maxIndex = 10000; // Total number of items\n  }\n\n  // Create the DOM element for each list item\n  protected createElement(): HTMLElement {\n    return document.createElement('my-list-item');\n  }\n\n  // Update the element content based on its index\n  protected updateElement(element: HTMLElement, index: number): void {\n    if (index < 0 || index >= this.maxIndex) {\n      element.style.visibility = 'hidden';\n      return;\n    }\n\n    element.style.visibility = 'visible';\n    element.textContent = `Item ${index}`;\n  }\n}\n```\n\n## Advanced Example\n\nHere's a more complete example with custom styling and data:\n\n```typescript\nimport { css, html, LitElement } from 'lit';\nimport { customElement, property } from 'lit/decorators.js';\nimport { InfiniteScroller } from '@arcmantle/infinite-scroller';\n\ninterface ListItem {\n  id: string;\n  title: string;\n  description: string;\n}\n\n@customElement('data-list')\nexport class DataListComponent extends InfiniteScroller {\n\n  @property({ type: Array })\n  data: ListItem[] = [];\n\n  constructor() {\n    super();\n    this.maxIndex = this.data.length;\n  }\n\n  protected createElement(): HTMLElement {\n    return document.createElement('data-list-item');\n  }\n\n  protected updateElement(element: DataListItemComponent, index: number): void {\n    if (index < 0 || index >= this.data.length) {\n      element.style.visibility = 'hidden';\n      return;\n    }\n\n    element.style.visibility = 'visible';\n    element.item = this.data[index];\n  }\n\n  // Handle infinite loading\n  protected override onScroll(): void {\n    super.onScroll();\n\n    // Load more data when near the end\n    if ((this.maxIndex - this.position) < 30) {\n      this.loadMoreData();\n    }\n  }\n\n  private async loadMoreData(): Promise<void> {\n    const newData = await fetchMoreItems();\n    this.data = [...this.data, ...newData];\n    this.maxIndex = this.data.length;\n  }\n\n  static override styles = css`\n    :host {\n      --item-height: 80px;\n      height: 400px;\n      border: 1px solid #ccc;\n    }\n  `;\n}\n\n@customElement('data-list-item')\nexport class DataListItemComponent extends LitElement {\n\n  @property({ type: Object })\n  item?: ListItem;\n\n  protected render() {\n    if (!this.item) return html``;\n\n    return html`\n      <div class=\"item\">\n        <h3>${this.item.title}</h3>\n        <p>${this.item.description}</p>\n      </div>\n    `;\n  }\n\n  static styles = css`\n    :host {\n      display: block;\n      padding: 16px;\n      border-bottom: 1px solid #eee;\n    }\n\n    .item h3 {\n      margin: 0 0 8px 0;\n      font-size: 16px;\n    }\n\n    .item p {\n      margin: 0;\n      color: #666;\n      font-size: 14px;\n    }\n  `;\n}\n```\n\n## Properties\n\n### Core Properties\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `maxIndex` | `number` | Total number of items in the list |\n| `itemHeight` | `number` | Height of each item in pixels |\n| `bufferSize` | `number` | Number of items to render outside viewport (calculated automatically) |\n| `position` | `number` | Current scroll position as item index (can be fractional) |\n\n### CSS Custom Properties\n\n| Property | Default | Description |\n|----------|---------|-------------|\n| `--item-height` | `60px` | Height of each list item |\n\n## Methods\n\n### Abstract Methods (Must Implement)\n\n```typescript\n// Create a new DOM element for list items\nprotected abstract createElement(): HTMLElement;\n\n// Update element content based on its index position\nprotected abstract updateElement(element: HTMLElement, index: number): void;\n```\n\n### Public Methods\n\n```typescript\n// Get/set scroll position by item index\nscroller.position = 100; // Scroll to item 100\nconst currentPosition = scroller.position;\n```\n\n### Lifecycle Hooks\n\n```typescript\n// Override to handle scroll events\nprotected onScroll(): void {\n  super.onScroll();\n  // Your custom scroll logic\n}\n\n// Override to handle resize events\nprotected onResize(entries?: ResizeObserverEntry[]): boolean {\n  const result = super.onResize(entries);\n  // Your custom resize logic\n  return result;\n}\n```\n\n## Architecture\n\n### Virtual Scrolling Strategy\n\nThe infinite scroller uses a dual-buffer strategy:\n\n1. **Two Buffers**: Maintains two buffer zones that contain rendered items\n2. **Dynamic Translation**: Buffers are translated vertically as the user scrolls\n3. **Automatic Sizing**: Buffer size is calculated based on viewport height\n4. **Smart Updates**: Only updates items that are actually visible\n\n### Buffer Management\n\n```text\n┌─────────────────┐\n│   Buffer 0      │ ← Contains items 0-19\n├─────────────────┤\n│   Buffer 1      │ ← Contains items 20-39\n├─────────────────┤\n│   (Virtual)     │ ← Items 40+ not rendered\n└─────────────────┘\n```\n\nAs the user scrolls down:\n\n- Buffer 0 moves below Buffer 1 and updates to show items 40-59\n- Buffer 1 continues showing items 20-39\n- The cycle continues seamlessly\n\n### Performance Optimizations\n\n- **CSS Transforms**: Uses `translate3d()` for hardware acceleration\n- **Passive Scrolling**: Scroll listeners are passive for better performance\n- **ResizeObserver**: Efficiently handles container size changes\n- **Minimal DOM**: Only creates elements that fit in the buffers\n- **Smart Updates**: Only updates visible elements during scroll\n\n## Events\n\n### Custom Events\n\n```typescript\n// Fired when the scroller is ready and initialized\nscroller.addEventListener('ready', (event) => {\n  console.log('Scroller is ready');\n});\n```\n\n### Native Events\n\nThe component supports all standard scroll events on the internal scroller element.\n\n## Styling\n\n### Basic Styling\n\n```css\nmy-list {\n  --item-height: 100px;\n  height: 500px;\n  width: 100%;\n  border: 1px solid #ddd;\n}\n```\n\n### Advanced Styling with CSS Parts\n\n```css\nmy-list::part(scroller) {\n  border-radius: 8px;\n  background: #f9f9f9;\n}\n\nmy-list::part(buffer) {\n  /* Style the buffer containers */\n}\n```\n\n### Responsive Design\n\n```css\nmy-list {\n  --item-height: 60px;\n  height: 100%;\n}\n\n@media (max-width: 768px) {\n  my-list {\n    --item-height: 80px;\n  }\n}\n```\n\n## Common Patterns\n\n### Infinite Loading\n\n```typescript\nprotected override onScroll(): void {\n  super.onScroll();\n\n  const threshold = 50; // Items from end\n  if ((this.maxIndex - this.position) < threshold) {\n    this.loadMoreItems();\n  }\n}\n\nprivate async loadMoreItems(): Promise<void> {\n  if (this.loading) return;\n\n  this.loading = true;\n  const newItems = await this.dataService.fetchMore();\n  this.data.push(...newItems);\n  this.maxIndex = this.data.length;\n  this.loading = false;\n}\n```\n\n### Search and Filter\n\n```typescript\nprivate filteredData: Item[] = [];\n\nsearch(query: string): void {\n  this.filteredData = this.allData.filter(item =>\n    item.title.toLowerCase().includes(query.toLowerCase())\n  );\n  this.maxIndex = this.filteredData.length;\n  this.position = 0; // Reset to top\n}\n\nprotected updateElement(element: HTMLElement, index: number): void {\n  const item = this.filteredData[index];\n  // Update element with filtered data\n}\n```\n\n### Dynamic Item Heights\n\nFor variable item heights, calculate and cache heights:\n\n```typescript\nprivate itemHeights = new Map<number, number>();\n\nprotected override get itemHeight(): number {\n  // Return average height or base height\n  return this.averageItemHeight || 60;\n}\n\nprotected updateElement(element: HTMLElement, index: number): void {\n  super.updateElement(element, index);\n\n  // Measure and cache actual height\n  requestAnimationFrame(() => {\n    const height = element.getBoundingClientRect().height;\n    this.itemHeights.set(index, height);\n  });\n}\n```\n\n## Browser Support\n\n- **Modern Browsers**: Chrome 69+, Firefox 63+, Safari 12+\n- **Required Features**:\n  - ResizeObserver API\n  - CSS Grid Layout\n  - CSS Custom Properties\n  - ES2020+ JavaScript features\n\n## Performance Tips\n\n1. **Keep Updates Light**: Minimize work in `updateElement()`\n2. **Use CSS for Styling**: Prefer CSS over JavaScript for visual changes\n3. **Batch DOM Updates**: Group multiple changes together\n4. **Profile Your Code**: Use browser dev tools to identify bottlenecks\n5. **Consider Item Complexity**: Simpler items = better performance\n\n## Troubleshooting\n\n### Common Issues\n\n**Items not updating correctly:**\n\n- Ensure `updateElement()` handles all edge cases\n- Check that `maxIndex` is set correctly\n\n**Scrolling feels sluggish:**\n\n- Reduce complexity in `updateElement()`\n- Check for memory leaks in item components\n\n**Layout jumping:**\n\n- Ensure consistent `--item-height` values\n- Avoid dynamic height changes during scroll\n\n**Buffer size warnings:**\n\n- Increase container height or decrease item height\n- Check for CSS issues affecting measurements\n\n## Development\n\n### Building\n\n```bash\npnpm install\npnpm build\n```\n\n### Development Server\n\n```bash\npnpm dev\n```\n\n### Testing\n\n```bash\npnpm test\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Add tests for new functionality\n4. Ensure all tests pass\n5. Submit a pull request\n\n## License\n\nThis project is licensed under the Apache 2.0 License - see the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0) for details.\n\n## Related Packages\n\nThis component is part of the @arcmantle ecosystem:\n\n- `@arcmantle/library` - Core utilities and helper functions\n- `lit` - The underlying web component framework\n\n.\n","readmeFilename":"README.md","_rev":"1-59fac5540f38fdc9d5ac37038fb3b511"}