@liveblocks/react-flow 3.16.0-flow1 → 3.16.0-flow2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/constants.cjs +33 -0
- package/dist/constants.cjs.map +1 -0
- package/dist/constants.js +29 -0
- package/dist/constants.js.map +1 -0
- package/dist/cursors.cjs +27 -18
- package/dist/cursors.cjs.map +1 -1
- package/dist/cursors.js +29 -20
- package/dist/cursors.js.map +1 -1
- package/dist/flow.cjs +174 -237
- package/dist/flow.cjs.map +1 -1
- package/dist/flow.js +179 -240
- package/dist/flow.js.map +1 -1
- package/dist/helpers.cjs +35 -0
- package/dist/helpers.cjs.map +1 -0
- package/dist/helpers.js +30 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.cjs +3 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +158 -91
- package/dist/index.d.ts +158 -91
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/package.json +6 -18
- package/dist/suspense.cjs +0 -13
- package/dist/suspense.cjs.map +0 -1
- package/dist/suspense.d.cts +0 -149
- package/dist/suspense.d.ts +0 -149
- package/dist/suspense.js +0 -7
- package/dist/suspense.js.map +0 -1
- package/suspense/README.md +0 -5
- package/suspense/package.json +0 -4
package/README.md
CHANGED
|
@@ -4,3 +4,54 @@
|
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
6
|
# `@liveblocks/react-flow`
|
|
7
|
+
|
|
8
|
+
<p>
|
|
9
|
+
<a href="https://npmjs.org/package/@liveblocks/react-flow"><img src="https://img.shields.io/npm/v/@liveblocks/react-flow?style=flat&label=npm&color=c33" alt="NPM" /></a>
|
|
10
|
+
<a href="https://bundlephobia.com/package/@liveblocks/react-flow"><img src="https://img.shields.io/bundlephobia/minzip/@liveblocks/react-flow?style=flat&label=size&color=09f" alt="Size" /></a>
|
|
11
|
+
<a href="https://github.com/liveblocks/liveblocks/blob/main/licenses/LICENSE-APACHE-2.0"><img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="License" /></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
`@liveblocks/react-flow` provides [React](https://reactjs.org/) APIs to
|
|
15
|
+
integrate [React Flow](https://reactflow.dev/) diagrams with Liveblocks—a
|
|
16
|
+
platform to build, host, and scale collaborative applications with zero
|
|
17
|
+
configuration, no maintenance required.
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
npm install @liveblocks/client @liveblocks/react @liveblocks/react-ui @liveblocks/react-flow
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Documentation
|
|
26
|
+
|
|
27
|
+
Read the
|
|
28
|
+
[documentation](https://liveblocks.io/docs/api-reference/liveblocks-react-flow)
|
|
29
|
+
for guides and API references.
|
|
30
|
+
|
|
31
|
+
## Examples
|
|
32
|
+
|
|
33
|
+
Explore our [collaborative examples](https://liveblocks.io/examples) to help you
|
|
34
|
+
get started.
|
|
35
|
+
|
|
36
|
+
> All examples are open-source and live in this repository, within
|
|
37
|
+
> [`/examples`](../../examples).
|
|
38
|
+
|
|
39
|
+
## Releases
|
|
40
|
+
|
|
41
|
+
See the [latest changes](https://github.com/liveblocks/liveblocks/releases) or
|
|
42
|
+
learn more about
|
|
43
|
+
[upcoming releases](https://github.com/liveblocks/liveblocks/milestones).
|
|
44
|
+
|
|
45
|
+
## Community
|
|
46
|
+
|
|
47
|
+
- [Discord](https://liveblocks.io/discord) - To get involved with the Liveblocks
|
|
48
|
+
community, ask questions and share tips.
|
|
49
|
+
- [X](https://x.com/liveblocks) - To receive updates, announcements, blog posts,
|
|
50
|
+
and general Liveblocks tips.
|
|
51
|
+
|
|
52
|
+
## License
|
|
53
|
+
|
|
54
|
+
Licensed under the Apache License 2.0, Copyright © 2021-present
|
|
55
|
+
[Liveblocks](https://liveblocks.io).
|
|
56
|
+
|
|
57
|
+
See [LICENSE](../../licenses/LICENSE-APACHE-2.0) for more information.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_STORAGE_KEY = "flow";
|
|
4
|
+
const NODE_BASE_CONFIG = {
|
|
5
|
+
// Local-only (not synced)
|
|
6
|
+
selected: false,
|
|
7
|
+
dragging: false,
|
|
8
|
+
measured: false,
|
|
9
|
+
resizing: false,
|
|
10
|
+
// Atomic (synced as plain Json)
|
|
11
|
+
position: "atomic",
|
|
12
|
+
sourcePosition: "atomic",
|
|
13
|
+
targetPosition: "atomic",
|
|
14
|
+
extent: "atomic",
|
|
15
|
+
origin: "atomic",
|
|
16
|
+
handles: "atomic"
|
|
17
|
+
// Note: the `data` key is intentionally left out of this base config, as it
|
|
18
|
+
// is expected to be provided by the end user
|
|
19
|
+
};
|
|
20
|
+
const EDGE_BASE_CONFIG = {
|
|
21
|
+
// Local-only (not synced)
|
|
22
|
+
selected: false,
|
|
23
|
+
// Atomic (synced as plain Json)
|
|
24
|
+
markerStart: "atomic",
|
|
25
|
+
markerEnd: "atomic"
|
|
26
|
+
// Note: the `data` key is intentionally left out of this base config, as it
|
|
27
|
+
// is expected to be provided by the end user
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
exports.DEFAULT_STORAGE_KEY = DEFAULT_STORAGE_KEY;
|
|
31
|
+
exports.EDGE_BASE_CONFIG = EDGE_BASE_CONFIG;
|
|
32
|
+
exports.NODE_BASE_CONFIG = NODE_BASE_CONFIG;
|
|
33
|
+
//# sourceMappingURL=constants.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.cjs","sources":["../src/constants.ts"],"sourcesContent":["import type { SyncConfig } from \"@liveblocks/core\";\n\nexport const DEFAULT_STORAGE_KEY = \"flow\";\n\nexport const NODE_BASE_CONFIG = {\n // Local-only (not synced)\n selected: false,\n dragging: false,\n measured: false,\n resizing: false,\n\n // Atomic (synced as plain Json)\n position: \"atomic\",\n sourcePosition: \"atomic\",\n targetPosition: \"atomic\",\n extent: \"atomic\",\n origin: \"atomic\",\n handles: \"atomic\",\n\n // Note: the `data` key is intentionally left out of this base config, as it\n // is expected to be provided by the end user\n} as const satisfies SyncConfig;\n\nexport const EDGE_BASE_CONFIG = {\n // Local-only (not synced)\n selected: false,\n\n // Atomic (synced as plain Json)\n markerStart: \"atomic\",\n markerEnd: \"atomic\",\n\n // Note: the `data` key is intentionally left out of this base config, as it\n // is expected to be provided by the end user\n} as const satisfies SyncConfig;\n"],"names":[],"mappings":";;AAEO,MAAM,mBAAsB,GAAA,OAAA;AAE5B,MAAM,gBAAmB,GAAA;AAAA;AAAA,EAE9B,QAAU,EAAA,KAAA;AAAA,EACV,QAAU,EAAA,KAAA;AAAA,EACV,QAAU,EAAA,KAAA;AAAA,EACV,QAAU,EAAA,KAAA;AAAA;AAAA,EAGV,QAAU,EAAA,QAAA;AAAA,EACV,cAAgB,EAAA,QAAA;AAAA,EAChB,cAAgB,EAAA,QAAA;AAAA,EAChB,MAAQ,EAAA,QAAA;AAAA,EACR,MAAQ,EAAA,QAAA;AAAA,EACR,OAAS,EAAA,QAAA;AAAA;AAAA;AAIX,EAAA;AAEO,MAAM,gBAAmB,GAAA;AAAA;AAAA,EAE9B,QAAU,EAAA,KAAA;AAAA;AAAA,EAGV,WAAa,EAAA,QAAA;AAAA,EACb,SAAW,EAAA,QAAA;AAAA;AAAA;AAIb;;;;;;"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const DEFAULT_STORAGE_KEY = "flow";
|
|
2
|
+
const NODE_BASE_CONFIG = {
|
|
3
|
+
// Local-only (not synced)
|
|
4
|
+
selected: false,
|
|
5
|
+
dragging: false,
|
|
6
|
+
measured: false,
|
|
7
|
+
resizing: false,
|
|
8
|
+
// Atomic (synced as plain Json)
|
|
9
|
+
position: "atomic",
|
|
10
|
+
sourcePosition: "atomic",
|
|
11
|
+
targetPosition: "atomic",
|
|
12
|
+
extent: "atomic",
|
|
13
|
+
origin: "atomic",
|
|
14
|
+
handles: "atomic"
|
|
15
|
+
// Note: the `data` key is intentionally left out of this base config, as it
|
|
16
|
+
// is expected to be provided by the end user
|
|
17
|
+
};
|
|
18
|
+
const EDGE_BASE_CONFIG = {
|
|
19
|
+
// Local-only (not synced)
|
|
20
|
+
selected: false,
|
|
21
|
+
// Atomic (synced as plain Json)
|
|
22
|
+
markerStart: "atomic",
|
|
23
|
+
markerEnd: "atomic"
|
|
24
|
+
// Note: the `data` key is intentionally left out of this base config, as it
|
|
25
|
+
// is expected to be provided by the end user
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export { DEFAULT_STORAGE_KEY, EDGE_BASE_CONFIG, NODE_BASE_CONFIG };
|
|
29
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sources":["../src/constants.ts"],"sourcesContent":["import type { SyncConfig } from \"@liveblocks/core\";\n\nexport const DEFAULT_STORAGE_KEY = \"flow\";\n\nexport const NODE_BASE_CONFIG = {\n // Local-only (not synced)\n selected: false,\n dragging: false,\n measured: false,\n resizing: false,\n\n // Atomic (synced as plain Json)\n position: \"atomic\",\n sourcePosition: \"atomic\",\n targetPosition: \"atomic\",\n extent: \"atomic\",\n origin: \"atomic\",\n handles: \"atomic\",\n\n // Note: the `data` key is intentionally left out of this base config, as it\n // is expected to be provided by the end user\n} as const satisfies SyncConfig;\n\nexport const EDGE_BASE_CONFIG = {\n // Local-only (not synced)\n selected: false,\n\n // Atomic (synced as plain Json)\n markerStart: \"atomic\",\n markerEnd: \"atomic\",\n\n // Note: the `data` key is intentionally left out of this base config, as it\n // is expected to be provided by the end user\n} as const satisfies SyncConfig;\n"],"names":[],"mappings":"AAEO,MAAM,mBAAsB,GAAA,OAAA;AAE5B,MAAM,gBAAmB,GAAA;AAAA;AAAA,EAE9B,QAAU,EAAA,KAAA;AAAA,EACV,QAAU,EAAA,KAAA;AAAA,EACV,QAAU,EAAA,KAAA;AAAA,EACV,QAAU,EAAA,KAAA;AAAA;AAAA,EAGV,QAAU,EAAA,QAAA;AAAA,EACV,cAAgB,EAAA,QAAA;AAAA,EAChB,cAAgB,EAAA,QAAA;AAAA,EAChB,MAAQ,EAAA,QAAA;AAAA,EACR,MAAQ,EAAA,QAAA;AAAA,EACR,OAAS,EAAA,QAAA;AAAA;AAAA;AAIX,EAAA;AAEO,MAAM,gBAAmB,GAAA;AAAA;AAAA,EAE9B,QAAU,EAAA,KAAA;AAAA;AAAA,EAGV,WAAa,EAAA,QAAA;AAAA,EACb,SAAW,EAAA,QAAA;AAAA;AAAA;AAIb;;;;"}
|
package/dist/cursors.cjs
CHANGED
|
@@ -19,18 +19,24 @@ function $coordinates(value) {
|
|
|
19
19
|
}
|
|
20
20
|
return void 0;
|
|
21
21
|
}
|
|
22
|
+
function DefaultCursorWithUserInfo({ userId }) {
|
|
23
|
+
const { user, isLoading } = react.useUser(userId);
|
|
24
|
+
const color = $string(user?.color);
|
|
25
|
+
const name = $string(user?.name);
|
|
26
|
+
if (isLoading) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return /* @__PURE__ */ jsxRuntime.jsx(reactUi.Cursor, { color, label: name });
|
|
30
|
+
}
|
|
22
31
|
function PresenceCursor({
|
|
23
32
|
connectionId,
|
|
24
|
-
presenceKey
|
|
33
|
+
presenceKey,
|
|
34
|
+
Cursor
|
|
25
35
|
}) {
|
|
26
36
|
const room = react.useRoom();
|
|
27
37
|
const cursorRef = react$1.useRef(null);
|
|
28
38
|
const reactFlowStoreApi = react$2.useStoreApi();
|
|
29
39
|
const userId = react.useOther(connectionId, (other) => $string(other.id));
|
|
30
|
-
const { user, isLoading } = react.useUser(userId ?? "");
|
|
31
|
-
const hasUserInfo = userId !== void 0 && !isLoading;
|
|
32
|
-
const color = $string(user?.color);
|
|
33
|
-
const name = $string(user?.name);
|
|
34
40
|
_private.useLayoutEffect(() => {
|
|
35
41
|
const spring = _private$1.makeCursorSpring();
|
|
36
42
|
function update() {
|
|
@@ -40,7 +46,7 @@ function PresenceCursor({
|
|
|
40
46
|
if (!element) {
|
|
41
47
|
return;
|
|
42
48
|
}
|
|
43
|
-
if (
|
|
49
|
+
if (coordinates === null) {
|
|
44
50
|
element.style.transform = "translate3d(0, 0, 0)";
|
|
45
51
|
element.style.display = "none";
|
|
46
52
|
return;
|
|
@@ -68,19 +74,21 @@ function PresenceCursor({
|
|
|
68
74
|
unsubscribeTransform();
|
|
69
75
|
unsubscribeOther();
|
|
70
76
|
};
|
|
71
|
-
}, [room, connectionId, presenceKey,
|
|
72
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
73
|
-
reactUi.Cursor,
|
|
74
|
-
{
|
|
75
|
-
color,
|
|
76
|
-
label: name,
|
|
77
|
-
ref: cursorRef,
|
|
78
|
-
style: { display: "none" }
|
|
79
|
-
}
|
|
80
|
-
);
|
|
77
|
+
}, [room, connectionId, presenceKey, reactFlowStoreApi]);
|
|
78
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { ref: cursorRef, style: { display: "none" }, children: userId ? /* @__PURE__ */ jsxRuntime.jsx(Cursor, { userId, connectionId }) : /* @__PURE__ */ jsxRuntime.jsx(reactUi.Cursor, {}) });
|
|
81
79
|
}
|
|
82
80
|
const Cursors = react$1.forwardRef(
|
|
83
|
-
({
|
|
81
|
+
({
|
|
82
|
+
className,
|
|
83
|
+
children,
|
|
84
|
+
presenceKey = DEFAULT_PRESENCE_KEY,
|
|
85
|
+
components,
|
|
86
|
+
...props
|
|
87
|
+
}, forwardedRef) => {
|
|
88
|
+
const Cursor = _private$1.useStableComponent(
|
|
89
|
+
components?.Cursor,
|
|
90
|
+
DefaultCursorWithUserInfo
|
|
91
|
+
);
|
|
84
92
|
const reactFlow = react$2.useReactFlow();
|
|
85
93
|
const updateMyPresence = react.useUpdateMyPresence();
|
|
86
94
|
const othersConnectionIds = react.useOthersConnectionIds();
|
|
@@ -133,7 +141,8 @@ const Cursors = react$1.forwardRef(
|
|
|
133
141
|
PresenceCursor,
|
|
134
142
|
{
|
|
135
143
|
connectionId,
|
|
136
|
-
presenceKey
|
|
144
|
+
presenceKey,
|
|
145
|
+
Cursor
|
|
137
146
|
},
|
|
138
147
|
connectionId
|
|
139
148
|
)),
|
package/dist/cursors.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursors.cjs","sources":["../src/cursors.tsx"],"sourcesContent":["import { isPlainObject } from \"@liveblocks/core\";\nimport {\n useOther,\n useOthersConnectionIds,\n useRoom,\n useUpdateMyPresence,\n useUser,\n} from \"@liveblocks/react\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport { Cursor } from \"@liveblocks/react-ui\";\nimport { cn, makeCursorSpring } from \"@liveblocks/react-ui/_private\";\nimport { useReactFlow, useStore, useStoreApi } from \"@xyflow/react\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport { forwardRef, useCallback, useEffect, useRef } from \"react\";\n\nconst DEFAULT_PRESENCE_KEY = \"cursor\";\n\nexport interface CursorsProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The key used to store the cursors in users' Presence.\n *\n * Defaults to `\"cursor\"`.\n */\n presenceKey?: string;\n}\n\ntype Coordinates = {\n x: number;\n y: number;\n};\n\nfunction $string(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction $coordinates(value: unknown): Coordinates | undefined {\n if (\n isPlainObject(value) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\"\n ) {\n return value as Coordinates;\n }\n\n return undefined;\n}\n\nfunction PresenceCursor({\n connectionId,\n presenceKey,\n}: {\n connectionId: number;\n presenceKey: string;\n}) {\n const room = useRoom();\n const cursorRef = useRef<HTMLDivElement>(null);\n const reactFlowStoreApi = useStoreApi();\n const userId = useOther(connectionId, (other) => $string(other.id));\n const { user, isLoading } = useUser(userId ?? \"\");\n const hasUserInfo = userId !== undefined && !isLoading;\n const color = $string(user?.color);\n const name = $string(user?.name);\n\n useLayoutEffect(() => {\n const spring = makeCursorSpring();\n\n function update() {\n const element = cursorRef.current;\n const coordinates = spring.get();\n const [panX, panY, zoom] = reactFlowStoreApi.getState().transform;\n\n if (!element) {\n return;\n }\n\n if (!hasUserInfo || coordinates === null) {\n element.style.transform = \"translate3d(0, 0, 0)\";\n element.style.display = \"none\";\n return;\n }\n\n element.style.transform = `translate3d(${coordinates.x * zoom + panX}px, ${coordinates.y * zoom + panY}px, 0)`;\n element.style.display = \"\";\n }\n\n update();\n\n const unsubscribeSpring = spring.subscribe(update);\n const unsubscribeTransform = reactFlowStoreApi.subscribe(\n (state, previousState) => {\n // Update positions whenever the canvas is panned or zoomed.\n if (state.transform !== previousState.transform) {\n update();\n }\n }\n );\n const unsubscribeOther = room.events.others.subscribe(({ others }) => {\n const other = others.find((other) => other.connectionId === connectionId);\n const cursor = $coordinates(other?.presence[presenceKey]);\n\n spring.set(cursor ?? null);\n });\n\n return () => {\n spring.dispose();\n unsubscribeSpring();\n unsubscribeTransform();\n unsubscribeOther();\n };\n }, [room, connectionId, presenceKey, hasUserInfo, reactFlowStoreApi]);\n\n return (\n <Cursor\n color={color}\n label={name}\n ref={cursorRef}\n style={{ display: \"none\" }}\n />\n );\n}\n\n/**\n * Displays other users' cursors inside a React Flow canvas and stores the\n * current user's cursor in Presence as `{ cursor: { x, y } }`.\n *\n * Cursor coordinates are kept in React Flow canvas space, so panning moves\n * them correctly while zooming only affects their position, not their size.\n */\nexport const Cursors = forwardRef<HTMLDivElement, CursorsProps>(\n (\n { className, children, presenceKey = DEFAULT_PRESENCE_KEY, ...props },\n forwardedRef\n ) => {\n const reactFlow = useReactFlow();\n const updateMyPresence = useUpdateMyPresence();\n const othersConnectionIds = useOthersConnectionIds();\n const reactFlowDomNode = useStore((state) => state.domNode);\n const reactFlowStoreApi = useStoreApi();\n\n const handlePointerMove = useCallback(\n (event: PointerEvent) => {\n const isPanning = reactFlowStoreApi.getState().paneDragging;\n\n if (isPanning) {\n return;\n }\n\n updateMyPresence({\n [presenceKey]: reactFlow.screenToFlowPosition({\n x: event.clientX,\n y: event.clientY,\n }),\n });\n },\n [updateMyPresence, presenceKey, reactFlow, reactFlowStoreApi]\n );\n\n const handlePointerLeave = useCallback(() => {\n updateMyPresence({ [presenceKey]: null });\n }, [updateMyPresence, presenceKey]);\n\n useEffect(() => {\n if (!reactFlowDomNode) {\n return;\n }\n\n reactFlowDomNode.addEventListener(\"pointermove\", handlePointerMove);\n reactFlowDomNode.addEventListener(\"pointerleave\", handlePointerLeave);\n window.addEventListener(\"blur\", handlePointerLeave);\n\n return () => {\n reactFlowDomNode.removeEventListener(\"pointermove\", handlePointerMove);\n reactFlowDomNode.removeEventListener(\n \"pointerleave\",\n handlePointerLeave\n );\n window.removeEventListener(\"blur\", handlePointerLeave);\n handlePointerLeave();\n };\n }, [reactFlowDomNode, handlePointerMove, handlePointerLeave]);\n\n return (\n <div\n aria-hidden\n className={cn(\"lb-root lb-react-flow-cursors\", className)}\n {...props}\n ref={forwardedRef}\n >\n {othersConnectionIds.map((connectionId) => (\n <PresenceCursor\n key={connectionId}\n connectionId={connectionId}\n presenceKey={presenceKey}\n />\n ))}\n\n {children}\n </div>\n );\n }\n);\n"],"names":["isPlainObject","useRoom","useRef","useStoreApi","useOther","useUser","useLayoutEffect","makeCursorSpring","other","jsx","Cursor","forwardRef","useReactFlow","useUpdateMyPresence","useOthersConnectionIds","useStore","useCallback","useEffect","jsxs","cn"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cursors.cjs","sources":["../src/cursors.tsx"],"sourcesContent":["import { isPlainObject } from \"@liveblocks/core\";\nimport {\n useOther,\n useOthersConnectionIds,\n useRoom,\n useUpdateMyPresence,\n useUser,\n} from \"@liveblocks/react\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport { Cursor as DefaultCursor } from \"@liveblocks/react-ui\";\nimport {\n cn,\n makeCursorSpring,\n useStableComponent,\n} from \"@liveblocks/react-ui/_private\";\nimport { useReactFlow, useStore, useStoreApi } from \"@xyflow/react\";\nimport type { ComponentPropsWithoutRef, ComponentType } from \"react\";\nimport { forwardRef, useCallback, useEffect, useRef } from \"react\";\n\nconst DEFAULT_PRESENCE_KEY = \"cursor\";\n\nexport interface CursorsCursorProps {\n /**\n * The user ID for this cursor.\n */\n userId: string;\n\n /**\n * The connection ID for this cursor.\n */\n connectionId: number;\n}\n\ninterface CursorsComponents {\n /**\n * The component used to display each cursor.\n */\n Cursor: ComponentType<CursorsCursorProps>;\n}\n\nexport interface CursorsProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The key used to store the cursors in users' Presence.\n *\n * Defaults to `\"cursor\"`.\n */\n presenceKey?: string;\n\n /**\n * Override the component's components.\n */\n components?: Partial<CursorsComponents>;\n}\n\ntype Coordinates = {\n x: number;\n y: number;\n};\n\nfunction $string(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction $coordinates(value: unknown): Coordinates | undefined {\n if (\n isPlainObject(value) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\"\n ) {\n return value as Coordinates;\n }\n\n return undefined;\n}\n\nfunction DefaultCursorWithUserInfo({ userId }: CursorsCursorProps) {\n const { user, isLoading } = useUser(userId);\n const color = $string(user?.color);\n const name = $string(user?.name);\n\n if (isLoading) {\n return null;\n }\n\n return <DefaultCursor color={color} label={name} />;\n}\n\nfunction PresenceCursor({\n connectionId,\n presenceKey,\n Cursor,\n}: {\n connectionId: number;\n presenceKey: string;\n Cursor: ComponentType<CursorsCursorProps>;\n}) {\n const room = useRoom();\n const cursorRef = useRef<HTMLDivElement>(null);\n const reactFlowStoreApi = useStoreApi();\n const userId = useOther(connectionId, (other) => $string(other.id));\n\n useLayoutEffect(() => {\n const spring = makeCursorSpring();\n\n function update() {\n const element = cursorRef.current;\n const coordinates = spring.get();\n const [panX, panY, zoom] = reactFlowStoreApi.getState().transform;\n\n if (!element) {\n return;\n }\n\n if (coordinates === null) {\n element.style.transform = \"translate3d(0, 0, 0)\";\n element.style.display = \"none\";\n return;\n }\n\n element.style.transform = `translate3d(${coordinates.x * zoom + panX}px, ${coordinates.y * zoom + panY}px, 0)`;\n element.style.display = \"\";\n }\n\n update();\n\n const unsubscribeSpring = spring.subscribe(update);\n const unsubscribeTransform = reactFlowStoreApi.subscribe(\n (state, previousState) => {\n // Update positions whenever the canvas is panned or zoomed.\n if (state.transform !== previousState.transform) {\n update();\n }\n }\n );\n const unsubscribeOther = room.events.others.subscribe(({ others }) => {\n const other = others.find((other) => other.connectionId === connectionId);\n const cursor = $coordinates(other?.presence[presenceKey]);\n\n spring.set(cursor ?? null);\n });\n\n return () => {\n spring.dispose();\n unsubscribeSpring();\n unsubscribeTransform();\n unsubscribeOther();\n };\n }, [room, connectionId, presenceKey, reactFlowStoreApi]);\n\n return (\n <div ref={cursorRef} style={{ display: \"none\" }}>\n {userId ? (\n <Cursor userId={userId} connectionId={connectionId} />\n ) : (\n <DefaultCursor />\n )}\n </div>\n );\n}\n\n/**\n * Displays other users' cursors inside a React Flow canvas and stores the\n * current user's cursor in Presence as `{ cursor: { x, y } }`.\n *\n * Cursor coordinates are kept in React Flow canvas space, so panning moves\n * them correctly while zooming only affects their position, not their size.\n */\nexport const Cursors = forwardRef<HTMLDivElement, CursorsProps>(\n (\n {\n className,\n children,\n presenceKey = DEFAULT_PRESENCE_KEY,\n components,\n ...props\n },\n forwardedRef\n ) => {\n const Cursor = useStableComponent(\n components?.Cursor,\n DefaultCursorWithUserInfo\n );\n const reactFlow = useReactFlow();\n const updateMyPresence = useUpdateMyPresence();\n const othersConnectionIds = useOthersConnectionIds();\n const reactFlowDomNode = useStore((state) => state.domNode);\n const reactFlowStoreApi = useStoreApi();\n\n const handlePointerMove = useCallback(\n (event: PointerEvent) => {\n const isPanning = reactFlowStoreApi.getState().paneDragging;\n\n if (isPanning) {\n return;\n }\n\n updateMyPresence({\n [presenceKey]: reactFlow.screenToFlowPosition({\n x: event.clientX,\n y: event.clientY,\n }),\n });\n },\n [updateMyPresence, presenceKey, reactFlow, reactFlowStoreApi]\n );\n\n const handlePointerLeave = useCallback(() => {\n updateMyPresence({ [presenceKey]: null });\n }, [updateMyPresence, presenceKey]);\n\n useEffect(() => {\n if (!reactFlowDomNode) {\n return;\n }\n\n reactFlowDomNode.addEventListener(\"pointermove\", handlePointerMove);\n reactFlowDomNode.addEventListener(\"pointerleave\", handlePointerLeave);\n window.addEventListener(\"blur\", handlePointerLeave);\n\n return () => {\n reactFlowDomNode.removeEventListener(\"pointermove\", handlePointerMove);\n reactFlowDomNode.removeEventListener(\n \"pointerleave\",\n handlePointerLeave\n );\n window.removeEventListener(\"blur\", handlePointerLeave);\n handlePointerLeave();\n };\n }, [reactFlowDomNode, handlePointerMove, handlePointerLeave]);\n\n return (\n <div\n aria-hidden\n className={cn(\"lb-root lb-react-flow-cursors\", className)}\n {...props}\n ref={forwardedRef}\n >\n {othersConnectionIds.map((connectionId) => (\n <PresenceCursor\n key={connectionId}\n connectionId={connectionId}\n presenceKey={presenceKey}\n Cursor={Cursor}\n />\n ))}\n\n {children}\n </div>\n );\n }\n);\n"],"names":["isPlainObject","useUser","jsx","DefaultCursor","useRoom","useRef","useStoreApi","useOther","useLayoutEffect","makeCursorSpring","other","forwardRef","useStableComponent","useReactFlow","useUpdateMyPresence","useOthersConnectionIds","useStore","useCallback","useEffect","jsxs","cn"],"mappings":"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"}
|
package/dist/cursors.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
2
2
|
import { isPlainObject } from '@liveblocks/core';
|
|
3
|
-
import { useRoom, useOther,
|
|
3
|
+
import { useUser, useRoom, useOther, useUpdateMyPresence, useOthersConnectionIds } from '@liveblocks/react';
|
|
4
4
|
import { useLayoutEffect } from '@liveblocks/react/_private';
|
|
5
5
|
import { Cursor } from '@liveblocks/react-ui';
|
|
6
|
-
import { makeCursorSpring, cn } from '@liveblocks/react-ui/_private';
|
|
6
|
+
import { makeCursorSpring, useStableComponent, cn } from '@liveblocks/react-ui/_private';
|
|
7
7
|
import { useStoreApi, useReactFlow, useStore } from '@xyflow/react';
|
|
8
8
|
import { useRef, forwardRef, useCallback, useEffect } from 'react';
|
|
9
9
|
|
|
@@ -17,18 +17,24 @@ function $coordinates(value) {
|
|
|
17
17
|
}
|
|
18
18
|
return void 0;
|
|
19
19
|
}
|
|
20
|
+
function DefaultCursorWithUserInfo({ userId }) {
|
|
21
|
+
const { user, isLoading } = useUser(userId);
|
|
22
|
+
const color = $string(user?.color);
|
|
23
|
+
const name = $string(user?.name);
|
|
24
|
+
if (isLoading) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return /* @__PURE__ */ jsx(Cursor, { color, label: name });
|
|
28
|
+
}
|
|
20
29
|
function PresenceCursor({
|
|
21
30
|
connectionId,
|
|
22
|
-
presenceKey
|
|
31
|
+
presenceKey,
|
|
32
|
+
Cursor: Cursor$1
|
|
23
33
|
}) {
|
|
24
34
|
const room = useRoom();
|
|
25
35
|
const cursorRef = useRef(null);
|
|
26
36
|
const reactFlowStoreApi = useStoreApi();
|
|
27
37
|
const userId = useOther(connectionId, (other) => $string(other.id));
|
|
28
|
-
const { user, isLoading } = useUser(userId ?? "");
|
|
29
|
-
const hasUserInfo = userId !== void 0 && !isLoading;
|
|
30
|
-
const color = $string(user?.color);
|
|
31
|
-
const name = $string(user?.name);
|
|
32
38
|
useLayoutEffect(() => {
|
|
33
39
|
const spring = makeCursorSpring();
|
|
34
40
|
function update() {
|
|
@@ -38,7 +44,7 @@ function PresenceCursor({
|
|
|
38
44
|
if (!element) {
|
|
39
45
|
return;
|
|
40
46
|
}
|
|
41
|
-
if (
|
|
47
|
+
if (coordinates === null) {
|
|
42
48
|
element.style.transform = "translate3d(0, 0, 0)";
|
|
43
49
|
element.style.display = "none";
|
|
44
50
|
return;
|
|
@@ -66,19 +72,21 @@ function PresenceCursor({
|
|
|
66
72
|
unsubscribeTransform();
|
|
67
73
|
unsubscribeOther();
|
|
68
74
|
};
|
|
69
|
-
}, [room, connectionId, presenceKey,
|
|
70
|
-
return /* @__PURE__ */ jsx(
|
|
71
|
-
Cursor,
|
|
72
|
-
{
|
|
73
|
-
color,
|
|
74
|
-
label: name,
|
|
75
|
-
ref: cursorRef,
|
|
76
|
-
style: { display: "none" }
|
|
77
|
-
}
|
|
78
|
-
);
|
|
75
|
+
}, [room, connectionId, presenceKey, reactFlowStoreApi]);
|
|
76
|
+
return /* @__PURE__ */ jsx("div", { ref: cursorRef, style: { display: "none" }, children: userId ? /* @__PURE__ */ jsx(Cursor$1, { userId, connectionId }) : /* @__PURE__ */ jsx(Cursor, {}) });
|
|
79
77
|
}
|
|
80
78
|
const Cursors = forwardRef(
|
|
81
|
-
({
|
|
79
|
+
({
|
|
80
|
+
className,
|
|
81
|
+
children,
|
|
82
|
+
presenceKey = DEFAULT_PRESENCE_KEY,
|
|
83
|
+
components,
|
|
84
|
+
...props
|
|
85
|
+
}, forwardedRef) => {
|
|
86
|
+
const Cursor = useStableComponent(
|
|
87
|
+
components?.Cursor,
|
|
88
|
+
DefaultCursorWithUserInfo
|
|
89
|
+
);
|
|
82
90
|
const reactFlow = useReactFlow();
|
|
83
91
|
const updateMyPresence = useUpdateMyPresence();
|
|
84
92
|
const othersConnectionIds = useOthersConnectionIds();
|
|
@@ -131,7 +139,8 @@ const Cursors = forwardRef(
|
|
|
131
139
|
PresenceCursor,
|
|
132
140
|
{
|
|
133
141
|
connectionId,
|
|
134
|
-
presenceKey
|
|
142
|
+
presenceKey,
|
|
143
|
+
Cursor
|
|
135
144
|
},
|
|
136
145
|
connectionId
|
|
137
146
|
)),
|
package/dist/cursors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cursors.js","sources":["../src/cursors.tsx"],"sourcesContent":["import { isPlainObject } from \"@liveblocks/core\";\nimport {\n useOther,\n useOthersConnectionIds,\n useRoom,\n useUpdateMyPresence,\n useUser,\n} from \"@liveblocks/react\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport { Cursor } from \"@liveblocks/react-ui\";\nimport { cn, makeCursorSpring } from \"@liveblocks/react-ui/_private\";\nimport { useReactFlow, useStore, useStoreApi } from \"@xyflow/react\";\nimport type { ComponentPropsWithoutRef } from \"react\";\nimport { forwardRef, useCallback, useEffect, useRef } from \"react\";\n\nconst DEFAULT_PRESENCE_KEY = \"cursor\";\n\nexport interface CursorsProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The key used to store the cursors in users' Presence.\n *\n * Defaults to `\"cursor\"`.\n */\n presenceKey?: string;\n}\n\ntype Coordinates = {\n x: number;\n y: number;\n};\n\nfunction $string(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction $coordinates(value: unknown): Coordinates | undefined {\n if (\n isPlainObject(value) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\"\n ) {\n return value as Coordinates;\n }\n\n return undefined;\n}\n\nfunction PresenceCursor({\n connectionId,\n presenceKey,\n}: {\n connectionId: number;\n presenceKey: string;\n}) {\n const room = useRoom();\n const cursorRef = useRef<HTMLDivElement>(null);\n const reactFlowStoreApi = useStoreApi();\n const userId = useOther(connectionId, (other) => $string(other.id));\n const { user, isLoading } = useUser(userId ?? \"\");\n const hasUserInfo = userId !== undefined && !isLoading;\n const color = $string(user?.color);\n const name = $string(user?.name);\n\n useLayoutEffect(() => {\n const spring = makeCursorSpring();\n\n function update() {\n const element = cursorRef.current;\n const coordinates = spring.get();\n const [panX, panY, zoom] = reactFlowStoreApi.getState().transform;\n\n if (!element) {\n return;\n }\n\n if (!hasUserInfo || coordinates === null) {\n element.style.transform = \"translate3d(0, 0, 0)\";\n element.style.display = \"none\";\n return;\n }\n\n element.style.transform = `translate3d(${coordinates.x * zoom + panX}px, ${coordinates.y * zoom + panY}px, 0)`;\n element.style.display = \"\";\n }\n\n update();\n\n const unsubscribeSpring = spring.subscribe(update);\n const unsubscribeTransform = reactFlowStoreApi.subscribe(\n (state, previousState) => {\n // Update positions whenever the canvas is panned or zoomed.\n if (state.transform !== previousState.transform) {\n update();\n }\n }\n );\n const unsubscribeOther = room.events.others.subscribe(({ others }) => {\n const other = others.find((other) => other.connectionId === connectionId);\n const cursor = $coordinates(other?.presence[presenceKey]);\n\n spring.set(cursor ?? null);\n });\n\n return () => {\n spring.dispose();\n unsubscribeSpring();\n unsubscribeTransform();\n unsubscribeOther();\n };\n }, [room, connectionId, presenceKey, hasUserInfo, reactFlowStoreApi]);\n\n return (\n <Cursor\n color={color}\n label={name}\n ref={cursorRef}\n style={{ display: \"none\" }}\n />\n );\n}\n\n/**\n * Displays other users' cursors inside a React Flow canvas and stores the\n * current user's cursor in Presence as `{ cursor: { x, y } }`.\n *\n * Cursor coordinates are kept in React Flow canvas space, so panning moves\n * them correctly while zooming only affects their position, not their size.\n */\nexport const Cursors = forwardRef<HTMLDivElement, CursorsProps>(\n (\n { className, children, presenceKey = DEFAULT_PRESENCE_KEY, ...props },\n forwardedRef\n ) => {\n const reactFlow = useReactFlow();\n const updateMyPresence = useUpdateMyPresence();\n const othersConnectionIds = useOthersConnectionIds();\n const reactFlowDomNode = useStore((state) => state.domNode);\n const reactFlowStoreApi = useStoreApi();\n\n const handlePointerMove = useCallback(\n (event: PointerEvent) => {\n const isPanning = reactFlowStoreApi.getState().paneDragging;\n\n if (isPanning) {\n return;\n }\n\n updateMyPresence({\n [presenceKey]: reactFlow.screenToFlowPosition({\n x: event.clientX,\n y: event.clientY,\n }),\n });\n },\n [updateMyPresence, presenceKey, reactFlow, reactFlowStoreApi]\n );\n\n const handlePointerLeave = useCallback(() => {\n updateMyPresence({ [presenceKey]: null });\n }, [updateMyPresence, presenceKey]);\n\n useEffect(() => {\n if (!reactFlowDomNode) {\n return;\n }\n\n reactFlowDomNode.addEventListener(\"pointermove\", handlePointerMove);\n reactFlowDomNode.addEventListener(\"pointerleave\", handlePointerLeave);\n window.addEventListener(\"blur\", handlePointerLeave);\n\n return () => {\n reactFlowDomNode.removeEventListener(\"pointermove\", handlePointerMove);\n reactFlowDomNode.removeEventListener(\n \"pointerleave\",\n handlePointerLeave\n );\n window.removeEventListener(\"blur\", handlePointerLeave);\n handlePointerLeave();\n };\n }, [reactFlowDomNode, handlePointerMove, handlePointerLeave]);\n\n return (\n <div\n aria-hidden\n className={cn(\"lb-root lb-react-flow-cursors\", className)}\n {...props}\n ref={forwardedRef}\n >\n {othersConnectionIds.map((connectionId) => (\n <PresenceCursor\n key={connectionId}\n connectionId={connectionId}\n presenceKey={presenceKey}\n />\n ))}\n\n {children}\n </div>\n );\n }\n);\n"],"names":["other"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cursors.js","sources":["../src/cursors.tsx"],"sourcesContent":["import { isPlainObject } from \"@liveblocks/core\";\nimport {\n useOther,\n useOthersConnectionIds,\n useRoom,\n useUpdateMyPresence,\n useUser,\n} from \"@liveblocks/react\";\nimport { useLayoutEffect } from \"@liveblocks/react/_private\";\nimport { Cursor as DefaultCursor } from \"@liveblocks/react-ui\";\nimport {\n cn,\n makeCursorSpring,\n useStableComponent,\n} from \"@liveblocks/react-ui/_private\";\nimport { useReactFlow, useStore, useStoreApi } from \"@xyflow/react\";\nimport type { ComponentPropsWithoutRef, ComponentType } from \"react\";\nimport { forwardRef, useCallback, useEffect, useRef } from \"react\";\n\nconst DEFAULT_PRESENCE_KEY = \"cursor\";\n\nexport interface CursorsCursorProps {\n /**\n * The user ID for this cursor.\n */\n userId: string;\n\n /**\n * The connection ID for this cursor.\n */\n connectionId: number;\n}\n\ninterface CursorsComponents {\n /**\n * The component used to display each cursor.\n */\n Cursor: ComponentType<CursorsCursorProps>;\n}\n\nexport interface CursorsProps extends ComponentPropsWithoutRef<\"div\"> {\n /**\n * The key used to store the cursors in users' Presence.\n *\n * Defaults to `\"cursor\"`.\n */\n presenceKey?: string;\n\n /**\n * Override the component's components.\n */\n components?: Partial<CursorsComponents>;\n}\n\ntype Coordinates = {\n x: number;\n y: number;\n};\n\nfunction $string(value: unknown): string | undefined {\n return typeof value === \"string\" ? value : undefined;\n}\n\nfunction $coordinates(value: unknown): Coordinates | undefined {\n if (\n isPlainObject(value) &&\n typeof value.x === \"number\" &&\n typeof value.y === \"number\"\n ) {\n return value as Coordinates;\n }\n\n return undefined;\n}\n\nfunction DefaultCursorWithUserInfo({ userId }: CursorsCursorProps) {\n const { user, isLoading } = useUser(userId);\n const color = $string(user?.color);\n const name = $string(user?.name);\n\n if (isLoading) {\n return null;\n }\n\n return <DefaultCursor color={color} label={name} />;\n}\n\nfunction PresenceCursor({\n connectionId,\n presenceKey,\n Cursor,\n}: {\n connectionId: number;\n presenceKey: string;\n Cursor: ComponentType<CursorsCursorProps>;\n}) {\n const room = useRoom();\n const cursorRef = useRef<HTMLDivElement>(null);\n const reactFlowStoreApi = useStoreApi();\n const userId = useOther(connectionId, (other) => $string(other.id));\n\n useLayoutEffect(() => {\n const spring = makeCursorSpring();\n\n function update() {\n const element = cursorRef.current;\n const coordinates = spring.get();\n const [panX, panY, zoom] = reactFlowStoreApi.getState().transform;\n\n if (!element) {\n return;\n }\n\n if (coordinates === null) {\n element.style.transform = \"translate3d(0, 0, 0)\";\n element.style.display = \"none\";\n return;\n }\n\n element.style.transform = `translate3d(${coordinates.x * zoom + panX}px, ${coordinates.y * zoom + panY}px, 0)`;\n element.style.display = \"\";\n }\n\n update();\n\n const unsubscribeSpring = spring.subscribe(update);\n const unsubscribeTransform = reactFlowStoreApi.subscribe(\n (state, previousState) => {\n // Update positions whenever the canvas is panned or zoomed.\n if (state.transform !== previousState.transform) {\n update();\n }\n }\n );\n const unsubscribeOther = room.events.others.subscribe(({ others }) => {\n const other = others.find((other) => other.connectionId === connectionId);\n const cursor = $coordinates(other?.presence[presenceKey]);\n\n spring.set(cursor ?? null);\n });\n\n return () => {\n spring.dispose();\n unsubscribeSpring();\n unsubscribeTransform();\n unsubscribeOther();\n };\n }, [room, connectionId, presenceKey, reactFlowStoreApi]);\n\n return (\n <div ref={cursorRef} style={{ display: \"none\" }}>\n {userId ? (\n <Cursor userId={userId} connectionId={connectionId} />\n ) : (\n <DefaultCursor />\n )}\n </div>\n );\n}\n\n/**\n * Displays other users' cursors inside a React Flow canvas and stores the\n * current user's cursor in Presence as `{ cursor: { x, y } }`.\n *\n * Cursor coordinates are kept in React Flow canvas space, so panning moves\n * them correctly while zooming only affects their position, not their size.\n */\nexport const Cursors = forwardRef<HTMLDivElement, CursorsProps>(\n (\n {\n className,\n children,\n presenceKey = DEFAULT_PRESENCE_KEY,\n components,\n ...props\n },\n forwardedRef\n ) => {\n const Cursor = useStableComponent(\n components?.Cursor,\n DefaultCursorWithUserInfo\n );\n const reactFlow = useReactFlow();\n const updateMyPresence = useUpdateMyPresence();\n const othersConnectionIds = useOthersConnectionIds();\n const reactFlowDomNode = useStore((state) => state.domNode);\n const reactFlowStoreApi = useStoreApi();\n\n const handlePointerMove = useCallback(\n (event: PointerEvent) => {\n const isPanning = reactFlowStoreApi.getState().paneDragging;\n\n if (isPanning) {\n return;\n }\n\n updateMyPresence({\n [presenceKey]: reactFlow.screenToFlowPosition({\n x: event.clientX,\n y: event.clientY,\n }),\n });\n },\n [updateMyPresence, presenceKey, reactFlow, reactFlowStoreApi]\n );\n\n const handlePointerLeave = useCallback(() => {\n updateMyPresence({ [presenceKey]: null });\n }, [updateMyPresence, presenceKey]);\n\n useEffect(() => {\n if (!reactFlowDomNode) {\n return;\n }\n\n reactFlowDomNode.addEventListener(\"pointermove\", handlePointerMove);\n reactFlowDomNode.addEventListener(\"pointerleave\", handlePointerLeave);\n window.addEventListener(\"blur\", handlePointerLeave);\n\n return () => {\n reactFlowDomNode.removeEventListener(\"pointermove\", handlePointerMove);\n reactFlowDomNode.removeEventListener(\n \"pointerleave\",\n handlePointerLeave\n );\n window.removeEventListener(\"blur\", handlePointerLeave);\n handlePointerLeave();\n };\n }, [reactFlowDomNode, handlePointerMove, handlePointerLeave]);\n\n return (\n <div\n aria-hidden\n className={cn(\"lb-root lb-react-flow-cursors\", className)}\n {...props}\n ref={forwardedRef}\n >\n {othersConnectionIds.map((connectionId) => (\n <PresenceCursor\n key={connectionId}\n connectionId={connectionId}\n presenceKey={presenceKey}\n Cursor={Cursor}\n />\n ))}\n\n {children}\n </div>\n );\n }\n);\n"],"names":["DefaultCursor","Cursor","other"],"mappings":"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"}
|