> ## Documentation Index
> Fetch the complete documentation index at: https://velt.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Customize Behavior

## enableSingleEditorMode

Enables single editor mode, allowing only one user to edit the document at a time while others remain in read-only mode.

**Params**

* `config`: (`object`, optional). Configuration object for controlling single editor mode behavior
  * `customMode`: (`boolean`, optional). When true, SDK won't automatically make HTML elements read-only for viewers. You need to handle this manually with the help of other APIs listed here. (default: `false`)
  * `singleTabEditor`: (`boolean`, optional). When enabled, restricts the editor to edit in only one browser tab at a time, preventing them from making changes across multiple tabs simultaneously (default: `true`)

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
>
> // Basic usage
> liveStateSyncElement.enableSingleEditorMode();
>
> // With configuration
> liveStateSyncElement.enableSingleEditorMode({ 
>     customMode: true,
>     singleTabEditor: false 
> });
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> // Get LiveStateSyncElement from Velt Client
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
>
> // Basic usage
> liveStateSyncElement.enableSingleEditorMode();
>
> // With configuration
> liveStateSyncElement.enableSingleEditorMode({
>     customMode: true,
>     singleTabEditor: false
> });
> ```

## disableSingleEditorMode

Disables single editor mode and returns to normal editing.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.disableSingleEditorMode();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.disableSingleEditorMode();
> ```

## Define Single Editor Mode Elements

### Restrict to specific containers

* Restrict Single Editor Mode to specific containers.
* By default Single Editor Mode is enabled at the entire DOM level. You can restrict this feature to only certain HTML containers & their children by using this.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.singleEditorModeContainerIds(["rightPanel", "editor"]);
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.singleEditorModeContainerIds(["rightPanel", "editor"]);
> ```

### Fine tune elements control

Control which elements are controlled by Single Editor Mode.

> [!WARNING]
>
> You must add the data-velt-sync-access-\* attributes to native HTML elements (e.g. button, input). It will not work directly on React components.

> [tab] React / Next.js
>
> ```jsx theme={null}
> // Enable sync access on custom elements
> return (
>     <div data-velt-sync-access="true">
>         Controlled by Single Editor Mode
>     </div>
> );
>
> // Exclude elements from sync access
> return (
>     <button data-velt-sync-access-disabled="true">
>         Not controlled by Single Editor Mode
>     </button>
> );
> ```

> [tab] Other Frameworks
>
> ```html theme={null}
> <!-- Enable sync access on custom elements -->
> <div data-velt-sync-access="true">
>     Controlled by Single Editor Mode
> </div>
>
> <!-- Exclude elements from sync access -->
> <button data-velt-sync-access-disabled="true">
>     Not controlled by Single Editor Mode
> </button>
> ```

## Timeout Configuration

### setEditorAccessTimeout

* Configure automatic editor access timeout.
* Default: `5 seconds`.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.setEditorAccessTimeout(15); // in seconds
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.setEditorAccessTimeout(15); // in seconds
> ```

### enableEditorAccessTransferOnTimeOut

* When editor access timeout is reached, automatically transfer editor access to the next user in the queue.
* Enabled by default.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.enableEditorAccessTransferOnTimeOut();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.enableEditorAccessTransferOnTimeOut();
> ```

### disableEditorAccessTransferOnTimeOut

* When editor access timeout is reached, do not automatically transfer editor access to the next user in the queue.
* Enabled by default.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.disableEditorAccessTransferOnTimeOut();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.disableEditorAccessTransferOnTimeOut();
> ```

### getEditorAccessTimer

Track the state of editor access request timeout.

**Returns**

* `EditorAccessTimer` object:
  * `state` (`'idle'` | `'inProgress'` | `'completed'`)
  * `durationLeft` (`number`)

> [tab] React / Next.js
>
> ```jsx theme={null}
> // Using Hooks
> const editorAccessTimer = useEditorAccessTimer();
>
> useEffect(() => {
>     if (editorAccessTimer?.state === 'completed') {
>         // Handle timeout completion
>         if (isEditor) {
>             acceptEditorAccessRequest();
>         } else if (isRequester) {
>             setUserAsEditor();
>         }
>     }
> }, [editorAccessTimer]);
>
> return (
>     <div>
>         Status: {editorAccessTimer?.state}
>         Time Left: {editorAccessTimer?.durationLeft}s
>     </div>
> );
>
>
> // Using API
> const liveStateSyncElement = useLiveStateSyncUtils();
> let subscription = liveStateSyncElement.getEditorAccessTimer().subscribe((editorAccessTimer) => {
>     console.log('Editor Access Timer:', editorAccessTimer);
> });
>
> ```

## Auto-Sync Text Elements

* Enable automatic syncing of text element contents across all users.
* Supported elements:
  * `<input>`
  * `<textarea>`
  * ContentEditable `<div>`
