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
41 changes: 33 additions & 8 deletions apps/docs/solutions/esign/api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,28 @@ Complete eSign component API
Display mode: `'full' | 'download'`
</ParamField>

<ParamField path="layoutMode" type="string">
Layout mode: `'paginated' | 'responsive'`
<ParamField path="viewOptions" type="object">
View options for controlling document layout (recommended)

<Expandable title="Properties">
<ParamField path="viewOptions.layout" type="string">
Document layout: `'print'` (default) or `'web'`
- `print` - Fixed page width, displays document as it prints
- `web` - Content reflows to fit container (mobile/accessibility)
</ParamField>
</Expandable>
</ParamField>

<ParamField path="layoutMargins" type="object">
Custom margins (pixels) for `layoutMode: 'responsive'`
<ParamField path="layoutMode" type="string" deprecated>
**Deprecated.** Use `viewOptions.layout` instead.
- `'paginated'` → `viewOptions: { layout: 'print' }`
- `'responsive'` → `viewOptions: { layout: 'web' }`
</ParamField>


<ParamField path="layoutMargins" type="object" deprecated>
**Deprecated.** No longer supported. Use CSS for margin control.
</ParamField>

<ParamField path="validation.scroll.required" type="boolean">
Require scrolling to bottom before signing
</ParamField>
Expand Down Expand Up @@ -235,9 +249,19 @@ interface AuditEvent {
}
```

### LayoutMargins
### ViewOptions

Document view options (recommended):

```typescript
interface ViewOptions {
layout?: 'web' | 'print'; // 'print' is default
}
```

### LayoutMargins (Deprecated)

Custom margins for responsive layout mode:
<Warning>Deprecated since v2.0. Use CSS for margin control.</Warning>

```typescript
interface LayoutMargins {
Expand Down Expand Up @@ -345,6 +369,7 @@ All types are exported:
import type {
SuperDocESignProps,
SuperDocESignHandle,
DocumentConfig,
SubmitData,
DownloadData,
SigningState,
Expand All @@ -355,7 +380,7 @@ import type {
FieldChange,
AuditEvent,
FieldComponentProps,
LayoutMargins
LayoutMargins // deprecated
} from '@superdoc-dev/esign';
```

Expand Down
33 changes: 23 additions & 10 deletions apps/docs/solutions/esign/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ document={{
// Display mode
mode: 'full', // 'full' (default) or 'download'

// Layout mode
layoutMode: 'responsive', // 'paginated' (default) or 'responsive'
layoutMargins: { top: 10, bottom: 10, left: 10, right: 10 },
// View options (recommended)
viewOptions: { layout: 'web' }, // 'print' (default) or 'web'

// Requirements
validation: {
Expand All @@ -27,14 +26,29 @@ document={{
}}
```

### Layout modes
### View options

Control how the document is displayed:
Control how the document is displayed using `viewOptions.layout`:

- **`paginated`** (default) - Fixed page width with page breaks
- **`responsive`** - 100% width, text reflows to fit container
- **`print`** (default) - Fixed page width, displays document as it prints
- **`web`** - Content reflows to fit container width (mobile/accessibility)

Use `responsive` when you want text to reflow to fit the container (useful for mobile/accessibility). Optional `layoutMargins` sets custom margins (in pixels) for responsive mode.
Use `web` when you want text to reflow to fit the container. This is recommended for mobile devices and WCAG AA reflow compliance (Success Criterion 1.4.10).

```jsx
// Mobile-friendly responsive layout
document={{
source: contractFile,
viewOptions: { layout: 'web' }
}}
```

<Warning>
**Deprecated options**: `layoutMode` and `layoutMargins` are deprecated since v2.0. Use `viewOptions` instead:
- `layoutMode: 'responsive'` → `viewOptions: { layout: 'web' }`
- `layoutMode: 'paginated'` → `viewOptions: { layout: 'print' }`
- `layoutMargins` is no longer supported. Use CSS to control margins.
</Warning>

When `mode` is set to `download`, the submit button is hidden.

Expand Down Expand Up @@ -340,8 +354,7 @@ Style the component to match your brand.
document={{
source: contractFile,
mode: "full",
layoutMode: "responsive",
layoutMargins: { top: 10, bottom: 10, left: 10, right: 10 },
viewOptions: { layout: "web" },
validation: {
scroll: { required: true },
},
Expand Down
3 changes: 1 addition & 2 deletions packages/esign/demo/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,7 @@ export function App() {
document={{
source: documentSource,
mode: 'full',
layoutMode: 'responsive',
layoutMargins: { top: 10, bottom: 10, left: 10, right: 10 },
viewOptions: { layout: 'web' },
validation: {
scroll: { required: true },
},
Expand Down
2 changes: 1 addition & 1 deletion packages/esign/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@superdoc-dev/esign",
"version": "1.3.0",
"version": "2.0.0",
"description": "React eSignature component for SuperDoc",
"type": "module",
"main": "./dist/index.js",
Expand Down
95 changes: 94 additions & 1 deletion packages/esign/src/__tests__/SuperDocESign.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@
import type { FieldComponentProps, SuperDocESignHandle, SuperDocESignProps, AuditEvent } from '../types';

import { SuperDoc } from 'superdoc';
import { getAuditEventTypes, resetAuditEvents } from '../test/setup';
import {
getAuditEventTypes,
resetAuditEvents,
getLastConstructorOptions,
resetLastConstructorOptions,
} from '../test/setup';

const scrollListeners = new WeakMap<HTMLElement, EventListener>();
const originalAddEventListener = HTMLElement.prototype.addEventListener;
Expand Down Expand Up @@ -144,6 +149,7 @@
superDocMock.mockGetStructuredContentTablesById.mockReturnValue([]);
superDocMock.mockDestroy.mockReset();
resetAuditEvents();
resetLastConstructorOptions();
});

describe('SuperDocESign component', () => {
Expand Down Expand Up @@ -398,10 +404,10 @@
],
},
submit: {
component: SubmitButton as unknown as ComponentType<any>,

Check warning on line 407 in packages/esign/src/__tests__/SuperDocESign.test.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
},
download: {
component: DownloadButton as unknown as ComponentType<any>,

Check warning on line 410 in packages/esign/src/__tests__/SuperDocESign.test.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
},
});

Expand Down Expand Up @@ -724,4 +730,91 @@
]);
});
});

