Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ jobs:
cp docs/child.html _site/child.html
cp docs/child-simple.html _site/child-simple.html
cp docs/child-chart.html _site/child-chart.html
cp docs/embed.html _site/embed.html
cp dist/index.iife.js _site/iframe-flight.iife.js
cp dist/index.js _site/iframe-flight.esm.js

Expand Down
136 changes: 136 additions & 0 deletions docs/embed.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iframe-flight × Vega-Lite</title>
<script type="importmap">
{"imports": {"iframe-flight": "https://ihatexcel.github.io/iframe-flight/iframe-flight.esm.js"}}
</script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; }
body { font-family: system-ui, -apple-system, sans-serif; padding: 2rem; background: #fafafa; color: #18181b; }
h1 { font-size: 1rem; font-weight: 600; margin-bottom: 1rem; }
.bar { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
button {
padding: 6px 16px; border-radius: 6px; border: 1px solid #e4e4e7;
background: #18181b; color: #fff; font-size: 13px; cursor: pointer;
}
button:disabled { opacity: 0.35; cursor: not-allowed; }
#status { font-size: 12px; font-family: monospace; color: #71717a; }
iframe {
display: block; width: 100%; height: 480px;
border: 1px solid #e4e4e7; border-radius: 10px;
background: #fff;
}
</style>
</head>
<body>

<h1>iframe-flight × Vega-Lite — single file, no install</h1>

<div class="bar">
<button id="btn" disabled>New snapshot</button>
<span id="status">Connecting…</span>
</div>

<iframe id="frame"></iframe>

<script type="module">
import { ArrowParentEmitter, tableFromArrays, tableToIPC } from 'iframe-flight';

// ── Child iframe HTML — injected via srcdoc, no separate file needed ──────
const CHILD = `<!DOCTYPE html>
<html><head><meta charset="UTF-8">
<script type="importmap">
{"imports":{"iframe-flight":"https://ihatexcel.github.io/iframe-flight/iframe-flight.esm.js"}}
<\/script>
<style>body{margin:0;padding:12px 16px;font-family:system-ui}<\/style>
</head><body>
<div id="c"></div>
<script type="module">
import { ArrowChildReceiver } from 'iframe-flight';
import vegaEmbed from 'https://cdn.jsdelivr.net/npm/vega-embed@6/+esm';

new ArrowChildReceiver({ allowedOrigins:['*'] }).onData(async result => {
const rows = [];
const t = result.table;
for (let i = 0; i < t.numRows; i++) {
const row = {};
t.schema.fields.forEach((f, ci) => { row[f.name] = t.getChildAt(ci).get(i); });
rows.push(row);
}
await vegaEmbed('#c', {
width: 'container',
data: { values: rows },
mark: { type: 'bar', cornerRadiusEnd: 5 },
encoding: {
y: { field: 'product', type: 'nominal', sort: '-x', title: null,
axis: { labelFontSize: 13 } },
x: { field: 'revenue', type: 'quantitative', title: 'Revenue ($M)',
axis: { gridColor: '#f4f4f5', labelColor: '#71717a', titleColor: '#71717a' } },
color: { field: 'growth', type: 'quantitative', title: 'Growth %',
scale: { scheme: 'redyellowgreen', domain: [-25, 25] },
legend: { orient: 'right', format: '+.0f', labelFontSize: 10, titleFontSize: 10 } },
tooltip: [
{ field: 'product' },
{ field: 'revenue', title: 'Revenue ($M)', format: '.1f' },
{ field: 'growth', title: 'Growth %', format: '+.1f' },
{ field: 'units', title: 'Units', format: ',' }
]
},
config: { view: { stroke: null }, background: '#fff',
font: 'system-ui, -apple-system, sans-serif' }
}, { actions: false });
});
<\/script>
</body></html>`;

// ── Data ──────────────────────────────────────────────────────────────────
const PRODUCTS = ['Aurora','Beacon','Catalyst','Dynamo','Ember',
'Forge','Galaxy','Helix','Ignite','Jupiter'];

function snapshot() {
const p = PRODUCTS;
return tableToIPC(tableFromArrays({
product: p,
revenue: Float64Array.from(p.map(() => Math.random() * 90 + 10)),
growth: Float64Array.from(p.map(() => Math.random() * 50 - 25)),
units: Int32Array.from(p.map(() => Math.floor(Math.random() * 45000 + 5000))),
}));
}

// ── Wire-up ───────────────────────────────────────────────────────────────
const frame = document.getElementById('frame');
const btn = document.getElementById('btn');
const status = document.getElementById('status');

frame.srcdoc = CHILD;

const emitter = new ArrowParentEmitter(frame, {
handshakeTimeout: 20000,
allowedOrigins: ['*'],
});

emitter
.onReady(() => {
btn.disabled = false;
push();
})
.onError(err => {
status.textContent = `❌ ${err.message}`;
});

async function push() {
btn.disabled = true;
status.textContent = 'Sending…';
const ack = await emitter.send(snapshot());
status.textContent = `${ack.rows} rows · ${ack.processingTime}ms`;
btn.disabled = false;
}

btn.addEventListener('click', push);
</script>

</body>
</html>
Loading