@liveblocks/react-flow 0.0.0 → 3.16.0-flow1

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 ADDED
@@ -0,0 +1,6 @@
1
+ <p>
2
+ <a href="https://liveblocks.io#gh-light-mode-only"><img src="https://raw.githubusercontent.com/liveblocks/liveblocks/main/.github/assets/header-light.svg" alt="Liveblocks" /></a>
3
+ <a href="https://liveblocks.io#gh-dark-mode-only"><img src="https://raw.githubusercontent.com/liveblocks/liveblocks/main/.github/assets/header-dark.svg" alt="Liveblocks" /></a>
4
+ </p>
5
+
6
+ # `@liveblocks/react-flow`
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ var jsxRuntime = require('react/jsx-runtime');
4
+ var core = require('@liveblocks/core');
5
+ var react = require('@liveblocks/react');
6
+ var _private = require('@liveblocks/react/_private');
7
+ var reactUi = require('@liveblocks/react-ui');
8
+ var _private$1 = require('@liveblocks/react-ui/_private');
9
+ var react$2 = require('@xyflow/react');
10
+ var react$1 = require('react');
11
+
12
+ const DEFAULT_PRESENCE_KEY = "cursor";
13
+ function $string(value) {
14
+ return typeof value === "string" ? value : void 0;
15
+ }
16
+ function $coordinates(value) {
17
+ if (core.isPlainObject(value) && typeof value.x === "number" && typeof value.y === "number") {
18
+ return value;
19
+ }
20
+ return void 0;
21
+ }
22
+ function PresenceCursor({
23
+ connectionId,
24
+ presenceKey
25
+ }) {
26
+ const room = react.useRoom();
27
+ const cursorRef = react$1.useRef(null);
28
+ const reactFlowStoreApi = react$2.useStoreApi();
29
+ 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
+ _private.useLayoutEffect(() => {
35
+ const spring = _private$1.makeCursorSpring();
36
+ function update() {
37
+ const element = cursorRef.current;
38
+ const coordinates = spring.get();
39
+ const [panX, panY, zoom] = reactFlowStoreApi.getState().transform;
40
+ if (!element) {
41
+ return;
42
+ }
43
+ if (!hasUserInfo || coordinates === null) {
44
+ element.style.transform = "translate3d(0, 0, 0)";
45
+ element.style.display = "none";
46
+ return;
47
+ }
48
+ element.style.transform = `translate3d(${coordinates.x * zoom + panX}px, ${coordinates.y * zoom + panY}px, 0)`;
49
+ element.style.display = "";
50
+ }
51
+ update();
52
+ const unsubscribeSpring = spring.subscribe(update);
53
+ const unsubscribeTransform = reactFlowStoreApi.subscribe(
54
+ (state, previousState) => {
55
+ if (state.transform !== previousState.transform) {
56
+ update();
57
+ }
58
+ }
59
+ );
60
+ const unsubscribeOther = room.events.others.subscribe(({ others }) => {
61
+ const other = others.find((other2) => other2.connectionId === connectionId);
62
+ const cursor = $coordinates(other?.presence[presenceKey]);
63
+ spring.set(cursor ?? null);
64
+ });
65
+ return () => {
66
+ spring.dispose();
67
+ unsubscribeSpring();
68
+ unsubscribeTransform();
69
+ unsubscribeOther();
70
+ };
71
+ }, [room, connectionId, presenceKey, hasUserInfo, reactFlowStoreApi]);
72
+ return /* @__PURE__ */ jsxRuntime.jsx(
73
+ reactUi.Cursor,
74
+ {
75
+ color,
76
+ label: name,
77
+ ref: cursorRef,
78
+ style: { display: "none" }
79
+ }
80
+ );
81
+ }
82
+ const Cursors = react$1.forwardRef(
83
+ ({ className, children, presenceKey = DEFAULT_PRESENCE_KEY, ...props }, forwardedRef) => {
84
+ const reactFlow = react$2.useReactFlow();
85
+ const updateMyPresence = react.useUpdateMyPresence();
86
+ const othersConnectionIds = react.useOthersConnectionIds();
87
+ const reactFlowDomNode = react$2.useStore((state) => state.domNode);
88
+ const reactFlowStoreApi = react$2.useStoreApi();
89
+ const handlePointerMove = react$1.useCallback(
90
+ (event) => {
91
+ const isPanning = reactFlowStoreApi.getState().paneDragging;
92
+ if (isPanning) {
93
+ return;
94
+ }
95
+ updateMyPresence({
96
+ [presenceKey]: reactFlow.screenToFlowPosition({
97
+ x: event.clientX,
98
+ y: event.clientY
99
+ })
100
+ });
101
+ },
102
+ [updateMyPresence, presenceKey, reactFlow, reactFlowStoreApi]
103
+ );
104
+ const handlePointerLeave = react$1.useCallback(() => {
105
+ updateMyPresence({ [presenceKey]: null });
106
+ }, [updateMyPresence, presenceKey]);
107
+ react$1.useEffect(() => {
108
+ if (!reactFlowDomNode) {
109
+ return;
110
+ }
111
+ reactFlowDomNode.addEventListener("pointermove", handlePointerMove);
112
+ reactFlowDomNode.addEventListener("pointerleave", handlePointerLeave);
113
+ window.addEventListener("blur", handlePointerLeave);
114
+ return () => {
115
+ reactFlowDomNode.removeEventListener("pointermove", handlePointerMove);
116
+ reactFlowDomNode.removeEventListener(
117
+ "pointerleave",
118
+ handlePointerLeave
119
+ );
120
+ window.removeEventListener("blur", handlePointerLeave);
121
+ handlePointerLeave();
122
+ };
123
+ }, [reactFlowDomNode, handlePointerMove, handlePointerLeave]);
124
+ return /* @__PURE__ */ jsxRuntime.jsxs(
125
+ "div",
126
+ {
127
+ "aria-hidden": true,
128
+ className: _private$1.cn("lb-root lb-react-flow-cursors", className),
129
+ ...props,
130
+ ref: forwardedRef,
131
+ children: [
132
+ othersConnectionIds.map((connectionId) => /* @__PURE__ */ jsxRuntime.jsx(
133
+ PresenceCursor,
134
+ {
135
+ connectionId,
136
+ presenceKey
137
+ },
138
+ connectionId
139
+ )),
140
+ children
141
+ ]
142
+ }
143
+ );
144
+ }
145
+ );
146
+
147
+ exports.Cursors = Cursors;
148
+ //# sourceMappingURL=cursors.cjs.map
@@ -0,0 +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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"}
@@ -0,0 +1,146 @@
1
+ import { jsx, jsxs } from 'react/jsx-runtime';
2
+ import { isPlainObject } from '@liveblocks/core';
3
+ import { useRoom, useOther, useUser, useUpdateMyPresence, useOthersConnectionIds } from '@liveblocks/react';
4
+ import { useLayoutEffect } from '@liveblocks/react/_private';
5
+ import { Cursor } from '@liveblocks/react-ui';
6
+ import { makeCursorSpring, cn } from '@liveblocks/react-ui/_private';
7
+ import { useStoreApi, useReactFlow, useStore } from '@xyflow/react';
8
+ import { useRef, forwardRef, useCallback, useEffect } from 'react';
9
+
10
+ const DEFAULT_PRESENCE_KEY = "cursor";
11
+ function $string(value) {
12
+ return typeof value === "string" ? value : void 0;
13
+ }
14
+ function $coordinates(value) {
15
+ if (isPlainObject(value) && typeof value.x === "number" && typeof value.y === "number") {
16
+ return value;
17
+ }
18
+ return void 0;
19
+ }
20
+ function PresenceCursor({
21
+ connectionId,
22
+ presenceKey
23
+ }) {
24
+ const room = useRoom();
25
+ const cursorRef = useRef(null);
26
+ const reactFlowStoreApi = useStoreApi();
27
+ 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
+ useLayoutEffect(() => {
33
+ const spring = makeCursorSpring();
34
+ function update() {
35
+ const element = cursorRef.current;
36
+ const coordinates = spring.get();
37
+ const [panX, panY, zoom] = reactFlowStoreApi.getState().transform;
38
+ if (!element) {
39
+ return;
40
+ }
41
+ if (!hasUserInfo || coordinates === null) {
42
+ element.style.transform = "translate3d(0, 0, 0)";
43
+ element.style.display = "none";
44
+ return;
45
+ }
46
+ element.style.transform = `translate3d(${coordinates.x * zoom + panX}px, ${coordinates.y * zoom + panY}px, 0)`;
47
+ element.style.display = "";
48
+ }
49
+ update();
50
+ const unsubscribeSpring = spring.subscribe(update);
51
+ const unsubscribeTransform = reactFlowStoreApi.subscribe(
52
+ (state, previousState) => {
53
+ if (state.transform !== previousState.transform) {
54
+ update();
55
+ }
56
+ }
57
+ );
58
+ const unsubscribeOther = room.events.others.subscribe(({ others }) => {
59
+ const other = others.find((other2) => other2.connectionId === connectionId);
60
+ const cursor = $coordinates(other?.presence[presenceKey]);
61
+ spring.set(cursor ?? null);
62
+ });
63
+ return () => {
64
+ spring.dispose();
65
+ unsubscribeSpring();
66
+ unsubscribeTransform();
67
+ unsubscribeOther();
68
+ };
69
+ }, [room, connectionId, presenceKey, hasUserInfo, reactFlowStoreApi]);
70
+ return /* @__PURE__ */ jsx(
71
+ Cursor,
72
+ {
73
+ color,
74
+ label: name,
75
+ ref: cursorRef,
76
+ style: { display: "none" }
77
+ }
78
+ );
79
+ }
80
+ const Cursors = forwardRef(
81
+ ({ className, children, presenceKey = DEFAULT_PRESENCE_KEY, ...props }, forwardedRef) => {
82
+ const reactFlow = useReactFlow();
83
+ const updateMyPresence = useUpdateMyPresence();
84
+ const othersConnectionIds = useOthersConnectionIds();
85
+ const reactFlowDomNode = useStore((state) => state.domNode);
86
+ const reactFlowStoreApi = useStoreApi();
87
+ const handlePointerMove = useCallback(
88
+ (event) => {
89
+ const isPanning = reactFlowStoreApi.getState().paneDragging;
90
+ if (isPanning) {
91
+ return;
92
+ }
93
+ updateMyPresence({
94
+ [presenceKey]: reactFlow.screenToFlowPosition({
95
+ x: event.clientX,
96
+ y: event.clientY
97
+ })
98
+ });
99
+ },
100
+ [updateMyPresence, presenceKey, reactFlow, reactFlowStoreApi]
101
+ );
102
+ const handlePointerLeave = useCallback(() => {
103
+ updateMyPresence({ [presenceKey]: null });
104
+ }, [updateMyPresence, presenceKey]);
105
+ useEffect(() => {
106
+ if (!reactFlowDomNode) {
107
+ return;
108
+ }
109
+ reactFlowDomNode.addEventListener("pointermove", handlePointerMove);
110
+ reactFlowDomNode.addEventListener("pointerleave", handlePointerLeave);
111
+ window.addEventListener("blur", handlePointerLeave);
112
+ return () => {
113
+ reactFlowDomNode.removeEventListener("pointermove", handlePointerMove);
114
+ reactFlowDomNode.removeEventListener(
115
+ "pointerleave",
116
+ handlePointerLeave
117
+ );
118
+ window.removeEventListener("blur", handlePointerLeave);
119
+ handlePointerLeave();
120
+ };
121
+ }, [reactFlowDomNode, handlePointerMove, handlePointerLeave]);
122
+ return /* @__PURE__ */ jsxs(
123
+ "div",
124
+ {
125
+ "aria-hidden": true,
126
+ className: cn("lb-root lb-react-flow-cursors", className),
127
+ ...props,
128
+ ref: forwardedRef,
129
+ children: [
130
+ othersConnectionIds.map((connectionId) => /* @__PURE__ */ jsx(
131
+ PresenceCursor,
132
+ {
133
+ connectionId,
134
+ presenceKey
135
+ },
136
+ connectionId
137
+ )),
138
+ children
139
+ ]
140
+ }
141
+ );
142
+ }
143
+ );
144
+
145
+ export { Cursors };
146
+ //# sourceMappingURL=cursors.js.map
@@ -0,0 +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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"}
package/dist/flow.cjs ADDED
@@ -0,0 +1,332 @@
1
+ 'use strict';
2
+
3
+ var core = require('@liveblocks/core');
4
+ var react = require('@liveblocks/react');
5
+ var _private = require('@liveblocks/react/_private');
6
+ var react$2 = require('@xyflow/react');
7
+ var react$1 = require('react');
8
+
9
+ const DEFAULT_STORAGE_KEY = "flow";
10
+ const NODE_LOCAL_KEYS = [
11
+ "selected",
12
+ "dragging",
13
+ "measured",
14
+ "resizing"
15
+ ];
16
+ const EDGE_LOCAL_KEYS = ["selected"];
17
+ const EMPTY_ARRAY = [];
18
+ function pick(from, keys) {
19
+ const result = {};
20
+ for (const key of keys) {
21
+ const value = from[key];
22
+ if (value !== void 0 && value !== null) {
23
+ result[key] = value;
24
+ }
25
+ }
26
+ return result;
27
+ }
28
+ function omit(from, keys) {
29
+ const result = { ...from };
30
+ for (const key of keys) {
31
+ delete result[key];
32
+ }
33
+ return result;
34
+ }
35
+ function reconcile(cache, next) {
36
+ const previous = cache.get(next.id);
37
+ if (previous && core.shallow(previous, next)) {
38
+ return previous;
39
+ }
40
+ cache.set(next.id, next);
41
+ return next;
42
+ }
43
+ function merge(cache, remote, local) {
44
+ for (const id of cache.keys()) {
45
+ if (!remote.has(id)) {
46
+ cache.delete(id);
47
+ }
48
+ }
49
+ for (const id of local.keys()) {
50
+ if (!remote.has(id)) {
51
+ local.delete(id);
52
+ }
53
+ }
54
+ return Array.from(remote.values(), (item) => {
55
+ const localItem = local.get(item.id);
56
+ return reconcile(
57
+ cache,
58
+ localItem ? Object.assign({}, item, localItem) : item
59
+ );
60
+ });
61
+ }
62
+ function updateLocalState(map, key, changes) {
63
+ const next = {};
64
+ for (const change in changes) {
65
+ const value = changes[change];
66
+ if (value !== void 0 && value !== false) {
67
+ next[change] = value;
68
+ }
69
+ }
70
+ if (Object.keys(next).length > 0) {
71
+ map.set(key, next);
72
+ return true;
73
+ } else {
74
+ const hasItem = map.has(key);
75
+ map.delete(key);
76
+ return hasItem;
77
+ }
78
+ }
79
+ function nodeToStorage(node) {
80
+ const { data, ...rest } = omit(node, NODE_LOCAL_KEYS);
81
+ return new core.LiveObject({
82
+ ...rest,
83
+ data: new core.LiveObject(data)
84
+ });
85
+ }
86
+ function edgeToStorage(edge) {
87
+ const { data, ...rest } = omit(edge, EDGE_LOCAL_KEYS);
88
+ return new core.LiveObject({
89
+ ...rest,
90
+ // `data` is optional on edges.
91
+ data: data === void 0 ? void 0 : new core.LiveObject(data)
92
+ });
93
+ }
94
+ function applyNodeChanges(args) {
95
+ const { changes, nodes, nextLocal, nodeCache } = args;
96
+ let hasLocalChanged = false;
97
+ for (const change of changes) {
98
+ switch (change.type) {
99
+ case "add":
100
+ case "replace":
101
+ nodes.set(change.item.id, nodeToStorage(change.item));
102
+ if (updateLocalState(
103
+ nextLocal,
104
+ change.item.id,
105
+ pick(change.item, NODE_LOCAL_KEYS)
106
+ )) {
107
+ hasLocalChanged = true;
108
+ }
109
+ break;
110
+ case "remove":
111
+ nodes.delete(change.id);
112
+ nodeCache.delete(change.id);
113
+ nextLocal.delete(change.id);
114
+ hasLocalChanged = true;
115
+ break;
116
+ case "position": {
117
+ const node = nodes.get(change.id);
118
+ if (!node || !change.position) {
119
+ break;
120
+ }
121
+ const previous = node.get("position");
122
+ if (previous?.x !== change.position.x || previous?.y !== change.position.y) {
123
+ node.set("position", change.position);
124
+ }
125
+ if (change.dragging !== void 0) {
126
+ updateLocalState(nextLocal, change.id, {
127
+ ...nextLocal.get(change.id),
128
+ dragging: change.dragging
129
+ });
130
+ hasLocalChanged = true;
131
+ }
132
+ break;
133
+ }
134
+ case "dimensions": {
135
+ const node = nodes.get(change.id);
136
+ const patch = {
137
+ ...nextLocal.get(change.id)
138
+ };
139
+ if (node && change.dimensions !== void 0 && change.setAttributes !== void 0) {
140
+ if (change.setAttributes === true || change.setAttributes === "width") {
141
+ node.set("width", change.dimensions.width);
142
+ }
143
+ if (change.setAttributes === true || change.setAttributes === "height") {
144
+ node.set("height", change.dimensions.height);
145
+ }
146
+ }
147
+ if (change.dimensions !== void 0) {
148
+ patch.measured = change.dimensions;
149
+ }
150
+ if (change.resizing !== void 0) {
151
+ patch.resizing = change.resizing;
152
+ }
153
+ updateLocalState(nextLocal, change.id, patch);
154
+ hasLocalChanged = true;
155
+ break;
156
+ }
157
+ case "select":
158
+ updateLocalState(nextLocal, change.id, {
159
+ ...nextLocal.get(change.id),
160
+ selected: change.selected
161
+ });
162
+ hasLocalChanged = true;
163
+ break;
164
+ }
165
+ }
166
+ return hasLocalChanged;
167
+ }
168
+ function applyEdgeChanges(args) {
169
+ const { changes, edges, nextLocal, edgeCache } = args;
170
+ let hasLocalChanged = false;
171
+ for (const change of changes) {
172
+ switch (change.type) {
173
+ case "add":
174
+ case "replace":
175
+ edges.set(change.item.id, edgeToStorage(change.item));
176
+ if (updateLocalState(
177
+ nextLocal,
178
+ change.item.id,
179
+ pick(change.item, EDGE_LOCAL_KEYS)
180
+ )) {
181
+ hasLocalChanged = true;
182
+ }
183
+ break;
184
+ case "remove":
185
+ edges.delete(change.id);
186
+ edgeCache.delete(change.id);
187
+ nextLocal.delete(change.id);
188
+ hasLocalChanged = true;
189
+ break;
190
+ case "select":
191
+ updateLocalState(nextLocal, change.id, {
192
+ ...nextLocal.get(change.id),
193
+ selected: change.selected
194
+ });
195
+ hasLocalChanged = true;
196
+ break;
197
+ }
198
+ }
199
+ return hasLocalChanged;
200
+ }
201
+ function createLiveblocksFlow(nodes = [], edges = []) {
202
+ return new core.LiveObject({
203
+ nodes: new core.LiveMap(nodes.map((node) => [node.id, nodeToStorage(node)])),
204
+ edges: new core.LiveMap(edges.map((edge) => [edge.id, edgeToStorage(edge)]))
205
+ });
206
+ }
207
+ function useLiveblocksFlow(options = {}) {
208
+ const isStorageLoaded = react.useStorage(() => true) ?? false;
209
+ const frozenOptions = _private.useInitial({
210
+ initial: options.initial,
211
+ storageKey: options.storageKey ?? DEFAULT_STORAGE_KEY
212
+ });
213
+ const nodeCache = react$1.useRef(/* @__PURE__ */ new Map());
214
+ const edgeCache = react$1.useRef(/* @__PURE__ */ new Map());
215
+ const [localNodes\u03A3] = react$1.useState(
216
+ () => new core.Signal(/* @__PURE__ */ new Map())
217
+ );
218
+ const [localEdges\u03A3] = react$1.useState(
219
+ () => new core.Signal(/* @__PURE__ */ new Map())
220
+ );
221
+ const localNodes = _private.useSignal(localNodes\u03A3);
222
+ const localEdges = _private.useSignal(localEdges\u03A3);
223
+ const remoteNodesMap = react.useStorage((storage) => {
224
+ const flow = storage[frozenOptions.storageKey];
225
+ return flow?.nodes ?? null;
226
+ });
227
+ const remoteEdgesMap = react.useStorage((storage) => {
228
+ const flow = storage[frozenOptions.storageKey];
229
+ return flow?.edges ?? null;
230
+ });
231
+ const nodes = react$1.useMemo(
232
+ () => remoteNodesMap ? merge(nodeCache.current, remoteNodesMap, localNodes) : null,
233
+ [remoteNodesMap, localNodes]
234
+ );
235
+ const edges = react$1.useMemo(
236
+ () => remoteEdgesMap ? merge(edgeCache.current, remoteEdgesMap, localEdges) : null,
237
+ [remoteEdgesMap, localEdges]
238
+ );
239
+ const onNodesChange = react.useMutation(
240
+ ({ storage }, changes) => {
241
+ const flow = storage.get(frozenOptions.storageKey);
242
+ if (!flow) {
243
+ return;
244
+ }
245
+ const nextLocal = new Map(localNodes\u03A3.get());
246
+ const hasLocalChanged = applyNodeChanges({
247
+ changes,
248
+ nodes: flow.get("nodes"),
249
+ nextLocal,
250
+ nodeCache: nodeCache.current
251
+ });
252
+ if (hasLocalChanged) {
253
+ localNodes\u03A3.set(nextLocal);
254
+ }
255
+ },
256
+ []
257
+ );
258
+ const onEdgesChange = react.useMutation(
259
+ ({ storage }, changes) => {
260
+ const flow = storage.get(frozenOptions.storageKey);
261
+ if (!flow) {
262
+ return;
263
+ }
264
+ const nextLocal = new Map(localEdges\u03A3.get());
265
+ const hasLocalChanged = applyEdgeChanges({
266
+ changes,
267
+ edges: flow.get("edges"),
268
+ nextLocal,
269
+ edgeCache: edgeCache.current
270
+ });
271
+ if (hasLocalChanged) {
272
+ localEdges\u03A3.set(nextLocal);
273
+ }
274
+ },
275
+ []
276
+ );
277
+ const onConnect = react.useMutation(({ storage }, connection) => {
278
+ const flow = storage.get(frozenOptions.storageKey);
279
+ if (!flow) {
280
+ return;
281
+ }
282
+ const edges2 = flow.get("edges");
283
+ for (const edge of edges2.values()) {
284
+ if (edge.get("source") === connection.source && edge.get("target") === connection.target && (edge.get("sourceHandle") ?? null) === (connection.sourceHandle ?? null) && (edge.get("targetHandle") ?? null) === (connection.targetHandle ?? null)) {
285
+ return;
286
+ }
287
+ }
288
+ const [newEdge] = react$2.addEdge(connection, []);
289
+ if (!newEdge) {
290
+ return;
291
+ }
292
+ edges2.set(newEdge.id, edgeToStorage(newEdge));
293
+ }, []);
294
+ const setInitialStorage = react.useMutation(({ storage }) => {
295
+ if (storage.get(frozenOptions.storageKey) !== void 0) {
296
+ return;
297
+ }
298
+ const { nodes: initialNodes = [], edges: initialEdges = [] } = frozenOptions.initial ?? {};
299
+ storage.set(
300
+ frozenOptions.storageKey,
301
+ createLiveblocksFlow(initialNodes, initialEdges)
302
+ );
303
+ }, []);
304
+ react$1.useEffect(() => {
305
+ if (isStorageLoaded) {
306
+ setInitialStorage();
307
+ }
308
+ }, [isStorageLoaded, setInitialStorage]);
309
+ return {
310
+ nodes,
311
+ edges,
312
+ isLoading: !isStorageLoaded,
313
+ onNodesChange,
314
+ onEdgesChange,
315
+ onConnect
316
+ };
317
+ }
318
+ function useLiveblocksFlowSuspense(options = {}) {
319
+ const result = useLiveblocksFlow(options);
320
+ _private.useSuspendUntilStorageReady();
321
+ return {
322
+ ...result,
323
+ nodes: result.nodes ?? EMPTY_ARRAY,
324
+ edges: result.edges ?? EMPTY_ARRAY,
325
+ isLoading: false
326
+ };
327
+ }
328
+
329
+ exports.createLiveblocksFlow = createLiveblocksFlow;
330
+ exports.useLiveblocksFlow = useLiveblocksFlow;
331
+ exports.useLiveblocksFlowSuspense = useLiveblocksFlowSuspense;
332
+ //# sourceMappingURL=flow.cjs.map