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
1 change: 1 addition & 0 deletions apps/docs/core/superdoc/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ new SuperDoc({
</ParamField>
</Expandable>
<Note>Use `'web'` for mobile devices and WCAG AA reflow compliance (Success Criterion 1.4.10). When set to `'web'`, the layout engine is automatically disabled.</Note>
<Note>Web layout also forces `disableContextMenu: true` to prevent long-press context menus on mobile.</Note>

```javascript
viewOptions: { layout: 'web' }
Expand Down
4 changes: 3 additions & 1 deletion apps/docs/modules/context-menu.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ A contextual command menu triggered by right-clicking. Shows relevant actions ba

## Quick start

The context menu is **enabled by default**. Right-click anywhere in the document to open it.
The context menu is **enabled by default** in print layout. In web layout (`viewOptions.layout = 'web'`), the custom
context menu is disabled to avoid long-press menus on mobile.

To disable it:

Expand Down Expand Up @@ -35,6 +36,7 @@ new SuperDoc({

<ParamField path="disableContextMenu" type="boolean" default="false">
Top-level option to disable the context menu entirely
<Note>Web layout (`viewOptions.layout = 'web'`) forces this to `true`.</Note>
</ParamField>

<ParamField path="modules.slashMenu.includeDefaultItems" type="boolean" default="true">
Expand Down
29 changes: 28 additions & 1 deletion packages/super-editor/src/core/Editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ export class Editor extends EventEmitter<EditorEventMap> {
* Guard flag to prevent double-tracking document open
*/
#documentOpenTracked = false;
#disableContextMenuBeforeWebLayout: boolean | undefined = undefined;

options: EditorOptions = {
element: null,
Expand Down Expand Up @@ -1486,10 +1487,36 @@ export class Editor extends EventEmitter<EditorEventMap> {
* Set editor options and update state.
*/
setOptions(options: Partial<EditorOptions> = {}): void {
this.options = {
const prevLayout = this.options.viewOptions?.layout;
const prevDisableContextMenu = this.options.disableContextMenu;
const disableContextMenuProvided = Object.prototype.hasOwnProperty.call(options, 'disableContextMenu');
const nextOptions = {
...this.options,
...options,
};
const nextLayout = nextOptions.viewOptions?.layout;
const nextDisableContextMenu = disableContextMenuProvided ? options.disableContextMenu : prevDisableContextMenu;

if (nextLayout === 'web') {
if (prevLayout !== 'web') {
this.#disableContextMenuBeforeWebLayout = nextDisableContextMenu;
} else if (disableContextMenuProvided) {
this.#disableContextMenuBeforeWebLayout = nextDisableContextMenu;
}
// Web layout mode should not surface the context menu (e.g., on mobile long-press).
nextOptions.disableContextMenu = true;
} else if (prevLayout === 'web') {
if (disableContextMenuProvided) {
nextOptions.disableContextMenu = nextDisableContextMenu;
} else {
nextOptions.disableContextMenu = this.#disableContextMenuBeforeWebLayout ?? false;
}
this.#disableContextMenuBeforeWebLayout = undefined;
} else if (disableContextMenuProvided) {
nextOptions.disableContextMenu = nextDisableContextMenu;
}

this.options = nextOptions;

if ((this.options.isNewFile || !this.options.ydoc) && this.options.isCommentsEnabled) {
this.options.shouldLoadComments = true;
Expand Down
65 changes: 65 additions & 0 deletions packages/super-editor/src/core/Editor.webLayout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,71 @@ describe('Editor Web Layout Mode', () => {
});
});

describe('context menu behavior', () => {
it('forces disableContextMenu when viewOptions.layout is "web"', () => {
const editor = createTestEditor({
viewOptions: { layout: 'web' },
disableContextMenu: false,
});

expect(editor.options.disableContextMenu).toBe(true);
});

it('respects disableContextMenu when viewOptions.layout is "print"', () => {
const editor = createTestEditor({
viewOptions: { layout: 'print' },
disableContextMenu: false,
});

expect(editor.options.disableContextMenu).toBe(false);
});

it('resets disableContextMenu when switching from "web" to "print" without explicit override', () => {
const editor = createTestEditor({
viewOptions: { layout: 'web' },
});

expect(editor.options.disableContextMenu).toBe(true);

editor.setOptions({ viewOptions: { layout: 'print' } });

expect(editor.options.disableContextMenu).toBe(false);
});

it('keeps explicit disableContextMenu when switching from "web" to "print"', () => {
const editor = createTestEditor({
viewOptions: { layout: 'web' },
disableContextMenu: true,
});

editor.setOptions({ viewOptions: { layout: 'print' } });

expect(editor.options.disableContextMenu).toBe(true);
});

it('defaults to enabled context menu after leaving web when created in web mode', () => {
const editor = createTestEditor({
viewOptions: { layout: 'web' },
});

editor.setOptions({ disableContextMenu: true });
editor.setOptions({ viewOptions: { layout: 'print' } });

expect(editor.options.disableContextMenu).toBe(true);
});

it('does not override disableContextMenu when switching layouts if it was explicitly set before', () => {
const editor = createTestEditor({
viewOptions: { layout: 'web' },
});

editor.setOptions({ disableContextMenu: false });
editor.setOptions({ viewOptions: { layout: 'print' } });

expect(editor.options.disableContextMenu).toBe(false);
});
});

describe('getMaxContentSize()', () => {
describe('web layout mode', () => {
it('returns empty object to skip image constraints', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/superdoc/src/dev/components/SuperdocDev.vue
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ const init = async () => {
documentMode: 'editing',
licenseKey: 'community-and-eval-agplv3',
telemetry: {
enabled: false,
enabled: true,
},
comments: {
visible: true,
Expand Down
Loading