Single Editor Mode

With Single Editor Mode, only one user will be able to edit the document at any given time. All other users live on the document will automatically be read-only.

1

Accessing the LiveStateSyncElement

The LiveStateSyncElement contains all of the methods used to interact with the single editor mode and live state sync features.

There are two ways to access the LiveStateSyncElement.

Accessing via the Velt client object

const liveStateSyncElement = client.getLiveStateSyncElement();

Accessing via a hook

let liveStateSyncElement = useLiveSelectionUtils();
2

Enable Single Editor Mode

Enables single editor mode.

Disabled by default

liveStateSyncElement.enableSingleEditorMode(); //enables single editor mode

(Optional) Set your DOM to read-only yourself, or let our SDK do it for you.

Default: We handle it for you.

You can pass a configuration object to enable customMode on Single Editor Mode.

If custom mode is enabled, we will not make the viewer’s DOM read-only. You will have to handle the logic yourself.

liveStateSyncElement.enableSingleEditorMode({ customMode: true });

(Optional) Lock Edit access to Single-Tab.

Default: true

The configuration {singleTabEditor: boolean} limits editing to one tab at a time. If an editor opens the same document in multiple tabs and begins editing in one (e.g., Tab 1), edit access is automatically disabled in the others (e.g., Tab 2). To enable editing in a different tab, a button can be implemented to trigger the editCurrentTab() method, granting edit access in that specific tab.

liveStateSyncElement.enableSingleEditorMode({ singleTabEditor: false });
3

Disable Single Editor Mode

Disables single editor mode.

liveStateSyncElement.disableSingleEditorMode(); // disables single editor mode
4

Enable default Single Editor UI

In Single Editor mode, by default there is a small toast at the bottom of the screen that shows:

  • whether you are the editor or viewer
  • whether any other viewer has requested editor access
  • access request timeout coundown
  • option to reject request

To enable this toast, you can use the enableDefaultSingleEditorUI() method.

Default: enabled

liveStateSyncElement.enableDefaultSingleEditorUI();
5

Disable default single editor UI

In Single Editor mode, by default there is a small toast at the bottom of the screen that shows:

  • whether you are the editor or viewer
  • whether any other viewer has requested editor access
  • access request timeout coundown
  • option to reject request

You can choose to disable this toast in favor of using your own custom UI.

To disable this pop up, you can use the disableDefaultSingleEditorUI() method.

liveStateSyncElement.disableDefaultSingleEditorUI();
6

Set current user as the Editor

Sets the current user as Editor. Note this will mark all the other users on the document as readonly users.

liveStateSyncElement.setUserAsEditor();
7

Check if current user is the Editor

isUserEditor() returns a subscription that emits the following UserEditorAccess object:

{
  isEditor: true, // `true` if the user is the editor  
  isEditorOnCurrentTab: true // true if the user is the editor on the current tab 
}

let subscription = liveStateSyncElement.isUserEditor().subscribe((userEditorAccess) => {
  //userEditorAccess.isEditor <-- True if user is editor
  //userEditorAccess.isEditorOnCurrentTab <-- true if the user is editor on current tab
});

To unsubscribe from the subscription:

subscription?.unsubscribe()

Using Hooks:

The useUserEditorState() hook is used to call liveStateSyncElement.isUserEditor() without having to handle the subscription.

The hook will automatically unsubscribe from the subscription when the component dismounts.

import { useUserEditorState} from '@veltdev/react';

export default function YourDocument() {

  let {isEditor, isEditorOnCurrentTab} = useUserEditorState()
  console.log("Is User Editor?: ", isEditor)

  return (
    <div>
        Is User Editor?: {isEditor}
    </div>
  )
}
8

Get the current editor

getEditor() returns a subscription that emits the User object representing the current editor.

User object schema:

{
    "email": "userX@example.com",
    "name": "User X",
    "photoUrl": "https://i.pravatar.cc/302",
    "userId": "X",
}
let subscription = liveStateSyncElement.getEditor().subscribe((user) => {
  // user contains User object data

});

To unsubscribe from the subscription:

subscription?.unsubscribe()

Using Hooks:

The useEditor() hook is used to call liveStateSyncElement.getEditor() without having to handle the subscription.

The hook will automatically unsubscribe from the subscription when the component dismounts.

import { useEditor} from '@veltdev/react';

export default function YourDocument() {

  let user = useEditor()
  console.log("Editor User Data: ", user)

  return (
    <div>
        Editor Email: {user.email}
        Editor Name: {user.name}
        Editor ID: {user.userId}
    </div>
  )
}
9

Reset access for all users

Resets access for all users.

liveStateSyncElement.resetUserAccess();
10

Request editor access from the current editor

Call requestEditorAccess() from a Viewer’s client to get editor access from the current editor.

It returns a subscription that emits:

  • null if the request is pending
  • true if the request went accepted by the editor
  • false if the request was rejected by the editor
let subscription = liveStateSyncElement.requestEditorAccess().subscribe((data) => {
	// data == null -> Request is pending
	// data == true -> Request accepted by editor
  // data == false -> Request rejected by editor
});

To unsubscribe from the subscription:

subscription?.unsubscribe()
11

Check whether editor access is being requested

The isEditorAccessRequested() method is used to detect on the Editor's client if a Viewer has requested editor access or not.

If the user is not the editor or the request has been canceled, the subscription will emit a null value.

It returns a subscription that emits:

  • null if the user is not the editor or the request has been canceled
  • a isUserEditor object if the user is the editor and there is an ongoing request

isUserEditor Object Schema:

{ 
  requestStatus: 'requested', // currently always is equal to 'requested' if there is an ongoing request
  requestedBy: { // User Object
    "email": "userX@example.com",
    "name": "User X",
    "photoUrl": "https://i.pravatar.cc/302",
    "userId": "X",
  }
}
let subscription = liveStateSyncElement.isEditorAccessRequested().subscribe((data) => {
  // data == null --> if user is not editor or the request has been canceled
  // data.requestStatus == 'requested' -> Current user is Editor & access requested by Viewer
  // data.requestedBy -> contains data about the User that requested access
});

To unsubscribe from the subscription:

subscription?.unsubscribe()

Using Hooks:

The useEditorAccessRequestHandler() hook is used to call liveStateSyncElement.isEditorAccessRequested() without having to handle the subscription.

The hook will automatically unsubscribe from the subscription when the component dismounts.

import { useEditorAccessRequestHandler } from '@veltdev/react';

export default function YourDocument= () {
	const editorAccessRequested = useEditorAccessRequestHandler();


  // editorAccessRequested == null --> if user is not editor or the request has been canceled
  // editorAccessRequested.requestStatus == 'requested' -> Current user is Editor & access requested by Viewer
  // editorAccessRequestedrequestedBy -> contains data about the User that requested access


  return (
    <div>
        Editor Access Request status: {editorAccessRequested.requestStatus}

        Editor Access Requested by: {editorAccessRequested.requestedBy}
    </div>
  )
}
12

Accept Editor access request

Once the Editor gets an Editor Access request, call ‘acceptEditorAccessRequest()’ method to accept the request.

liveStateSyncElement.acceptEditorAccessRequest();

Use with isEditorAccessRequested():

let subscription = liveStateSyncElement.isEditorAccessRequested().subscribe((data) => {
  // data == null --> if user is not editor or the request has been canceled
  // data.requestStatus == 'requested' -> Current user is Editor & access requested by Viewer
  // data.requestedBy -> contains data about the User that requested access

  if(data.requestStatus === 'requested'){
    liveStateSyncElement.acceptEditorAccessRequest();
  }
});
13

Reject Editor access request

Once the Editor gets an Editor access request, call ‘rejectEditorAccessRequest()’ method to reject the request.

liveStateSyncElement.rejectEditorAccessRequest();

Use with isEditorAccessRequested():

let subscription = liveStateSyncElement.isEditorAccessRequested().subscribe((data) => {
  // data == null --> if user is not editor or the request has been canceled
  // data.requestStatus == 'requested' -> Current user is Editor & access requested by Viewer
  // data.requestedBy -> contains data about the User that requested access

  if(data.requestStatus === 'requested'){
    liveStateSyncElement.rejectEditorAccessRequest();
  }
});
14