* First enable the feature and then define which elements should sync realtime.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
>
> // Enable auto-sync state
> liveStateSyncElement.enableAutoSyncState();
>
> // In your JSX
> return (
>     <textarea id="uniqueId" data-velt-sync-state="true"></textarea>
> );
> ```

> [tab] Other Frameworks
>
> ```html theme={null}
> <script>
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.enableAutoSyncState();
> </script>
>
> <body>
>     <!-- Enable auto-sync on text elements -->
>     <textarea id="uniqueId" data-velt-sync-state="true"></textarea>
> </body>
> ```

## Editor

### setUserAsEditor

Sets the current user as the editor, making all other users read-only.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.setUserAsEditor();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.setUserAsEditor();
> ```

#### Error handling

`setUserAsEditor` includes validations to prevent assigning an editor when one already exists. It returns a promise that resolves to an optional error object.

> [tab] React / Next.js
>
> ```ts theme={null}
> export interface SetUserAsEditorResponse {
>   error?: ErrorEvent;
> }
>
> export type ErrorEvent = {
>   error?: string;
>   code: string;
>   message?: string;
>   source?: string;
> };
>
> // Usage
> const liveStateSyncElement = useLiveStateSyncUtils();
> const result = await liveStateSyncElement.setUserAsEditor();
> if (result?.error) {
>   if (result.error.code === 'same_user_editor_current_tab') {
>     // Same user already editor in current tab
>   } else if (result.error.code === 'same_user_editor_different_tab') {
>     // Same user already editor in different tab
>   } else if (result.error.code === 'another_user_editor') {
>     // Another user already editor
>   }
> }
> ```

> [tab] Other Frameworks
>
> ```js theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> const result = await liveStateSyncElement.setUserAsEditor();
> if (result && result.error) {
>   if (result.error.code === 'same_user_editor_current_tab') {
>     // Same user already editor in current tab
>   } else if (result.error.code === 'same_user_editor_different_tab') {
>     // Same user already editor in different tab
>   } else if (result.error.code === 'another_user_editor') {
>     // Another user already editor
>   }
> }
> ```

Possible error values:

```ts theme={null}
{
  code: 'same_user_editor_current_tab',
  message: 'Same user is already editor in current tab.',
  source: 'setUserAsEditor',
}
{
  code: 'same_user_editor_different_tab',
  message: 'Same user is already editor in different tab.',
  source: 'setUserAsEditor',
}
{
  code: 'another_user_editor',
  message: 'Another user is already editor.',
  source: 'setUserAsEditor',
}
```

### isUserEditor

* Get the current user's editor status.

**Returns**

* `null`: When the state is not available yet.
* `undefined`: When there are no current editors available in single editor mode.
* `UserEditorAccess`: When there is at least one editor available in single editor mode.
  * `isEditor` (`boolean`) - Whether the user is the editor
  * `isEditorOnCurrentTab` (`boolean`) - Whether the user is editor on current tab

> [tab] React / Next.js
>
> ```jsx theme={null}
> // Using Hooks
> const { isEditor, isEditorOnCurrentTab } = useUserEditorState();
>
> // Using API
> const liveStateSyncElement = useLiveStateSyncUtils();
> let subscription = liveStateSyncElement.isUserEditor().subscribe((userEditorAccess) => {
>     console.log('Is Editor:', userEditorAccess.isEditor);
>     console.log('Is Editor on Current Tab:', userEditorAccess.isEditorOnCurrentTab);
> });
> ```
>
> To unsubscribe from the subscription:
>
> ```jsx theme={null}
> subscription?.unsubscribe()
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> let subscription = liveStateSyncElement.isUserEditor().subscribe((userEditorAccess) => {
>     console.log('Is Editor:', userEditorAccess.isEditor);
>     console.log('Is Editor on Current Tab:', userEditorAccess.isEditorOnCurrentTab);
> });
>
> // To unsubscribe from the subscription:
> subscription?.unsubscribe()
> ```

### getEditor

Get information about the current editor.

**Returns**

* `User` object:
  * `email` (`string`) - Editor's email
  * `name` (`string`) - Editor's name
  * `photoUrl` (`string`) - Editor's photo URL
  * `userId` (`string`) - Editor's unique ID

> [tab] React / Next.js
>
> ```jsx theme={null}
> // Using Hooks
> const editor = useEditor();
>
> // Using API
> const liveStateSyncElement = useLiveStateSyncUtils();
> let subscription = liveStateSyncElement.getEditor().subscribe((user) => {
>     console.log('Editor:', user);
> });
> ```
>
> To unsubscribe from the subscription:
>
> ```jsx theme={null}
> subscription?.unsubscribe()
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> let subscription = liveStateSyncElement.getEditor().subscribe((user) => {
>     console.log('Editor:', user);
> });
>
> // To unsubscribe from the subscription:
> subscription?.unsubscribe()
> ```

