Skip to content
Closed
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
2 changes: 1 addition & 1 deletion apps/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@superdoc-dev/cli",
"version": "0.6.0",
"version": "0.7.0",
"type": "module",
"bin": {
"superdoc": "./dist/index.js"
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/platforms/cli-darwin-arm64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@superdoc-dev/cli-darwin-arm64",
"version": "0.6.0",
"version": "0.7.0",
"os": [
"darwin"
],
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/platforms/cli-darwin-x64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@superdoc-dev/cli-darwin-x64",
"version": "0.6.0",
"version": "0.7.0",
"os": [
"darwin"
],
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/platforms/cli-linux-arm64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@superdoc-dev/cli-linux-arm64",
"version": "0.6.0",
"version": "0.7.0",
"os": [
"linux"
],
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/platforms/cli-linux-x64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@superdoc-dev/cli-linux-x64",
"version": "0.6.0",
"version": "0.7.0",
"os": [
"linux"
],
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/platforms/cli-windows-x64/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@superdoc-dev/cli-windows-x64",
"version": "0.6.0",
"version": "0.7.0",
"os": [
"win32"
],
Expand Down
28 changes: 28 additions & 0 deletions packages/layout-engine/layout-engine/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,34 @@ describe('layoutDocument', () => {
expect(layout.columns).toMatchObject({ count: 2, gap: 20, withSeparator: true });
});

<<<<<<< HEAD
=======
it('preserves explicit column widths on page-level column metadata', () => {
const options: LayoutOptions = {
pageSize: { w: 600, h: 800 },
margins: { top: 40, right: 40, bottom: 40, left: 40 },
columns: { count: 2, gap: 20, widths: [100, 400], equalWidth: false, withSeparator: true },
};
const layout = layoutDocument([block], [makeMeasure([350, 350, 350])], options);

expect(layout.pages).toHaveLength(1);
expect(layout.pages[0].columns).toEqual({
count: 2,
gap: 20,
widths: [100, 400],
equalWidth: false,
withSeparator: true,
});
expect(layout.columns).toEqual({
count: 2,
gap: 20,
widths: [100, 400],
equalWidth: false,
withSeparator: true,
});
});

>>>>>>> origin/stable
it('does not set "page.columns" on single column layout', () => {
const options: LayoutOptions = {
pageSize: { w: 600, h: 800 },
Expand Down
8 changes: 8 additions & 0 deletions packages/layout-engine/layout-engine/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1099,7 +1099,11 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
}

if (activeColumns.count > 1) {
<<<<<<< HEAD
page.columns = { count: activeColumns.count, gap: activeColumns.gap, withSeparator: activeColumns.withSeparator };
=======
Comment on lines +1102 to +1104
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P0 Badge Resolve merge conflict markers before committing

This file still contains unresolved merge markers (<<<<<<<, =======, >>>>>>>), which makes the TypeScript source unparsable and blocks builds/tests for this package. Because these markers appear in committed source (not just comments), any CI job that compiles layout-engine will fail until the conflict is manually resolved and only one branch’s code remains.

Useful? React with 👍 / 👎.

page.columns = cloneColumnLayout(activeColumns);
>>>>>>> origin/stable
}

// Set vertical alignment from active section state
Expand Down Expand Up @@ -2604,10 +2608,14 @@ export function layoutDocument(blocks: FlowBlock[], measures: Measure[], options
// after processing sections. Page/region-specific column changes are encoded
// implicitly via fragment positions. Consumers should not assume this is
// a static document-wide value.
<<<<<<< HEAD
columns:
activeColumns.count > 1
? { count: activeColumns.count, gap: activeColumns.gap, withSeparator: activeColumns.withSeparator }
: undefined,
=======
columns: activeColumns.count > 1 ? cloneColumnLayout(activeColumns) : undefined,
>>>>>>> origin/stable
};
}

Expand Down
20 changes: 20 additions & 0 deletions packages/layout-engine/layout-engine/src/section-props.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,4 +93,24 @@ describe('computeNextSectionPropsAtBreak', () => {
expect(map.get(0)?.columns).toEqual({ count: 2, gap: 48, withSeparator: true });
expect(map.get(2)?.columns).toEqual({ count: 2, gap: 48, withSeparator: true });
});
<<<<<<< HEAD
=======

it('preserves explicit column widths and equalWidth in snapshots', () => {
const sourceColumns = { count: 2, gap: 48, widths: [120, 360], equalWidth: false, withSeparator: true };
const blocks: FlowBlock[] = [sectionBreak({ id: 'sb-0', columns: sourceColumns })];
const map = computeNextSectionPropsAtBreak(blocks);
const snapshot = map.get(0);

expect(snapshot?.columns).toEqual({
count: 2,
gap: 48,
widths: [120, 360],
equalWidth: false,
withSeparator: true,
});
expect(snapshot?.columns).not.toBe(sourceColumns);
expect(snapshot?.columns?.widths).not.toBe(sourceColumns.widths);
});
>>>>>>> origin/stable
});
17 changes: 17 additions & 0 deletions packages/layout-engine/layout-engine/src/section-props.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import type { ColumnLayout, FlowBlock, SectionVerticalAlign } from '@superdoc/contracts';
<<<<<<< HEAD
=======
import { cloneColumnLayout } from './column-utils.js';
>>>>>>> origin/stable