To cancel an edit access request by the Viewer

The cancelEditorAccessRequest() method is used to cancel an existing edit access request by the Viewer.

liveStateSyncElement.cancelEditorAccessRequest();
15

To make the current tab editable when the editor has opened multiple tabs

The editCurrentTab() method is used to make the current tab editable when the editor has opened multiple tabs on the same page.

liveStateSyncElement.editCurrentTab();
16

Setting timeout value for automatic editor access transfer after request

The setEditorAccessTimeout() method is used to set the editor access timeout value in seconds.

Default: 5 seconds

liveStateSyncElement.setEditorAccessTimeout(15); // in seconds
17

Enable automatic transfer of edit access after timeout

The enableEditorAccessTransferOnTimeOut() method is used to enable automatic transferring of editor access after timeout.

Default: enabled

liveStateSyncElement.enableEditorAccessTransferOnTimeOut();
18

Disable automatic transfer of edit access after timeout

The disableEditorAccessTransferOnTimeOut() method is used to disable automatic transferring of editor access after timeout.

liveStateSyncElement.disableEditorAccessTransferOnTimeOut();
19

Automatically sync contents of Text Elements

Auto syncs the content of the following Text HTML elements, such that when editor types, the element’s content is synced across all active users currently live on the document.

  • input
  • textarea
  • ContentEditable Divs

To enable syncing, follow these two steps:

  1. Enable Auto Sync State feature:
liveStateSyncElement.enableAutoSyncState();
  1. Set the data-velt-sync-state attribute to true on the Text HTML element you want to sync:
You can set an id to the HTML element to make the syncing more robust.
<textarea id="uniqueId" data-velt-sync-state="true"></textarea>
You must add the data-velt-sync-state attribute to a native HTML element (e.g. input, textarea). It will not work directly on React components.
20

Automatically make HTML Elements readonly or editable

By default in Single Editor Mode, we control making these html elements editable or readonly on the entire DOM:

  • input
  • textarea
  • select
  • button
  • contentEditable divs

For Viewers, the default elements listed above will be disabled, meaning click, mouseup and mousedown events will be prevented.

If there are any other elements that you want us to control beyond the default elements we listed above, such as div, span elements etc, then you can add data-velt-sync-access="true" to that element.

<div data-velt-sync-access="true"></div>
You must add the data-velt-sync-access attribute to native HTML elements (e.g. div, span). It will not work directly on React components.
21

Exclude default HTML elements from Auto Syncing Access

In Single Editor Mode, we control enabling and disabling of certain html elements on the entire DOM. If you want to exclude any of those html elements from this behavior, you can add data-velt-sync-access-disabled="true" to that element.

<button data-velt-sync-access-disabled="true"></button>
You must add the data-velt-sync-access-disabled attribute to native HTML elements (e.g. button, input). It will not work directly on React components.
22

Restrict Single Editor Mode to Specific Parent 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 the singleEditorModeContainerIds() method. It takes in an array of element ID strings. These IDs represent the parent elements within which Single Editor Mode will be enabled.

liveStateElement.singleEditorModeContainerIds(["rightPanel"]);
23

Get Editor Access Timer State

With the useEditorAccessTimer hook, you can get the Editor Access Timer state.

import { useEditorAccessTimer } from '@veltdev/react';

function YourReactComponent {

    const editorAccessTimer = useEditorAccessTimer();

    useEffect(() => {
        if (editorAccessTimer?.state === 'completed') {
            onEditorAccessTimerCompleted();
        }
    }, [editorAccessTimer]);

    const onEditorAccessTimerCompleted = () => {
        // If user is editor, make requester as editor
        acceptEditorAccessRequest();
        // If user is requester, make it as editor
        setUserAsEditor();
    }
}

The editorAccessTimer class has the following schema:

  • state: 'idle' | 'inProgress' | 'completed'
  • durationLeft: number