### isEditorAccessRequested

Check if any viewer has requested editor access.

**Returns**

* `null` - User is not editor or request was canceled
* `EditorRequest` object:
  * `requestStatus` (`string`) - 'requested' for active requests
  * `requestedBy` (`User`) - User object of the requester

> [tab] React / Next.js
>
> ```jsx theme={null}
> // Using Hooks
> const editorAccessRequested = useEditorAccessRequestHandler();
>
> // Using API
> const liveStateSyncElement = useLiveStateSyncUtils();
> let subscription = liveStateSyncElement.isEditorAccessRequested().subscribe((data) => {
>     if (data === null) {
>         console.log('No active requests or user is not editor');
>     } else {
>         console.log('Request from:', data.requestedBy.name);
>     }
> });
> ```
>
> To unsubscribe from the subscription:
>
> ```jsx theme={null}
> subscription?.unsubscribe()
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> let subscription = liveStateSyncElement.isEditorAccessRequested().subscribe((data) => {
>     if (data === null) {
>         console.log('No active requests or user is not editor');
>     } else {
>         console.log('Request from:', data.requestedBy.name);
>     }
> });
>
> // To unsubscribe from the subscription:
> subscription?.unsubscribe()
> ```

### acceptEditorAccessRequest

Accept editor access requests.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.acceptEditorAccessRequest();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.acceptEditorAccessRequest();
> ```

### rejectEditorAccessRequest

Reject editor access requests.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.rejectEditorAccessRequest();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.rejectEditorAccessRequest();
> ```

### editCurrentTab

Make current tab editable when editor has multiple tabs open.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.editCurrentTab();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.editCurrentTab();
> ```

## Viewer

### requestEditorAccess

Request editor access from the current editor.

**Returns**

* `null` - Request is pending
* `true` - Request accepted
* `false` - Request rejected

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> let subscription = liveStateSyncElement.requestEditorAccess().subscribe((status) => {
>     if (status === null) console.log('Request pending');
>     else if (status === true) console.log('Request accepted');
>     else console.log('Request rejected');
> });
> ```
>
> To unsubscribe from the subscription:
>
> ```jsx theme={null}
> subscription?.unsubscribe()
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> let subscription = liveStateSyncElement.requestEditorAccess().subscribe((status) => {
>     if (status === null) console.log('Request pending');
>     else if (status === true) console.log('Request accepted');
>     else console.log('Request rejected');
> });
>
> // To unsubscribe from the subscription:
> subscription?.unsubscribe()
> ```

### cancelEditorAccessRequest

Cancel the editor access request.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.cancelEditorAccessRequest();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.cancelEditorAccessRequest();
> ```

## Heartbeat

The heartbeat mechanism provides more reliable presence detection in single editor mode scenarios. This feature is enabled by default when single editor mode is enabled.

> [!NOTE]
>
> If you want to disable heartbeat functionality, you must disable it before enabling single editor mode.

### [enableHeartbeat](https://velt.dev/docs/api-reference/sdk/api/api-methods.md#enableheartbeat)

Enables/Disables the heartbeat mechanism for Single Editor Mode presence detection.

Default: `enabled`

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.enableHeartbeat();
> liveStateSyncElement.disableHeartbeat();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.enableHeartbeat();
> liveStateSyncElement.disableHeartbeat();
> ```

## Presence Heartbeat

### updateUserPresence

Send a lightweight heartbeat from your host app to Velt. Velt will use that as a fallback in rare edge cases if it fails to detect multi‑tab/device presence. Most apps don't need this; use only if you see ambiguity in who's the active editor.

> [tab] React / Next.js
>
> ```ts theme={null}
> export interface LiveStateSingleEditorExternalUserPresence {
>   /** True if the same user is present on a different tab. */
>   sameUserPresentOnTab?: boolean;
>   /** True if a different user is present on a different tab. */
>   differentUserPresentOnTab?: boolean;
>   /** User IDs of users present on a different tab. */
>   userIds?: string[];
> }
>
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.updateUserPresence({
>   sameUserPresentOnTab: false,
>   differentUserPresentOnTab: true,
>   userIds: ['user-2']
> });
> ```

> [tab] Other Frameworks
>
> ```ts theme={null}
> export interface LiveStateSingleEditorExternalUserPresence {
>   /** True if the same user is present on a different tab. */
>   sameUserPresentOnTab?: boolean;
>   /** True if a different user is present on a different tab. */
>   differentUserPresentOnTab?: boolean;
>   /** User IDs of users present on different tabs. */
>   userIds?: string[];
> }
> ```
>
> ```js theme={null}
> // API method
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
>
> const userPresenceObject = {
>   sameUserPresentOnTab: false,
>   differentUserPresentOnTab: true,
>   userIds: ['user-2']
> };
>
> liveStateSyncElement.updateUserPresence(userPresenceObject);
> ```

