Files
2026-06-25 21:30:32 +00:00

145 lines
3.5 KiB
Clojure
Executable File

(ns reagent.impl.util
(:require [reagent.debug :refer-macros [dbg log warn]]
[reagent.interop :refer-macros [.' .!]]
[clojure.string :as string]))
(def is-client (and (exists? js/window)
(-> js/window (.' :document) nil? not)))
;;; Props accessors
(defn extract-props [v]
(let [p (nth v 1 nil)]
(if (map? p) p)))
(defn extract-children [v]
(let [p (nth v 1 nil)
first-child (if (or (nil? p) (map? p)) 2 1)]
(if (> (count v) first-child)
(subvec v first-child))))
(defn get-argv [c]
(.' c :props.argv))
(defn get-props [c]
(-> (.' c :props.argv) extract-props))
(defn get-children [c]
(-> (.' c :props.argv) extract-children))
(defn reagent-component? [c]
(-> (.' c :props.argv) nil? not))
(defn cached-react-class [c]
(.' c :cljsReactClass))
(defn cache-react-class [c constructor]
(.! c :cljsReactClass constructor))
;; Misc utilities
(defn memoize-1 [f]
(let [mem (atom {})]
(fn [arg]
(let [v (get @mem arg)]
(if-not (nil? v)
v
(let [ret (f arg)]
(swap! mem assoc arg ret)
ret))))))
(def dont-camel-case #{"aria" "data"})
(defn capitalize [s]
(if (< (count s) 2)
(string/upper-case s)
(str (string/upper-case (subs s 0 1)) (subs s 1))))
(defn dash-to-camel [dashed]
(if (string? dashed)
dashed
(let [name-str (name dashed)
[start & parts] (string/split name-str #"-")]
(if (dont-camel-case start)
name-str
(apply str start (map capitalize parts))))))
(deftype partial-ifn [f args ^:mutable p]
IFn
(-invoke [_ & a]
(or p (set! p (apply clojure.core/partial f args)))
(apply p a))
IEquiv
(-equiv [_ other]
(and (= f (.-f other)) (= args (.-args other))))
IHash
(-hash [_] (hash [f args])))
(defn- merge-class [p1 p2]
(let [class (when-let [c1 (:class p1)]
(when-let [c2 (:class p2)]
(str c1 " " c2)))]
(if (nil? class)
p2
(assoc p2 :class class))))
(defn- merge-style [p1 p2]
(let [style (when-let [s1 (:style p1)]
(when-let [s2 (:style p2)]
(merge s1 s2)))]
(if (nil? style)
p2
(assoc p2 :style style))))
(defn merge-props [p1 p2]
(if (nil? p1)
p2
(do
(assert (map? p1))
(merge-style p1 (merge-class p1 (merge p1 p2))))))
(def ^:dynamic *always-update* false)
(defonce roots (atom {}))
(defn clear-container [node]
;; If render throws, React may get confused, and throw on
;; unmount as well, so try to force React to start over.
(some-> node
(.! :innerHTML "")))
(defn render-component [comp container callback]
(let [rendered (volatile! nil)]
(try
(binding [*always-update* true]
(->> (.' js/React render (comp) container
(fn []
(binding [*always-update* false]
(swap! roots assoc container [comp container])
(if (some? callback)
(callback)))))
(vreset! rendered)))
(finally
(when-not @rendered
(clear-container container))))))
(defn re-render-component [comp container]
(render-component comp container nil))
(defn unmount-component-at-node [container]
(swap! roots dissoc container)
(.' js/React unmountComponentAtNode container))
(defn force-update-all []
(doseq [v (vals @roots)]
(apply re-render-component v))
"Updated")
(defn force-update [comp deep]
(if deep
(binding [*always-update* true]
(.' comp forceUpdate))
(.' comp forceUpdate)))