@lexical/react
Version:
This package provides Lexical components and hooks for React applications.
117 lines (103 loc) • 3.59 kB
text/typescript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import type {Doc} from 'yjs';
import devInvariant from '@lexical/internal/devInvariant';
import {createContext, useContext} from 'react';
/**
* The value stored in the {@link CollaborationContext}: the local user's
* display `name` and cursor `color`, whether collaboration is currently active,
* and the map of Yjs documents shared by the editors under this provider.
*/
export type CollaborationContextType = {
color: string;
isCollabActive: boolean;
name: string;
yjsDocMap: Map<string, Doc>;
};
const entries = [
['Cat', 'rgb(125, 50, 0)'],
['Dog', 'rgb(100, 0, 0)'],
['Rabbit', 'rgb(150, 0, 0)'],
['Frog', 'rgb(200, 0, 0)'],
['Fox', 'rgb(200, 75, 0)'],
['Hedgehog', 'rgb(0, 75, 0)'],
['Pigeon', 'rgb(0, 125, 0)'],
['Squirrel', 'rgb(75, 100, 0)'],
['Bear', 'rgb(125, 100, 0)'],
['Tiger', 'rgb(0, 0, 150)'],
['Leopard', 'rgb(0, 0, 200)'],
['Zebra', 'rgb(0, 0, 250)'],
['Wolf', 'rgb(0, 100, 150)'],
['Owl', 'rgb(0, 100, 100)'],
['Gull', 'rgb(100, 0, 100)'],
['Squid', 'rgb(150, 0, 150)'],
];
// Picked on first use rather than at module scope, where the `Math.random()`
// call is a side effect to bundlers that would pin this module (and the
// table above) into every bundle importing it. It is still picked once and
// shared by every context created afterwards, as before.
let randomEntry: undefined | (typeof entries)[number];
function getRandomEntry(): (typeof entries)[number] {
if (randomEntry === undefined) {
randomEntry = entries[Math.floor(Math.random() * entries.length)];
}
return randomEntry;
}
/**
* The React context that holds the shared {@link CollaborationContextType} for
* collaborative editors. Provide it with {@link LexicalCollaboration} and read
* it with {@link useCollaborationContext}.
*/
export const CollaborationContext =
// Annotated by hand: React's createContext is not a Lexical factory, so the
// build does not annotate it, and an unannotated module-scope call pins the
// module into every bundle.
/* @__PURE__ */ createContext<CollaborationContextType | null>(null);
/**
* @internal
* @__NO_SIDE_EFFECTS__
*/
export function newContext(): CollaborationContextType {
const [name, color] = getRandomEntry();
return {
color,
isCollabActive: false,
name,
yjsDocMap: new Map(),
};
}
// This is here to help the transition post-#7818, however should be removed in a future release as
// a shared context across editors is likely to lead to bugs.
const UNSAFE_GLOBAL_CONTEXT = newContext();
/**
* Reads the current {@link CollaborationContextType} from the nearest
* {@link LexicalCollaboration} provider. Optionally pass `username` and `color`
* to set the local user's display name and cursor color.
*
* @returns The active collaboration context.
*/
export function useCollaborationContext(
username?: string,
color?: string,
): CollaborationContextType {
let collabContext = useContext(CollaborationContext);
devInvariant(
collabContext != null,
'useCollaborationContext: no context provider found',
);
collabContext = collabContext ?? UNSAFE_GLOBAL_CONTEXT;
if (username != null) {
// eslint-disable-next-line react-hooks/immutability
collabContext.name = username;
}
if (color != null) {
// eslint-disable-next-line react-hooks/immutability
collabContext.color = color;
}
return collabContext;
}