describe('viewOptions configuration', () => {
it('passes viewOptions directly to SuperDoc when provided', async () => {
renderComponent({
document: {
source: '<p>Test</p>',
viewOptions: { layout: 'web' },
},
});

await waitForSuperDocReady();

const options = getLastConstructorOptions();
expect(options.viewOptions).toEqual({ layout: 'web' });
});

it('translates layoutMode "responsive" to viewOptions layout "web"', async () => {
renderComponent({
document: {
source: '<p>Test</p>',
layoutMode: 'responsive',
},
});

await waitForSuperDocReady();

const options = getLastConstructorOptions();
expect(options.viewOptions).toEqual({ layout: 'web' });
});

it('translates layoutMode "paginated" to viewOptions layout "print"', async () => {
renderComponent({
document: {
source: '<p>Test</p>',
layoutMode: 'paginated',
},
});

await waitForSuperDocReady();

const options = getLastConstructorOptions();
expect(options.viewOptions).toEqual({ layout: 'print' });
});

it('defaults to viewOptions layout "print" when neither viewOptions nor layoutMode is specified', async () => {
renderComponent({
document: {
source: '<p>Test</p>',
},
});

await waitForSuperDocReady();

const options = getLastConstructorOptions();
expect(options.viewOptions).toEqual({ layout: 'print' });
});

it('prefers viewOptions over deprecated layoutMode when both are provided', async () => {
renderComponent({
document: {
source: '<p>Test</p>',
viewOptions: { layout: 'print' },
layoutMode: 'responsive',
},
});

await waitForSuperDocReady();

const options = getLastConstructorOptions();
expect(options.viewOptions).toEqual({ layout: 'print' });
});

it('falls back to layoutMode when viewOptions is empty object', async () => {
renderComponent({
document: {
source: '<p>Test</p>',
viewOptions: {},
layoutMode: 'responsive',
},
});

await waitForSuperDocReady();

const options = getLastConstructorOptions();
expect(options.viewOptions).toEqual({ layout: 'web' });
});
});
});
19 changes: 5 additions & 14 deletions packages/esign/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@

