overflow-manager
v1.0.2
Published
Detects fixed-height containers that overflow and redistributes excess children to user-defined targets. Built for Astro + Puppeteer PDF pipelines.
Maintainers
Readme
@victorarangodev/overflow-manager
Detects fixed-height containers that overflow and redistributes excess child elements to user-defined targets. Built for Astro → Puppeteer → PDF pipelines.
npm install @victorarangodev/overflow-managerHow it works
The library runs inside the browser (Chromium via Puppeteer) using real getBoundingClientRect() measurements after all CSS, fonts, and images have loaded. It iterates in passes until no container overflows, then signals Puppeteer that the PDF can be generated.
Astro build → HTML + script tag
↓
Puppeteer opens HTML
↓
overflow-manager runs in Chromium:
1. Measures each [data-overflow-container]
2. Moves overflowing children to [data-overflow-target]
3. Reveals [data-overflow-page] wrappers that received content
4. Repeats until stable
↓
window.overflowManager.done === true
↓
Puppeteer generates PDFHTML attributes
| Attribute | On | Required | Description |
|---|---|---|---|
| data-overflow-container | source div | ✅ | Marks a container that can overflow |
| data-overflow-id | source div | ✅ | Unique ID linking source → target |
| data-overflow-height | source div | No | Max height in px. Overrides CSS height |
| data-overflow-target | target div | ✅ | Receives overflowing children |
| data-overflow-page | page wrapper | No | Hidden initially; revealed only if its target receives content |
Usage in Astro
Basic — single overflow page
---
// [id].astro
---
<!-- Page 1 — always visible -->
<Layout>
<Section height="450px" />
<Section height="450px">
<div
data-overflow-container
data-overflow-id="body-p1"
data-overflow-height="700"
style="height: 700px;"
>
<p>Item 1</p>
<p>Item 2</p>
<!-- more items... -->
</div>
</Section>
<Section height="450px" />
</Layout>
<!-- Page 2 — hidden by default, revealed only if body-p1 overflows -->
<div data-overflow-page="body-p1" style="display: none">
<Layout>
<Section height="950px">
<div
data-overflow-target="body-p1"
data-overflow-container
data-overflow-id="body-p2"
data-overflow-height="700"
style="height: 700px;"
>
</div>
</Section>
</Layout>
</div>
<script src="/overflow-manager.js"></script>Chained pages — unlimited overflow
<!-- Page 1 -->
<Layout>
<div data-overflow-container data-overflow-id="p1"
data-overflow-height="700" style="height:700px">
<!-- content -->
</div>
</Layout>
<!-- Page 2 -->
<div data-overflow-page="p1" style="display:none">
<Layout>
<div data-overflow-target="p1"
data-overflow-container data-overflow-id="p2"
data-overflow-height="700" style="height:700px">
</div>
</Layout>
</div>
<!-- Page 3 -->
<div data-overflow-page="p2" style="display:none">
<Layout>
<div data-overflow-target="p2"
data-overflow-container data-overflow-id="p3"
data-overflow-height="700" style="height:700px">
</div>
</Layout>
</div>Including the script
Option A — public folder (recommended)
cp node_modules/@victorarangodev/overflow-manager/dist/overflow-manager.browser.global.js \
public/overflow-manager.js<script src="/overflow-manager.js"></script>Option B — ES module
<script>
import { OverflowManager } from '@victorarangodev/overflow-manager';
const manager = new OverflowManager({ settleDelay: 50 });
window.overflowManager = manager;
manager.runAsync();
</script>Puppeteer integration
const page = await browser.newPage();
await page.goto(`file://${htmlPath}`, { waitUntil: 'networkidle0' });
await page.waitForFunction(
() => window.overflowManager?.done === true,
{ timeout: 15_000 }
);
await page.pdf({ path: outputPath, printBackground: true });Always use
waitUntil: 'networkidle0'— ensures web fonts are loaded before measurement.
API
new OverflowManager(options?)
| Option | Type | Default | Description |
|---|---|---|---|
| settleDelay | number | 0 | ms between async passes (0 = double rAF) |
| maxPasses | number | 100 | Safety cap on iterations |
| onPass | fn | — | Called after each pass with (pass, moved) |
| onDone | fn | — | Called when processing finishes |
Methods
| Method | Returns | Description |
|---|---|---|
| .run() | OverflowManagerResult | Synchronous execution |
| .runAsync() | Promise<Result> | Async execution — recommended |
Result
interface OverflowManagerResult {
passes: number; // passes executed
totalMoved: number; // total elements redistributed
done: boolean; // always true
}DOM event
document.addEventListener('overflow-manager:done', (e) => {
console.log(e.detail); // OverflowManagerResult
});Important notes
- No
overflow: hiddenon the source container — children must visually protrude forgetBoundingClientRect()to detect them. - Direct children only — one child per logical unit (paragraph, card, row).
- Web fonts — use
waitUntil: 'networkidle0'or setsettleDelay: 100. data-overflow-page— place it outside and after its Layout, in DOM order.
License
MIT © victorarangodev