### resetUserAccess

Reset editor access for all users.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> liveStateSyncElement.resetUserAccess();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> liveStateSyncElement.resetUserAccess();
> ```

## UI

### enableDefaultSingleEditorUI

* Control the visibility of the default Single Editor Mode System UI.
* The default UI shows:
  * Current user's editor/viewer status
  * Editor access requests
  * Request timeout countdown
  * Request rejection options
* If you disable the default UI, you'll need to implement your own UI for these features.

> [tab] React / Next.js
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
>
> // Enable default UI (enabled by default)
> liveStateSyncElement.enableDefaultSingleEditorUI();
>
> // Disable default UI for custom implementation
> liveStateSyncElement.disableDefaultSingleEditorUI();
> ```

> [tab] Other Frameworks
>
> ```jsx theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
>
> // Enable default UI (enabled by default)
> liveStateSyncElement.enableDefaultSingleEditorUI();
>
> // Disable default UI for custom implementation
> liveStateSyncElement.disableDefaultSingleEditorUI();
> ```

### Embed Single Editor Mode Panel

* Embed the Single Editor Mode Panel into your UI.

> [tab] React / Next.js
>
> ```jsx theme={null}
> <VeltSingleEditorModePanel shadowDom={false} darkMode={true} variant="custom-ui" />
> ```

> [tab] Other Frameworks
>
> ```html theme={null}
> <velt-single-editor-mode-panel shadow-dom="false" dark-mode="true" variant="custom-ui"></velt-single-editor-mode-panel>
> ```

## Event Subscription

### on

Subscribe to Single Editor Mode Events. Here is the list of events you can subscribe to and the event objects you will receive.

| Category | Event Type | Description | Event Object |
| - | - | - | - |
| Editor | `accessRequested` | When a user requests editor access. Editor will get this event. | [AccessRequestEvent](https://velt.dev/docs/api-reference/sdk/models/data-models.md#accessrequestevent) |
| Editor | `accessRequestCanceled` | When a user cancels their request for editor access. Editor will get this event. | [AccessRequestEvent](https://velt.dev/docs/api-reference/sdk/models/data-models.md#accessrequestevent) |
| Viewer | `accessAccepted` | When a user's request for editor access is accepted. Requesting Viewer will get this event. | [AccessRequestEvent](https://velt.dev/docs/api-reference/sdk/models/data-models.md#accessrequestevent) |
| Viewer | `accessRejected` | When a user's request for editor access is rejected. Requesting Viewer will get this event. | [AccessRequestEvent](https://velt.dev/docs/api-reference/sdk/models/data-models.md#accessrequestevent) |
| Access Assignment | `editorAssigned` | When a user is assigned as the editor. | [SEMEvent](https://velt.dev/docs/api-reference/sdk/models/data-models.md#semevent) |
| Access Assignment | `viewerAssigned` | When a user is assigned as a viewer. | [SEMEvent](https://velt.dev/docs/api-reference/sdk/models/data-models.md#semevent) |
| Editor | `editorOnDifferentTabDetected` | When the editor opens the same document in a different browser tab. Editor will get this event. | [SEMEvent](https://velt.dev/docs/api-reference/sdk/models/data-models.md#semevent) |

> [tab] React / Next.js
>
> **Using Hooks:**
>
> ```jsx theme={null}
> // Replace "EVENT_TYPE" with an actual event string. eg: "accessRequested".
> const eventData = useLiveStateSyncEventCallback("EVENT_TYPE"); 
>
> useEffect(() => {
>   if (eventData) { 
>     console.log("EVENT_TYPE data: ", eventData);
>   }
> }, [eventData]);
> ```
>
> **Using API:**
>
> ```jsx theme={null}
> const liveStateSyncElement = useLiveStateSyncUtils();
> // Replace "EVENT_TYPE" with an actual event string. eg: "accessRequested".
> liveStateSyncElement.on("EVENT_TYPE").subscribe((eventData) => {
>   console.log("EVENT_TYPE data: ", eventData);
> });
> ```

> [tab] Other Frameworks
>
> ```javascript theme={null}
> const liveStateSyncElement = Velt.getLiveStateSyncElement();
> // Replace "EVENT_TYPE" with an actual event string. eg: "accessRequested".
> liveStateSyncElement.on("EVENT_TYPE").subscribe((eventData) => {
>   console.log("EVENT_TYPE data: ", eventData);
> });
> ```

## Best Practices

* Use `singleTabEditor` to prevent confusion when users have multiple tabs open
* Add IDs to HTML elements with sync attributes for more robust syncing
* Only apply sync attributes to native HTML elements, not framework components


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.