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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 2025-12-29: v2.7.0

- Added event `embedSize`

## 2025-10-22: v2.6.0

- Added support for new locales: Danish (`da`), Finnish (`fi`), Filipino (`fil`), French Canadian (`fr-CA`), Hindi (`hi`), Indonesian (`id`), Malay (`ms`), Norwegian Bokmål (`nb`), Traditional Chinese Hong Kong (`zh-HK`), and Traditional Chinese Taiwan (`zh-TW`)
Expand Down
1 change: 1 addition & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module.exports = config => {
FLAT_EMBED_QUARTET_SCORE: process.env.FLAT_EMBED_QUARTET_SCORE,
FLAT_EMBED_PRIVATE_LINK_SCORE: process.env.FLAT_EMBED_PRIVATE_LINK_SCORE,
FLAT_EMBED_PRIVATE_LINK_SHARING_KEY: process.env.FLAT_EMBED_PRIVATE_LINK_SHARING_KEY,
FLAT_EMBED_NEW_DISPLAY: process.env.FLAT_EMBED_NEW_DISPLAY,
},
},
reporters: ['mocha'],
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,13 @@
"scripts": {
"clean": "rm build/*",
"build": "tsc && vite build",
"test": "tsc && pnpm run biome:check && npm run test:karma",
"test": "tsc && pnpm run biome:check && npm run test:karma:all-displays",
"biome:check": "biome check .",
"biome:fix": "biome check --write .",
"test:karma": "karma start --single-run",
"test:karma:old-display": "karma start --single-run",
"test:karma:new-display": "FLAT_EMBED_NEW_DISPLAY=true karma start --single-run",
"test:karma:all-displays": "npm run test:karma:old-display && npm run test:karma:new-display",
"test:karma-watch": "karma start --single-run=false --auto-watch",
"prepare": "husky",
"generate:docs": "npx tsx scripts/update-version-in-docs.ts && npx tsx scripts/generate-api-docs.ts"
Expand Down
9 changes: 9 additions & 0 deletions src/types/embedSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Embed size information
*/
export interface EmbedSize {
/** Total embed height in pixels */
height: number;
/** Total embed width in pixels */
width: number;
}
1 change: 1 addition & 0 deletions src/types/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const EVENTS_NAMES = [
'stop',
'playbackPosition',
'restrictedFeatureAttempt',
'embedSize',
] as const;

/**
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
export * from './cursorPosition';
export * from './embedMessage';
export * from './embedParameters';
export * from './embedSize';
export * from './events';
export * from './measure';
export * from './metronome';
Expand Down
72 changes: 72 additions & 0 deletions test/integration/embed-integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ describe('Integration - Embed', () => {
}
});

const USE_NEW_DISPLAY = window.__karma__.config.env.FLAT_EMBED_NEW_DISPLAY === 'true';
console.log('[embed-integration] USE_NEW_DISPLAY:', USE_NEW_DISPLAY);

function createEmbedForScoreId(score, embedParams = {}) {
const container = document.createElement('div');
document.body.appendChild(container);
Expand All @@ -38,6 +41,7 @@ describe('Integration - Embed', () => {
embedParams: {
...embedParams,
appId: APP_ID,
...(USE_NEW_DISPLAY && { newDisplay: true }),
},
});

Expand Down Expand Up @@ -733,6 +737,74 @@ describe('Integration - Embed', () => {
});
});

describe('Events - embedSize', () => {
// Note: New display (adagio-display) currently reports viewport dimensions via contentRect,
// not actual content dimensions. Height assertions are skipped for new display until
// adagio-display is updated to report actual content height.
it('should receive embedSize event with reasonable dimensions', done => {
const { embed, container } = createEmbedForScoreId(PUBLIC_SCORE);
container.style.width = '800px';

// Wait for score to be loaded before subscribing to embedSize
// This ensures the score has fully rendered before we check dimensions
embed.on('scoreLoaded', () => {
// Add a small delay to allow layout to stabilize (especially for new display)
setTimeout(() => {
let eventCount = 0;
embed.on('embedSize', data => {
eventCount++;
console.log(`[embedSize test] event #${eventCount}: height=${data.height}px, width=${data.width}px`);

assert.ok(typeof data.height === 'number', 'height is number');
assert.ok(typeof data.width === 'number', 'width is number');
// Height should be meaningful for a real score (not just iframe chrome)
// Skip height check for new display - it returns viewport height, not content height
if (!USE_NEW_DISPLAY) {
assert.ok(data.height > 500, `height should be > 500px, got ${data.height}px`);
}
assert.ok(data.width > 0, 'width > 0');
done();
});
}, 500);
});
});

// Skip resize test for new display - contentRect doesn't update on container resize
(USE_NEW_DISPLAY ? it.skip : it)('should emit new embedSize event on container resize', done => {
const { embed, container } = createEmbedForScoreId(PUBLIC_SCORE);
container.style.width = '800px';

// Wait for score to be loaded before subscribing to embedSize
embed.on('scoreLoaded', () => {
// Add a small delay to allow layout to stabilize (especially for new display)
setTimeout(() => {
let eventCount = 0;
let firstWidth = 0;

embed.on('embedSize', data => {
eventCount++;
console.log(`[embedSize resize test] event #${eventCount}: height=${data.height}px, width=${data.width}px`);

if (eventCount === 1) {
// First event after load
firstWidth = data.width;
assert.ok(data.height > 500, `initial height > 500px, got ${data.height}px`);
// Trigger resize after first event
setTimeout(() => {
console.log('[embedSize resize test] triggering resize to 500px');
container.style.width = '500px';
}, 200);
} else if (eventCount === 2) {
// Second event after resize
assert.ok(data.width < firstWidth, `width should decrease after resize: ${data.width} < ${firstWidth}`);
done();
}
});
}, 500);
});
});
});

describe('Parts display', () => {
it('should get all the parts by default', async () => {
const { embed } = createEmbedForScoreId(QUARTET_SCORE);
Expand Down
203 changes: 203 additions & 0 deletions test/manual/test-embedSize.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test embedSize Event</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
margin: 20px;
}
#embed-container {
width: 100%;
border: 1px solid #ccc;
}
#embed-container iframe {
display: block; /* Remove inline spacing */
}
#log {
margin-top: 20px;
padding: 10px;
background: #f5f5f5;
border-radius: 4px;
font-family: monospace;
white-space: pre-wrap;
max-height: 200px;
overflow-y: auto;
}
.controls {
margin-bottom: 15px;
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.controls-group {
border: 1px solid #ddd;
padding: 8px 12px;
border-radius: 4px;
display: flex;
gap: 8px;
align-items: center;
}
.controls-group label {
font-weight: bold;
font-size: 12px;
color: #666;
}
button {
padding: 8px 16px;
}
button.active {
background: #4CAF50;
color: white;
}
.size-info {
margin-bottom: 15px;
padding: 10px;
background: #e8f4e8;
border-radius: 4px;
font-family: monospace;
}
.mode-info {
margin-bottom: 15px;
padding: 10px;
background: #e8e8f4;
border-radius: 4px;
font-family: monospace;
}
</style>
</head>
<body>
<h1>embedSize Event Test</h1>

<div class="controls">
<div class="controls-group">
<label>Actions:</label>
<button onclick="clearLog()">Clear Log</button>
<button onclick="location.reload()">Reload</button>
</div>
<div class="controls-group">
<label>Width:</label>
<button onclick="resizeContainer(600)">600px</button>
<button onclick="resizeContainer(800)">800px</button>
<button onclick="resizeContainer('100%')">100%</button>
</div>
<div class="controls-group">
<label>Layout:</label>
<button onclick="switchMode('responsive')" id="btn-responsive">Responsive</button>
<button onclick="switchMode('page')" id="btn-page">Page</button>
<button onclick="switchMode('track')" id="btn-track">Track</button>
</div>
<div class="controls-group">
<label>Display:</label>
<button onclick="switchDisplay(false)" id="btn-old-display">Old</button>
<button onclick="switchDisplay(true)" id="btn-new-display">New</button>
</div>
</div>

<div class="mode-info" id="mode-info">
Mode: loading...
</div>

<div class="size-info" id="size-info">
Iframe size: waiting for embedSize event...
</div>

<div id="embed-container"></div>

<h3>Event Log:</h3>
<div id="log"></div>

<script src="../../dist/flat-embed.mjs" type="module"></script>
<script type="module">
import Embed from '../../dist/flat-embed.mjs';

const log = document.getElementById('log');
const sizeInfo = document.getElementById('size-info');
const modeInfo = document.getElementById('mode-info');
const container = document.getElementById('embed-container');

// Get params from URL
const urlParams = new URLSearchParams(window.location.search);
const layout = urlParams.get('layout') || 'responsive';
const newDisplay = urlParams.get('newDisplay') === 'true';

// Update mode info
modeInfo.textContent = `Layout: ${layout} | Display: ${newDisplay ? 'new' : 'old'}`;

// Highlight active buttons
document.getElementById(`btn-${layout}`)?.classList.add('active');
document.getElementById(newDisplay ? 'btn-new-display' : 'btn-old-display')?.classList.add('active');

function addLog(message) {
const time = new Date().toLocaleTimeString();
log.textContent += `[${time}] ${message}\n`;
log.scrollTop = log.scrollHeight;
}

window.clearLog = function() {
log.textContent = '';
};

window.resizeContainer = function(width) {
const widthStr = typeof width === 'number' ? `${width}px` : width;
container.style.width = widthStr;
addLog(`Container width set to ${widthStr}`);
};

window.switchMode = function(newLayout) {
const params = new URLSearchParams(window.location.search);
params.set('layout', newLayout);
window.location.search = params.toString();
};

window.switchDisplay = function(useNew) {
const params = new URLSearchParams(window.location.search);
params.set('newDisplay', useNew ? 'true' : 'false');
window.location.search = params.toString();
};

addLog(`Creating embed with layout=${layout}, newDisplay=${newDisplay}...`);

const embedParams = {
appId: '58f0ee6053674e68c81e5646',
controlsFloating: false,
layout: layout,
};

if (newDisplay) {
embedParams['newDisplay'] = true;
}

const embed = new Embed('embed-container', {
score: '56ae21579a127715a02901a6',
// score: '655421147ae4155c831a5dbe',
// baseUrl: 'https://flat.ovh:3000/embed',
embedParams: embedParams
});

// Listen for ready
embed.ready().then(() => {
addLog('Embed ready!');
});

// Listen for scoreLoaded
embed.on('scoreLoaded', () => {
addLog('scoreLoaded event received');
});

// Listen for embedSize and auto-resize iframe height
embed.on('embedSize', (data) => {
addLog(`embedSize: ${data.width}px × ${data.height}px`);

// Set iframe height to match content - no scrollbars!
embed.element.style.height = `${data.height}px`;

sizeInfo.textContent = `Iframe size: ${data.width}px × ${data.height}px (auto-adjusted)`;
});

addLog('Event listeners registered, waiting for events...');
</script>
</body>
</html>