Skip to content
Open
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
49 changes: 49 additions & 0 deletions packages/superdoc/src/SuperDoc.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2625,4 +2625,53 @@ describe('SuperDoc.vue', () => {
const styleVars = wrapper.vm.superdocStyleVars;
expect(styleVars['--sd-comments-highlight-hover']).toBe('#abcdef88');
});

it('does not emit layout-change before isReady', async () => {
const superdocStub = createSuperdocStub();
superdocStoreStub.isReady.value = false;

const wrapper = await mountComponent(superdocStub);
await nextTick();

// Set up container width measurement
const rootEl = wrapper.find('.superdoc').element;
const parentEl = rootEl.parentElement;
Object.defineProperty(rootEl, 'clientWidth', { configurable: true, value: 1200 });
if (parentEl) Object.defineProperty(parentEl, 'clientWidth', { configurable: true, value: 1200 });

// Trigger recalculation while not ready
wrapper.vm.recalculateCompactCommentsMode();
await nextTick();

// Should not emit before isReady
const layoutChangeCalls = superdocStub.emit.mock.calls.filter(([name]) => name === 'layout-change');
expect(layoutChangeCalls.length).toBe(0);
});

it('includes documentWidth and fitZoom in layout-change payload when ready', async () => {
const superdocStub = createSuperdocStub();
superdocStoreStub.isReady.value = true;

const wrapper = await mountComponent(superdocStub);
await nextTick();

// Set up container width measurement
const rootEl = wrapper.find('.superdoc').element;
const parentEl = rootEl.parentElement;
Object.defineProperty(rootEl, 'clientWidth', { configurable: true, value: 1200 });
if (parentEl) Object.defineProperty(parentEl, 'clientWidth', { configurable: true, value: 1200 });

// Trigger recalculation
wrapper.vm.recalculateCompactCommentsMode();
await nextTick();

const layoutChangeCalls = superdocStub.emit.mock.calls.filter(([name]) => name === 'layout-change');
if (layoutChangeCalls.length > 0) {
const payload = layoutChangeCalls[layoutChangeCalls.length - 1][1];
expect(payload).toHaveProperty('containerWidth');
expect(payload).toHaveProperty('documentWidth');
expect(payload).toHaveProperty('fitZoom');
expect(typeof payload.fitZoom).toBe('number');
}
});
});
37 changes: 37 additions & 0 deletions packages/superdoc/src/SuperDoc.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { collectTouchedTrackedChangeIds } from './helpers/collect-touched-tracke
import SurfaceHost from './components/surfaces/SurfaceHost.vue';
import {
DEFAULT_COMMENTS_DISPLAY_MODE,
DEFAULT_DOCUMENT_VISIBLE_MIN_WIDTH_PX,
RIGHT_CLICK_COMMENT_SUPPRESS_MS,
VALID_COMMENTS_DISPLAY_MODES,
} from './helpers/comment-small-screen.js';
Expand Down Expand Up @@ -1321,6 +1322,42 @@ watch(showCommentsSidebar, (value) => {
proxy.$superdoc.broadcastSidebarToggle(value);
});

// Emit layout-change event when container width changes.
// Capture base document width after layout resolves to avoid stale measurements.
let baseDocumentWidth = null;
let lastEmittedFitZoom = null;

const emitLayoutChange = () => {
const containerWidth = superdocContainerWidth.value;
Comment thread
mattConnHarbour marked this conversation as resolved.
if (!proxy.$superdoc || containerWidth <= 0) return;

// Wait for document layout to resolve before capturing base width
if (baseDocumentWidth === null) {
if (!isReady.value) return;
const docEl = superdocRoot.value?.querySelector('.superdoc__document');
const measured = docEl?.clientWidth || docEl?.getBoundingClientRect?.().width || 0;
baseDocumentWidth = measured > 0 ? measured : DEFAULT_DOCUMENT_VISIBLE_MIN_WIDTH_PX;
Comment thread
mattConnHarbour marked this conversation as resolved.
}

const rawFitZoom = (containerWidth / baseDocumentWidth) * 100;
const fitZoom = Math.round(rawFitZoom);

// Only emit if fitZoom changed
if (fitZoom === lastEmittedFitZoom) return;
lastEmittedFitZoom = fitZoom;

proxy.$superdoc.emit('layout-change', {
containerWidth,
documentWidth: baseDocumentWidth,
fitZoom,
});
};

watch(superdocContainerWidth, emitLayoutChange);
watch(isReady, (ready) => {
if (ready) emitLayoutChange();
});

/**
* Scroll the page to a given commentId
*
Expand Down
2 changes: 2 additions & 0 deletions packages/superdoc/src/core/SuperDoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import type {
SuperDocEditorPayload,
SuperDocExceptionPayload,
SuperDocExceptionStorePayload,
SuperDocLayoutChangePayload,
SuperDocLockedPayload,
SuperDocReadyPayload,
SuperDocState,
Expand Down Expand Up @@ -151,6 +152,7 @@ interface SuperDocEventMap {
'whiteboard:enabled': [boolean];
'whiteboard:tool': [string];
exception: [SuperDocExceptionPayload];
'layout-change': [SuperDocLayoutChangePayload];
}
// Notes on the event map above:
//
Expand Down
13 changes: 13 additions & 0 deletions packages/superdoc/src/core/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,19 @@ export type SuperDocExceptionPayload =
| SuperDocExceptionRestorePayload
| SuperDocExceptionEditorPayload;

/**
* Payload emitted when container dimensions change. Useful for implementing
* fit-to-container zoom behavior.
*/
export interface SuperDocLayoutChangePayload {
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
/** Current container width in pixels. */
containerWidth: number;
/** Measured document/page width in pixels. */
documentWidth: number;
/** Calculated zoom to fit document in available width (unclamped). User should clamp to their preferred min/max. */
fitZoom: number;
}

export interface Config {
/** The ID of the SuperDoc. */
superdocId?: string;
Expand Down
12 changes: 12 additions & 0 deletions packages/superdoc/src/dev/components/SuperdocDev.vue
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,18 @@ const init = async () => {
currentZoom.value = zoom;
});

superdoc.value?.on('layout-change', ({ fitZoom }) => {
// Clamp zoom between your min/max bounds
console.log('[layout-change]', fitZoom);
if (fitZoom < 50) {
superdoc.value.setZoom(50);
} else if (fitZoom > 200) {
superdoc.value.setZoom(200);
} else {
superdoc.value.setZoom(fitZoom);
}
});

window.superdoc = superdoc.value;

// const ydoc = superdoc.value.ydoc;
Expand Down
Loading