88
99* Usage
1010
11- You can mostly follow the [[https://reactflow.dev/docs/][ReactFlow documentation]] and be sure to replace
11+ You can mostly follow the [[https://reactflow.dev/docs/][ReactFlow documentation]], but make sure to replace
1212"react" with "reagent" and use ~kebab-casing~ instead of ~camelCasing~.
13- There are some exceptions.
13+
14+ There are a few important exceptions:
1415
1516- Types are prefixed with ~Flow~ and uses the original ~camelCasing~.
1617 We recommend using the keyword equivalent instead though, so you
1718 could ignore types altogether.
1819
1920- The parameters received in ~node-types~ & ~edge-types~ are unchanged, so
2021 if you want to use them you should apply
21- ~(js ->clj props :keywordize-keys true )~.
22+ ~(flowjs ->clj props)~.
2223 A nice pattern, is to only rely on the ~id~ from the parameters and do
2324 look-ups in your state manually.
2425#+begin_example clojurescript
@@ -32,6 +33,9 @@ There are some exceptions.
3233 and there are events to listen for viewport-changes on the main
3334 component; ~reagent-flow~.
3435
36+ - CSS is not automatically included, so you'll have to download the
37+ CSS files you'd like from xyflow on github.
38+
3539You can read more about the API at [[https://cljdoc.org/d/net.clojars.simtech/reagent-flow/][cljdocs]].
3640
3741/Please examine the examples below to get a better grasp of the aforementioned differences./
@@ -229,41 +233,63 @@ map with ~x~, ~y~ & ~zoom~ values, just as the hook equivalent.
229233 viewport (r/atom {:x 0 :y 0 :zoom 1})
230234 data-type "application/reagentflow"]
231235 (letfn [(handle-drag [event]
232- (let [data-transfer (-> event .-dataTransfer)]
233- (.setData data-transfer data-type "default")
236+ (let [data-transfer (-> event .-dataTransfer)
237+ target (.-target event)
238+ rect (.getBoundingClientRect target)
239+ offset-x (- (.-clientX event) (.-left rect))
240+ offset-y (- (.-clientY event) (.-top rect))
241+ payload (js/JSON.stringify
242+ #js {:type "default"
243+ :offsetX offset-x
244+ :offsetY offset-y})]
245+ (.setData data-transfer data-type payload)
234246 (set! (-> data-transfer .-effectAllowed) "move")))
247+
235248 (handle-node-changes [changes]
236249 (reset! nodes (apply-node-changes changes @nodes)))
250+
237251 (handle-edge-changes [changes]
238252 (reset! edges (apply-edge-changes changes @edges)))
253+
239254 (handle-connect [connection]
240255 (reset! edges (add-edge connection @edges)))
256+
241257 (handle-drop [event]
242258 (.preventDefault event)
243- (when-let [node-type (.getData (-> event .-dataTransfer) data-type)]
244- (let [{:keys [screen-to-flow-position]} @provider
245- flow-el (-> flow .-state .-firstChild)
246- rect (.getBoundingClientRect flow-el)
247- position (screen-to-flow-position {:x (.-clientX event)
248- :y (.-clientY event)})]
249- (swap! node-id inc)
250- (swap! nodes conj {:id (str "node-" @node-id)
251- :type node-type
252- :position position
253- :data {:label (str "Node #" @node-id)}}))))
259+ (let [data-json (.getData (-> event .-dataTransfer) data-type)
260+ data (js/JSON.parse data-json)]
261+ (when data
262+ (let [{:keys [screen-to-flow-position]} @provider
263+ flow-el (-> flow .-state .-firstChild)
264+ rect (.getBoundingClientRect flow-el)
265+ mouse-x (.-clientX event)
266+ mouse-y (.-clientY event)
267+ drop-pos (screen-to-flow-position {:x mouse-x
268+ :y mouse-y})
269+ node-pos (screen-to-flow-position {:x (- mouse-x (.-offsetX data))
270+ :y (- mouse-y (.-offsetY data))})]
271+ (swap! node-id inc)
272+ (swap! nodes conj {:id (str "node-" @node-id)
273+ :type (.-type data)
274+ :position node-pos
275+ :data {:label (str "Node #" @node-id)}})))))
276+
254277 (handle-drag-over [event]
255278 (.preventDefault event)
256279 (set! (-> event .-dataTransfer .-dropEffect) "move"))]
280+
257281 (fn []
258282 [:<>
259283 [:menu.node-palette
260284 [:div.node {:draggable true
261285 :on-drag-start handle-drag} "Node"]]
286+
262287 [reagent-flow {:ref #(reset! flow %)
263288 :id :drop-it-like-its-hot
264289 :nodes @nodes
265290 :edges @edges
266291 :fit-view true
292+ :viewport {:x 200 :y 200 :zoom 1.5}
267293 :on-init #(reset! provider %)
268294 :on-nodes-change handle-node-changes
269295 :on-edges-change handle-edge-changes
@@ -540,20 +566,21 @@ other third party libraries. The library we're using here is [[https://leva.ment
540566
541567*** Deps.edn
542568#+begin_src clojurescript :tangle babel/examples/deps.edn
543- {:deps {org.clojure/clojure {:mvn/version "1.11.3"}
544- org.clojure/clojurescript {:mvn/version "1.11.132"}
545- com.google.javascript/closure-compiler-unshaded {:mvn/version "v20230411"}
569+ {:deps {org.clojure/clojure {:mvn/version "1.12.0"}
570+ org.clojure/clojurescript {:mvn/version "1.12.42"
571+ :exclusions [com.google.javascript/closure-compiler-unshaded]}
572+ com.google.javascript/closure-compiler-unshaded {:mvn/version "v20250528"}
546573 reagent/reagent {:mvn/version "1.2.0"}
547574 re-frame/re-frame {:mvn/version "1.4.3"}
548575 ;; io.github.mentat-collective/leva.cljs {:git/sha "bb24493c8b4a0fcd862d69b4960fa297561fa5bb"}
549576 net.clojars.simtech/reagent-flow {:local/root "../"}}
550577 :paths ["src"]
551578 :aliases
552- {:watch {:extra-deps {thheller/shadow-cljs {:mvn/version "2.28.14 "}
553- binaryage/devtools {:mvn/version "1.0.7"}}
579+ {:watch {:extra-deps {thheller/shadow-cljs {:mvn/version "3.1.7 "}
580+ binaryage/devtools {:mvn/version "1.0.7"}}
554581 :main-opts ["-m" "shadow.cljs.devtools.cli" "watch" "examples"]}
555- :build {:extra-deps {thheller/shadow-cljs {:mvn/version "2.28.14 "}
556- binaryage/devtools {:mvn/version "1.0.7"}}
582+ :build {:extra-deps {thheller/shadow-cljs {:mvn/version "3.1.7 "}
583+ binaryage/devtools {:mvn/version "1.0.7"}}
557584 :main-opts ["-m" "shadow.cljs.devtools.cli" "compile" "examples"]}}}
558585#+end_src
559586
@@ -586,7 +613,7 @@ We use the same version-scheme as ReactFlow. You're currently viewing
586613version:
587614#+name: version
588615#+begin_src text
589- 12.2.0
616+ 12.7.1
590617#+end_src
591618
592619Here you'll find all the names of the classes, functions, hooks, and types of
@@ -665,6 +692,7 @@ this version of ReactFlow listed.
665692- XYZPosition
666693- Dimensions
667694- Rect
695+ - AriaLabelConfig
668696- Box
669697- Transform
670698- CoordinateExtent
@@ -714,6 +742,9 @@ this version of ReactFlow listed.
714742- OnConnectStartParams
715743- OnConnectStart
716744- OnConnectEnd
745+ - OnReconnect
746+ - OnReconnectConnectStart
747+ - OnReconnectConnectEnd
717748- Viewport
718749- KeyCode
719750- SnapGrid
@@ -816,7 +847,8 @@ these processed lists.
816847 [clojure.string :as str]
817848 [clojure.walk :refer [postwalk]]
818849 [cljs.core :refer [IDeref IEditableCollection]]
819- [medley.core :refer [map-keys map-vals]]
850+ [medley.core :refer [map-vals]]
851+ ["react" :as react]
820852 [reagent.core :as r]
821853 ["@xyflow/react$default" :as ReactFlow]
822854 ["@xyflow/react" :as rf
@@ -851,8 +883,9 @@ helper-functions:
851883 (let [f (fn [[k v]] (if (or (string? k) (keyword? k)) [(f k) v] [k v]))]
852884 (postwalk (fn [x] (if (map? x) (into {} (map f x)) x)) m)))
853885
854- (defn- flowjs->clj [o]
886+ (defn flowjs->clj
855887 "Convert a JavaScript object to a Clojure map with kebab-cased keys."
888+ [o]
856889 (let [obj (js->clj o :keywordize-keys true)]
857890 (if (map? obj)
858891 (change-keys (dissoc obj "") -->kebab-case)
@@ -868,7 +901,7 @@ helper-functions:
868901
869902(defn- apply-changes [f delta src]
870903 (-> (f (clj->flowjs delta) (clj->flowjs src))
871- (flowjs->clj)))
904+ (flowjs->clj)))
872905
873906(defn- react-flowify [types]
874907 (clj->js ((partial map-vals r/reactify-component) types)))
@@ -1014,7 +1047,6 @@ client-code.
10141047 params] children)]]))
10151048#+end_src
10161049
1017-
10181050** Tests :noexport:
10191051
10201052#+begin_src clojurescript :tangle babel/test/reagent_flow/core_test.cljs :noweb yes
@@ -1039,7 +1071,6 @@ client-code.
10391071 (let [transformed (#'reagent-flow.core/change-keys sample-map keyword)]
10401072 (is (every? (comp keyword? first) transformed)))))
10411073
1042- ;; TODO Could use a generative test for `test-flowjs->clj` as well
10431074(deftest test-flowjs->clj
10441075 (testing "Ensure flowjs->clj handles JS objects correctly"
10451076 (let [js-obj {:aKey "value" :nestedObj {:anotherKey 42} :aList [1 2 3]}]
@@ -1077,35 +1108,36 @@ client-code.
10771108
10781109**** Deps.edn
10791110#+begin_src clojurescript :tangle babel/deps.edn
1080- {:deps {org.clojure/clojure {:mvn/version "1.11.3"}
1081- org.clojure/clojurescript {:mvn/version "1.11.132"}
1082- camel-snake-kebab/camel-snake-kebab {:mvn/version "0.4.3"}
1083- dev.weavejester/medley {:mvn/version "1.8.1"}
1084- reagent/reagent {:mvn/version "1.2.0"}}
1111+ {:deps {org.clojure/clojure {:mvn/version "1.12.0"}
1112+ org.clojure/clojurescript {:mvn/version "1.12.42"
1113+ :exclusions [com.google.javascript/closure-compiler-unshaded]}
1114+ com.google.javascript/closure-compiler-unshaded {:mvn/version "v20250528"}}
10851115 :paths ["src"]
10861116 :aliases
1087- {:build {:extra-deps {thheller/shadow-cljs {:mvn/version "2.28.14 "}}
1117+ {:build {:extra-deps {thheller/shadow-cljs {:mvn/version "3.1.7 "}}
10881118 :main-opts ["-m" "shadow.cljs.devtools.cli" "release" "reagent-flow"]}
1089- :test {:extra-deps {olical/cljs-test-runner {:mvn/version "3.8.1"}
1090- org.clojure/test.check {:mvn/version "1.1.1"}}
1091- :extra-paths ["test" "cljs-test-runner-out/gen"]
1092- :main-opts ["-m" "cljs-test-runner.main" "-d" "test"]}
1093- :package {:deps {io.github.clojure/tools.build {:git/url "https://github.com/clojure/tools.build"
1094- :git/sha "143611fcf965919f1d9c18a10eeeed319305e034"}
1119+ :package {:deps {io.github.clojure/tools.build {:mvn/version "0.10.9"}
10951120 slipset/deps-deploy {:mvn/version "0.2.2"}}
10961121 :ns-default package}}}
10971122#+end_src
10981123
10991124**** Shadow-cljs.edn
11001125#+begin_src clojurescript :tangle babel/shadow-cljs.edn
1101- {:deps true
1126+ {:dependencies [[camel-snake-kebab/camel-snake-kebab "0.4.3"]
1127+ [dev.weavejester/medley "1.8.1"]
1128+ [reagent/reagent "1.3.0"]]
11021129 :nrepl {:port 9000}
11031130 :builds
11041131 {:reagent-flow
11051132 {:modules {:reagent-flow {:entries [reagent-flow.core]}}
11061133 :target :browser
11071134 :asset-path "js"
1108- :output-dir "target/classes/public/js"}}}
1135+ :output-dir "target/classes/public/js"}
1136+ :ci
1137+ {:target :karma
1138+ :output-to "out/ci.js"
1139+ :ns-regexp "-test$"
1140+ :autorun true}}}
11091141#+end_src
11101142
11111143#+begin_src clojurescript :tangle babel/src/deps.cljs :noweb yes
@@ -1114,6 +1146,24 @@ client-code.
11141146 "react-dom" "^18.3.1"}}
11151147#+end_src
11161148
1149+ #+begin_src javascript :tangle babel/karma.confg.js
1150+ module.exports = function (config) {
1151+ config.set({
1152+ browsers: ['ChromeHeadless'],
1153+ basePath: 'target',
1154+ files: ['ci.js'],
1155+ frameworks: ['cljs-test'],
1156+ plugins: ['karma-cljs-test', 'karma-chrome-launcher'],
1157+ colors: true,
1158+ logLevel: config.LOG_INFO,
1159+ client: {
1160+ args: ["shadow.test.karma.init"],
1161+ singleRun: true
1162+ }
1163+ })
1164+ };
1165+ #+end_src
1166+
11171167**** Package.json
11181168#+begin_src javascript :tangle babel/package.json :noweb yes
11191169{
@@ -1125,8 +1175,11 @@ client-code.
11251175 "@xyflow/react": "<<version>>"
11261176 },
11271177 "devDependencies": {
1128- "react": "^18.3.1",
1129- "react-dom": "^18.3.1"
1178+ "karma": "^6.4.4",
1179+ "karma-chrome-launcher": "^3.2.0",
1180+ "karma-cljs-test": "^0.1.0",
1181+ "react": "^18.3.1",
1182+ "react-dom": "^18.3.1"
11301183 }
11311184}
11321185#+end_src
0 commit comments