{"_id":"@ahmedbanihani/mesk","name":"@ahmedbanihani/mesk","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@ahmedbanihani/mesk","version":"0.1.0","description":"Mesk — virtualization for React — innerHTML + portals + RAF scroll + event delegation. Standalone engine, zero dependencies.","main":"lib/index.cjs","module":"lib/index.js","types":"lib/index.d.ts","type":"module","exports":{".":{"types":"./lib/index.d.ts","require":"./lib/index.cjs","default":"./lib/index.js"}},"sideEffects":false,"scripts":{"build":"rollup -c","typecheck":"tsc -p . --noEmit","test":"vitest run","test:watch":"vitest","lint":"eslint 'src/**/*.{ts,tsx}' --cache","size":"size-limit","prepublishOnly":"rimraf lib && npm run build"},"peerDependencies":{"react":">=18.0.0","react-dom":">=18.0.0"},"devDependencies":{"@babel/plugin-transform-react-pure-annotations":"^7.27.1","@rollup/plugin-babel":"^6.0.4","@rollup/plugin-terser":"^0.4.4","@rollup/plugin-typescript":"^12.0.0","@size-limit/preset-small-lib":"^12.0.0","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.0","@types/react":"^19.2.0","@types/react-dom":"^19.2.0","@vitejs/plugin-react":"^5.0.4","jsdom":"^27.0.0","react":"^19.2.0","react-dom":"^19.2.0","rimraf":"^6.0.1","rollup":"^4.54.0","rollup-plugin-banner2":"^1.3.1","size-limit":"^12.0.0","tslib":"^2.8.1","typescript":"^5.9.0","virtua":"^0.48.2","vite":"^7.3.0","vitest":"^4.0.16"},"keywords":["react","virtual","virtualization","list","scroll","monaco","innerHTML","performance","portal"],"repository":{"type":"git","url":"git+https://github.com/ahmedbanihanibh/mesk.git"},"homepage":"https://github.com/ahmedbanihanibh/mesk#readme","bugs":{"url":"https://github.com/ahmedbanihanibh/mesk/issues"},"author":{"name":"Ahmed Bani Hani","email":"ahmedbanihani@example.com"},"license":"MIT","dependencies":{"dompurify":"^3.3.1"},"gitHead":"ab17a276f7f0664ebde17da2943fc26527257ad3","_id":"@ahmedbanihani/mesk@0.1.0","_nodeVersion":"22.21.1","_npmVersion":"11.7.0","dist":{"integrity":"sha512-OFTlqvoYPHEH0zCHqQ3bGDYHVwD1EtI8DdejGiGpxet5yUD7k4H9k/lustQmAycVF/M/U6bpQvT7SXpgUh3A+A==","shasum":"835d8bf0b9d285167795d7c319528fcfe0fa8ca4","tarball":"https://registry.npmjs.org/@ahmedbanihani/mesk/-/mesk-0.1.0.tgz","fileCount":25,"unpackedSize":378432,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBcVO7/MP9QJvwXVwhPYh1IL7AYJD154hABSguPDnxJvAiEA2b0tmn3wuU+ofr3VROuDn2TVGvrk51fZU40/bMbmuxI="}]},"_npmUser":{"name":"ahmedbanihani","email":"awsahmedbanihani@mail.com"},"directories":{},"maintainers":[{"name":"ahmedbanihani","email":"awsahmedbanihani@mail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mesk_0.1.0_1771973886269_0.6616848529725767"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-24T22:58:06.195Z","0.1.0":"2026-02-24T22:58:06.405Z","modified":"2026-02-24T22:58:06.620Z"},"maintainers":[{"name":"ahmedbanihani","email":"awsahmedbanihani@mail.com"}],"description":"Mesk — virtualization for React — innerHTML + portals + RAF scroll + event delegation. Standalone engine, zero dependencies.","homepage":"https://github.com/ahmedbanihanibh/mesk#readme","keywords":["react","virtual","virtualization","list","scroll","monaco","innerHTML","performance","portal"],"repository":{"type":"git","url":"git+https://github.com/ahmedbanihanibh/mesk.git"},"author":{"name":"Ahmed Bani Hani","email":"ahmedbanihani@example.com"},"bugs":{"url":"https://github.com/ahmedbanihanibh/mesk/issues"},"license":"MIT","readme":"# mesk\n\n**Monaco-grade innerHTML virtualization + selective React portals for heavy React lists**\n\nZero re-renders during scroll. The only virtualization library where React Scan shows nothing.\n\n```bash\nnpm install mesk\n```\n\n## Why mesk?\n\nReact virtual lists (virtua, react-window, @tanstack/virtual) all render items as React components. With heavy rows (shadcn menus, Radix dropdowns, Lucide icons), this means **hundreds of component mount/unmount cycles per scroll frame**.\n\nmesk uses the same technique VS Code's Monaco editor uses: **innerHTML for structure, React portals for interactivity**. Zero React components in the scroll path.\n\n## Quick Start\n\n```tsx\nimport { MeskList } from \"mesk\";\n\nconst items = Array.from({ length: 100_000 }, (_, i) => ({\n  id: i,\n  name: `Item ${i}`,\n}));\n\nfunction App() {\n  return (\n    <div style={{ height: \"100vh\" }}>\n      <MeskList\n        data={items}\n        renderHTML={(index, item) => `\n          <div style=\"padding: 8px 16px; border-bottom: 1px solid #27272a\">\n            <span style=\"color: #60a5fa\">#${item.id}</span>\n            <span style=\"margin-left: 8px\">${item.name}</span>\n          </div>\n        `}\n        onRowClick={(index) => console.log(\"Clicked\", index)}\n      />\n    </div>\n  );\n}\n```\n\n## Interactive Content with Portals\n\nNeed a button in a row? Use `renderPortal`:\n\n```tsx\nimport { createPortal } from \"react-dom\";\n\n<MeskList\n  data={items}\n  renderHTML={(index, item) => `\n    <div style=\"display: flex; padding: 8px\">\n      <span>${item.name}</span>\n      <div data-portal-slot style=\"margin-left: auto\"></div>\n    </div>\n  `}\n  renderPortal={(index, element, item) => {\n    const slot = element.querySelector(\"[data-portal-slot]\");\n    if (!slot) return null;\n    return createPortal(\n      <button onClick={() => handleDelete(item.id)}>Delete</button>,\n      slot\n    );\n  }}\n/>\n```\n\n## Key Features\n\n| Feature | How it works |\n|---------|-------------|\n| **innerHTML Fast-Path** | Rows created via `innerHTML` — one native DOM parse instead of hundreds of `React.createElement` calls |\n| **React Portals** | Mount React components only where needed — interactive buttons, dropdowns, forms |\n| **Event Delegation** | One `click` listener on the container instead of N per row. Resolves via DOM traversal |\n| **RAF Scroll Coalescing** | Multiple scroll events per frame → single DOM update. No redundant reconciliation |\n| **DOM Recycling** | Kept nodes untouched, only edges add/remove. Full rebuild only when overlap drops below 50% |\n| **Zero Re-renders** | React Scan shows nothing during scroll — items are HTML strings, not React components |\n\n## vs Other Libraries\n\n| Library | Approach | Re-renders on scroll? |\n|---------|----------|----------------------|\n| **virtua** | React components per row | Yes — mount/unmount on range change |\n| **react-window** | React components per row | Yes — same |\n| **@tanstack/virtual** | React components per row | Yes — same |\n| **mesk** | innerHTML + portals | **No** |\n\n## API Overview\n\n### MeskList Props\n\n| Prop | Type | Description |\n|------|------|-------------|\n| `data` | `ArrayLike<T>` | Data items array |\n| `renderHTML` | `(index, data) => string` | Returns HTML for each item (required) |\n| `renderPortal` | `(index, element, data) => Portal \\| null` | Mounts React into DOM slots (optional) |\n| `bufferSize` | `number` | Extra pixels before/after viewport (default: 200) |\n| `itemSize` | `number` | Hint for unmeasured items (auto-estimated if omitted) |\n| `horizontal` | `boolean` | Horizontal scroll mode |\n| `gpuAccelerate` | `boolean` | Use `transform` instead of `top` |\n| `onRowClick` | `(index, event) => void` | Delegated click handler |\n| `onRowContextMenu` | `(index, event) => void` | Delegated right-click handler |\n| `onScroll` | `(offset) => void` | Scroll callback |\n| `onScrollEnd` | `() => void` | Scroll stop callback |\n\n### Imperative Handle\n\n```tsx\nconst ref = useRef<MeskListHandle>(null);\n\nref.current.scrollToIndex(500);\nref.current.scrollToIndex(500, { smooth: true, align: \"center\" });\nref.current.scrollTo(1000);\nref.current.scrollBy(100);\nref.current.forceRender();\n\n// Read-only:\nref.current.cache;        // CacheSnapshot for serialization\nref.current.scrollOffset; // current scrollTop\nref.current.scrollSize;   // current scrollHeight\nref.current.viewportSize; // current offsetHeight\n```\n\n## Architecture\n\nThe core engine (VirtualStore, ScrollObserver, SizeObserver) is framework-agnostic — no React dependencies in `src/engine/`. The React layer (MeskList, MeskVirtualizer, MeskWindowVirtualizer) is a thin adapter on top.\n\n**Standalone**: ~7.2 kB gzipped. Zero dependencies beyond React.\n\n## Examples\n\nSee [examples/](./examples/) for:\n- Simple text list\n- List with context menus via portals\n- Log viewer with action buttons\n\n## Security (XSS Protection)\n\n**NOTE**: mesk uses DOMPurify to automatically sanitize all HTML content passed to `renderHTML`, item icons, and custom renderer functions. This protection is built-in and active in all components:\n\n- `FastCommand`, `FastDropdownMenu`, `FastCombobox` – icon HTML is automatically sanitized\n- `MeskList`, `MeskVirtualizer`, `MeskWindowVirtualizer` – `renderHTML` output is automatically sanitized\n- `renderPortal` – React portals have React's built-in DOM escaping\n\n### What DOMPurify blocks\n- `<script>` tags and inline scripts\n- Event handlers (`onclick`, `onerror`, etc.)\n- `javascript:` and `data:` URLs\n- Unknown HTML tags/attributes\n\n### What remains safe\n- Safe SVG icons (basic paths, circles, rects, etc.)\n- `class` and `style` attributes for styling\n- Standard HTML elements (div, span, svg, etc.)\n\n### Recommended practices\n1. Only pass trusted HTML for icons and custom `renderHTML` functions\n2. If displaying untrusted user input, use the library's automatic sanitization (no additional code needed)\n3. For additional protection, set a strict Content-Security-Policy:\n   ```html\n   <meta http-equiv=\"Content-Security-Policy\" content=\"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';\">\n   ```\n\n## License\n\nMIT\n\n---\n\n**Made with ❤️ by the mesk team**\n","readmeFilename":"README.md","_rev":"1-b5de8ad47df16346c8563815e4b1459f"}