// Handle table fields
if (field.type === 'table' && Array.isArray(field.value)) {
const helpers = (editor.helpers as any)?.structuredContentCommands;

Check warning on line 60 in packages/esign/src/index.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
const tables = helpers?.getStructuredContentTablesById?.(field.id, editor.state) || [];

if (tables.length) {
Expand Down Expand Up @@ -86,7 +86,7 @@
}

// Append new rows after row 0 (copies style from row 0)
(editor.commands as any)?.appendRowsToStructuredContentTable?.({

Check warning on line 89 in packages/esign/src/index.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
id: field.id,
rows: field.value,
copyRowStyle: true,
Expand Down Expand Up @@ -138,7 +138,7 @@
});

const discovered: Types.FieldInfo[] = tags
.map(({ node }: any) => ({

Check warning on line 141 in packages/esign/src/index.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
id: node.attrs.id,
label: node.attrs.label,
value: configValues.get(node.attrs.id) ?? node.textContent ?? '',
Expand Down Expand Up @@ -178,7 +178,7 @@
...event,
timestamp: new Date().toISOString(),
};
const auditMock = (globalThis as any)?.__SUPERDOC_AUDIT_MOCK__;

Check warning on line 181 in packages/esign/src/index.tsx

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
if (auditMock) {
auditMock(auditEvent);
}
Expand Down Expand Up @@ -209,9 +209,9 @@
modules: {
comments: false,
},
// @ts-expect-error - layoutMode is a valid SuperDoc option
layoutMode: document.layoutMode,
layoutMargins: document.layoutMargins,
viewOptions: {
layout: document.viewOptions?.layout ?? (document.layoutMode === 'responsive' ? 'web' : 'print'),
},
onReady: () => {
// Guard callback execution if cleanup already ran
if (aborted) return;
Expand All @@ -237,17 +237,8 @@
}
superdocRef.current = null;
};
// Compare margin primitives to avoid re-init on every render
}, [
document.source,
document.mode,
document.layoutMode,
document.layoutMargins?.top,
document.layoutMargins?.bottom,
document.layoutMargins?.left,
document.layoutMargins?.right,
discoverAndApplyFields,
]);
// Use primitives to avoid re-init on every render when object references change
}, [document.source, document.mode, document.layoutMode, document.viewOptions?.layout, discoverAndApplyFields]);

useEffect(() => {
if (!document.validation?.scroll?.required || !isReady) return;
Expand Down
9 changes: 9 additions & 0 deletions packages/esign/src/test/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
export const getAuditEventTypes = () => auditEvents.map((event) => event.type);

if (typeof window !== 'undefined') {
(window as any).__SUPERDOC_AUDIT_MOCK__ = (event: { type: string; data?: Record<string, unknown> }) => {

Check warning on line 20 in packages/esign/src/test/setup.ts

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
recordAuditEvent(event.type, event.data);
};
}
Expand Down Expand Up @@ -46,7 +46,11 @@
},
};

let lastConstructorOptions: any = null;

Check warning on line 49 in packages/esign/src/test/setup.ts

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type

const SuperDocMock = vi.fn((options: any = {}) => {

Check warning on line 51 in packages/esign/src/test/setup.ts

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
lastConstructorOptions = options;

if (options?.onReady) {
if (typeof queueMicrotask === 'function') {
queueMicrotask(() => options.onReady());
Expand All @@ -62,7 +66,12 @@
};
});

export const getLastConstructorOptions = () => lastConstructorOptions;
export const resetLastConstructorOptions = () => {
lastConstructorOptions = null;
};

(SuperDocMock as any).mockEditor = mockEditor;

Check warning on line 74 in packages/esign/src/test/setup.ts

View workflow job for this annotation

GitHub Actions / validate

Unexpected any. Specify a different type
(SuperDocMock as any).mockUpdateStructuredContentById = mockUpdateStructuredContentById;
(SuperDocMock as any).mockGetStructuredContentTags = mockGetStructuredContentTags;
(SuperDocMock as any).mockAppendRowsToStructuredContentTable = mockAppendRowsToStructuredContentTable;
Expand Down
15 changes: 11 additions & 4 deletions packages/esign/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,15 +72,22 @@ export interface DocumentConfig {
};
};
/**
* Document view options (recommended for SuperDoc > v1.6.x):
* - 'print': Fixed page width, displays document as it prints (default)
* - 'web': Content reflows to fit container width (mobile/accessibility)
*/
viewOptions?: {
layout?: 'web' | 'print';
};
/**
* @deprecated Use `viewOptions: { layout: 'web' }` instead.
* Document layout mode:
* - 'paginated' (default): Fixed page width, shows page breaks
* - 'responsive': 100% width, text reflows to fit container (useful for mobile/accessibility)
* Note: 'responsive' takes precedence over pagination - pagination is ignored when layoutMode is 'responsive'
* - 'responsive': 100% width, text reflows to fit container
*/
layoutMode?: 'responsive' | 'paginated';
/**
* Custom margins in pixels for responsive layout mode.
* Only applies when layoutMode is 'responsive'.
* @deprecated No longer supported in v1.x. Use CSS for margin control.
*/
layoutMargins?: LayoutMargins;
}
Expand Down