/**
* Section-level formatting properties that control page layout.
Expand All @@ -21,6 +25,11 @@ export type SectionProps = {
vAlign?: SectionVerticalAlign;
};

const snapshotColumns = (columns?: ColumnLayout): ColumnLayout | undefined => {
if (!columns) return undefined;
return cloneColumnLayout(columns);
};

/**
* Extracts section properties from a section break block if any are present.
* Returns null if the block has no section-related properties.
Expand Down Expand Up @@ -59,7 +68,11 @@ const _snapshotSectionProps = (block: FlowBlock): SectionProps | null => {
}
if (block.columns) {
hasProps = true;
<<<<<<< HEAD
props.columns = { count: block.columns.count, gap: block.columns.gap, withSeparator: block.columns.withSeparator };
=======
props.columns = snapshotColumns(block.columns);
>>>>>>> origin/stable
}
if (block.orientation) {
hasProps = true;
Expand Down Expand Up @@ -135,11 +148,15 @@ export function computeNextSectionPropsAtBreak(blocks: FlowBlock[]): Map<number,
props.pageSize = { w: source.pageSize.w, h: source.pageSize.h };
}
if (source.columns) {
<<<<<<< HEAD
props.columns = {
count: source.columns.count,
gap: source.columns.gap,
withSeparator: source.columns.withSeparator,
};
=======
props.columns = snapshotColumns(source.columns);
>>>>>>> origin/stable
}
if (source.orientation) {
props.orientation = source.orientation;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,22 @@ describe('DomPainter renderColumnSeparators', () => {
expect(seps.map((s) => s.style.left)).toEqual(['296px', '520px']);
});

<<<<<<< HEAD
=======
it('uses explicit column widths when drawing separators for page.columns', () => {
const page = buildPage({
columns: { count: 2, gap: 48, widths: [200, 952], equalWidth: false, withSeparator: true },
});
paintOnce(buildLayout(page), mount);

const seps = querySeparators(mount);
expect(seps).toHaveLength(1);
// contentWidth=624, availableWidth=576. Explicit widths [200, 952] are
// normalized to [100, 476], so the separator belongs at 96 + 100 + 24 = 220.
expect(seps[0].style.left).toBe('220px');
});

>>>>>>> origin/stable
it('renders nothing when withSeparator is false', () => {
const page = buildPage({ columns: { count: 2, gap: 48, withSeparator: false } });
paintOnce(buildLayout(page), mount);
Expand Down Expand Up @@ -211,5 +227,27 @@ describe('DomPainter renderColumnSeparators', () => {
expect(seps[0].style.top).toBe('96px');
expect(seps[0].style.height).toBe('864px');
});
<<<<<<< HEAD
=======

it('uses explicit column widths when drawing separators for columnRegions', () => {
const page = buildPage({
columnRegions: [
{
yStart: 96,
yEnd: 500,
columns: { count: 2, gap: 48, widths: [200, 952], equalWidth: false, withSeparator: true },
},
],
});
paintOnce(buildLayout(page), mount);

const seps = querySeparators(mount);
expect(seps).toHaveLength(1);
expect(seps[0].style.top).toBe('96px');
expect(seps[0].style.height).toBe('404px');
expect(seps[0].style.left).toBe('220px');
});
>>>>>>> origin/stable
});
});
49 changes: 49 additions & 0 deletions packages/layout-engine/painters/dom/src/renderer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
ChartDrawing,
ColumnLayout,
CustomGeometryData,
DrawingBlock,
DrawingFragment,
Expand Down Expand Up @@ -61,6 +62,7 @@ import {
calculateJustifySpacing,
computeLinePmRange,
getCellSpacingPx,
normalizeColumnLayout,
normalizeBaselineShift,
resolveBaseFontSizeForVerticalText,
shouldApplyJustify,
Expand Down Expand Up @@ -2346,6 +2348,7 @@ export class DomPainter {
if (!columns.withSeparator) continue;
if (columns.count <= 1) continue;

<<<<<<< HEAD
const columnWidth = (contentWidth - columns.gap * (columns.count - 1)) / columns.count;
// Given the separator will have 1px width, ensure column has a larger width.
if (columnWidth <= 1) continue;
Expand All @@ -2355,6 +2358,15 @@ export class DomPainter {

for (let i = 0; i < columns.count - 1; i++) {
const separatorX = leftMargin + (i + 1) * columnWidth + i * columns.gap + columns.gap / 2;
=======
const regionHeight = yEnd - yStart;
if (regionHeight <= 0) continue;

const separatorPositions = this.getColumnSeparatorPositions(columns, leftMargin, contentWidth);
if (separatorPositions.length === 0) continue;

for (const separatorX of separatorPositions) {
>>>>>>> origin/stable
const separatorEl = this.doc.createElement('div');

separatorEl.style.position = 'absolute';
Expand All @@ -2369,6 +2381,43 @@ export class DomPainter {
}
}

<<<<<<< HEAD
=======
private getColumnSeparatorPositions(columns: ColumnLayout, leftMargin: number, contentWidth: number): number[] {
const hasExplicitWidths = Array.isArray(columns.widths) && columns.widths.length > 0;

if (!hasExplicitWidths) {
const equalWidth = (contentWidth - columns.gap * (columns.count - 1)) / columns.count;
if (equalWidth <= 1) return [];

const separatorPositions: number[] = [];
for (let index = 0; index < columns.count - 1; index += 1) {
separatorPositions.push(leftMargin + (index + 1) * equalWidth + index * columns.gap + columns.gap / 2);
}
return separatorPositions;
}

const normalizedColumns = normalizeColumnLayout(columns, contentWidth);
if (normalizedColumns.count <= 1) return [];

const columnWidths =
normalizedColumns.widths ?? Array.from({ length: normalizedColumns.count }, () => normalizedColumns.width);
// A 1px separator only makes sense when every participating column is wider than the separator itself.
if (columnWidths.some((columnWidth) => columnWidth <= 1)) return [];

const separatorPositions: number[] = [];
let cursorX = leftMargin;

for (let index = 0; index < normalizedColumns.count - 1; index += 1) {
const currentColumnWidth = columnWidths[index] ?? normalizedColumns.width;
separatorPositions.push(cursorX + currentColumnWidth + normalizedColumns.gap / 2);
cursorX += currentColumnWidth + normalizedColumns.gap;
}

return separatorPositions;
}

>>>>>>> origin/stable
private renderDecorationsForPage(pageEl: HTMLElement, page: Page, pageIndex: number): void {
if (this.isSemanticFlow) return;
this.renderDecorationSection(pageEl, page, pageIndex, 'header');
Expand Down
40 changes: 40 additions & 0 deletions packages/react/src/SuperDocEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,46 @@ describe('SuperDocEditor', () => {
{ timeout: 5000 },
);
});

it('should route onTransaction through the latest callback after rerender', async () => {
const ref = createRef<SuperDocRef>();
const onReady = vi.fn();
const firstOnTransaction = vi.fn();
const secondOnTransaction = vi.fn();

const { rerender } = render(<SuperDocEditor ref={ref} onReady={onReady} onTransaction={firstOnTransaction} />);

await waitFor(() => expect(onReady).toHaveBeenCalled(), { timeout: 5000 });

const instance = ref.current?.getInstance();
expect(instance).toBeTruthy();

const transactionEvent = {
editor: {},
sourceEditor: {},
transaction: { docChanged: true },
surface: 'body',
};

const firstCallCountBeforeManualDispatch = firstOnTransaction.mock.calls.length;
(instance as any).config.onTransaction(transactionEvent);

expect(firstOnTransaction).toHaveBeenLastCalledWith(transactionEvent);
expect(firstOnTransaction).toHaveBeenCalledTimes(firstCallCountBeforeManualDispatch + 1);
expect(secondOnTransaction).not.toHaveBeenCalled();

rerender(<SuperDocEditor ref={ref} onReady={onReady} onTransaction={secondOnTransaction} />);

expect(ref.current?.getInstance()).toBe(instance);

const firstCallCountBeforeRerenderDispatch = firstOnTransaction.mock.calls.length;
const secondCallCountBeforeManualDispatch = secondOnTransaction.mock.calls.length;
(instance as any).config.onTransaction(transactionEvent);

expect(firstOnTransaction).toHaveBeenCalledTimes(firstCallCountBeforeRerenderDispatch);
expect(secondOnTransaction).toHaveBeenLastCalledWith(transactionEvent);
expect(secondOnTransaction).toHaveBeenCalledTimes(secondCallCountBeforeManualDispatch + 1);
});
});

describe('onEditorDestroy', () => {
Expand Down
Loading
Loading