diff --git a/apps/docs/core/superdoc/configuration.mdx b/apps/docs/core/superdoc/configuration.mdx
index ec90f4406..5b83d3464 100644
--- a/apps/docs/core/superdoc/configuration.mdx
+++ b/apps/docs/core/superdoc/configuration.mdx
@@ -245,6 +245,7 @@ new SuperDoc({
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.
+ Web layout also forces `disableContextMenu: true` to prevent long-press context menus on mobile.
```javascript
viewOptions: { layout: 'web' }
diff --git a/apps/docs/modules/context-menu.mdx b/apps/docs/modules/context-menu.mdx
index 26675f0ba..ebf5c2745 100644
--- a/apps/docs/modules/context-menu.mdx
+++ b/apps/docs/modules/context-menu.mdx
@@ -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:
@@ -35,6 +36,7 @@ new SuperDoc({
Top-level option to disable the context menu entirely
+ Web layout (`viewOptions.layout = 'web'`) forces this to `true`.
diff --git a/packages/super-editor/src/core/Editor.ts b/packages/super-editor/src/core/Editor.ts
index 89e2e80e6..2fdf0d904 100644
--- a/packages/super-editor/src/core/Editor.ts
+++ b/packages/super-editor/src/core/Editor.ts
@@ -253,6 +253,7 @@ export class Editor extends EventEmitter {
* Guard flag to prevent double-tracking document open
*/
#documentOpenTracked = false;
+ #disableContextMenuBeforeWebLayout: boolean | undefined = undefined;
options: EditorOptions = {
element: null,
@@ -1486,10 +1487,36 @@ export class Editor extends EventEmitter {
* Set editor options and update state.
*/
setOptions(options: Partial = {}): 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;
diff --git a/packages/super-editor/src/core/Editor.webLayout.test.ts b/packages/super-editor/src/core/Editor.webLayout.test.ts
index e9456d117..485d3a66c 100644
--- a/packages/super-editor/src/core/Editor.webLayout.test.ts
+++ b/packages/super-editor/src/core/Editor.webLayout.test.ts
@@ -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 () => {
diff --git a/packages/superdoc/src/dev/components/SuperdocDev.vue b/packages/superdoc/src/dev/components/SuperdocDev.vue
index 4f6746005..46026e16c 100644
--- a/packages/superdoc/src/dev/components/SuperdocDev.vue
+++ b/packages/superdoc/src/dev/components/SuperdocDev.vue
@@ -180,7 +180,7 @@ const init = async () => {
documentMode: 'editing',
licenseKey: 'community-and-eval-agplv3',
telemetry: {
- enabled: false,
+ enabled: true,
},
comments: {
visible: true,