Created
October 26, 2025 12:45
-
-
Save borkdude/568945514e5061273ee1eb26f2c699ed to your computer and use it in GitHub Desktop.
Reagami ported to CLJS
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| (ns reagami.core | |
| {:clj-kondo/config '{:linters {:unresolved-symbol {:exclude [update!]}}}}) | |
| ;; array-seq didn't exist in scittle | |
| (defn array-seq [x] | |
| (into [] x)) | |
| (def svg-ns "http://www.w3.org/2000/svg") | |
| (defn- parse-tag | |
| "From hiccup, thanks @weavejester" | |
| [^String tag] | |
| (let [id-index (let [index (.indexOf tag "#")] (when (pos? index) index)) | |
| class-index (let [index (.indexOf tag ".")] (when (pos? index) index))] | |
| [(cond | |
| id-index (.substring tag 0 id-index) | |
| class-index (.substring tag 0 class-index) | |
| :else tag) | |
| (when id-index | |
| (if class-index | |
| (.substring tag (inc id-index) class-index) | |
| (.substring tag (inc id-index)))) | |
| (when class-index | |
| (.substring tag (inc class-index)))])) | |
| (defn boolean-attr? [x] | |
| (or (= "disabled" x) | |
| (= "checked" x))) | |
| #?(:squint (defn name [k] | |
| k)) | |
| #?(:squint (defn array-seq [s] | |
| s)) | |
| (defn- create-node* | |
| [hiccup in-svg?] | |
| (cond | |
| (or (nil? hiccup) | |
| (string? hiccup) | |
| (number? hiccup) | |
| (boolean? hiccup)) | |
| (js/document.createTextNode (str hiccup)) | |
| (vector? hiccup) | |
| (let [[tag & children] hiccup | |
| #?@(:squint [] | |
| :cljs [tag (if (keyword? tag) | |
| (subs (str tag) 1) | |
| tag)]) | |
| [tag id class] (if (string? tag) (parse-tag tag) [tag]) | |
| classes (when class (.split class ".")) | |
| [attrs children] (if (map? (first children)) | |
| [(first children) (rest children)] | |
| [nil children]) | |
| in-svg? (or in-svg? (= :svg tag)) | |
| node (if (fn? tag) | |
| (let [res (apply tag (if attrs | |
| (cons attrs children) | |
| children))] | |
| (create-node* res in-svg?)) | |
| (let [node (if in-svg? | |
| (js/document.createElementNS svg-ns tag) | |
| (js/document.createElement tag))] | |
| (doseq [child children] | |
| (let [child-nodes (if (and (seq? child) | |
| (not (vector? child))) | |
| (mapv #(create-node* % in-svg?) child) | |
| [(create-node* child in-svg?)])] | |
| (doseq [child-node child-nodes] | |
| (.appendChild node child-node)))) | |
| (let [modified-attrs (js/Set.)] | |
| (doseq [[k v] attrs] | |
| (let [k (name k)] | |
| (if (.startsWith k "on") | |
| (let [event (-> k | |
| (.replaceAll "-" "") | |
| (.toLowerCase))] | |
| (aset node event v) | |
| (.add modified-attrs event)) | |
| (do | |
| (.add modified-attrs k) | |
| (cond | |
| (and (= "style" k) (map? v)) | |
| (doseq [[k v] v] | |
| (aset (.-style node) (name k) (name v))) | |
| (.startsWith k "on") | |
| (let [event (-> k | |
| (.replaceAll "-" "") | |
| (.toLowerCase))] | |
| (aset node event v)) | |
| (boolean-attr? k) (aset node k v) | |
| :else (when v (.setAttribute node k (name v)))))))) | |
| (when (seq classes) | |
| (.setAttribute node "class" | |
| (str (when-let [c (.getAttribute node "class")] | |
| (str c " ")) | |
| (.join classes " "))) | |
| (.add modified-attrs "class")) | |
| (when id | |
| (.setAttribute node "id" id) | |
| (.add modified-attrs "id")) | |
| (aset node ::attrs #?(:squint modified-attrs | |
| :cljs (into #{} modified-attrs)))) | |
| node))] | |
| node) | |
| :else | |
| (throw (do | |
| (js/console.error "Invalid hiccup:" hiccup) | |
| (js/Error. (str "Invalid hiccup: " hiccup)))))) | |
| (defn- create-node [hiccup] | |
| (create-node* hiccup false)) | |
| (defn- patch [^js parent new-children] | |
| (let [old-children (.-childNodes parent)] | |
| (if (not= (alength old-children) (alength new-children)) | |
| (.apply parent.replaceChildren parent new-children) | |
| (doseq [[^js old ^js new] (mapv vector (array-seq old-children) (array-seq new-children))] | |
| (cond | |
| (and old new (= (.-nodeName old) (.-nodeName new))) | |
| (if (= 3 (.-nodeType old)) | |
| (let [txt (.-textContent new)] | |
| (set! (.-textContent old) txt)) | |
| (do | |
| (let [old-attrs (aget old ::attrs) | |
| new-attrs (aget new ::attrs)] | |
| (doseq [o old-attrs] | |
| (if (or (.startsWith o "on") | |
| (boolean-attr? o)) | |
| (aset old o nil) | |
| (.removeAttribute old o))) | |
| (doseq [n new-attrs] | |
| (if (or (.startsWith n "on") | |
| (boolean-attr? n)) | |
| (aset old n (aget new n)) | |
| (if | |
| (= "style" n) | |
| (set! (.-style.cssText old) (.-style.cssText new)) | |
| (.setAttribute old n (.getAttribute new n)))))) | |
| (when-let [new-children (.-childNodes new)] | |
| (patch old new-children)))) | |
| :else (.replaceChild parent new old)))))) | |
| (defn reagami:render [root hiccup] | |
| (let [new-node (create-node hiccup)] | |
| (patch root #js [new-node]))) | |
| (def state (atom {:counter 0})) | |
| (defn my-component [] | |
| [:div | |
| [:div "Counted: " (:counter @state)] | |
| [:button {:on-click #(swap! state update :counter inc)} | |
| "Click me!"]]) | |
| (defn render [] | |
| (reagami:render (js/document.querySelector "#app") [my-component])) | |
| (add-watch state ::render (fn [_ _ _ _] | |
| (render))) | |
| (render) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment