(ns frontend.rum (:require [clojure.string :as s] [clojure.set :as set] [clojure.walk :as w] [rum.core :refer [use-state use-effect!] :as rum] [cljs-bean.core :as bean])) ;; copy from https://github.com/priornix/antizer/blob/35ba264cf48b84e6597743e28b3570d8aa473e74/src/antizer/core.cljs (defn kebab-case->camel-case "Converts from kebab case to camel case, eg: on-click to onClick" [input] (let [words (s/split input #"-") capitalize (->> (rest words) (map #(apply str (s/upper-case (first %)) (rest %))))] (apply str (first words) capitalize))) (defn map-keys->camel-case "Stringifys all the keys of a cljs hashmap and converts them from kebab case to camel case. If :html-props option is specified, then rename the html properties values to their dom equivalent before conversion" [data & {:keys [html-props]}] (let [convert-to-camel (fn [[key value]] [(kebab-case->camel-case (name key)) value])] (w/postwalk (fn [x] (if (map? x) (let [new-map (if html-props (set/rename-keys x {:class :className :for :htmlFor}) x)] (into {} (map convert-to-camel new-map))) x)) data))) ;; adapted from https://github.com/tonsky/rum/issues/20 (defn adapt-class ([react-class] (adapt-class react-class false)) ([react-class skip-opts-transform?] (fn [& args] (let [[opts children] (if (map? (first args)) [(first args) (rest args)] [{} args]) type# (first children) ;; we have to make sure to check if the children is sequential ;; as a list can be returned, eg: from a (for) new-children (if (sequential? type#) (let [result (daiquiri.interpreter/interpret children)] (if (sequential? result) result [result])) children) ;; convert any options key value to a react element, if ;; a valid html element tag is used, using sablono vector->react-elems (fn [[key val]] (if (sequential? val) [key (daiquiri.interpreter/interpret val)] [key val])) new-options (into {} (if skip-opts-transform? opts (map vector->react-elems opts)))] (apply js/React.createElement react-class ;; sablono html-to-dom-attrs does not work for nested hashmaps (bean/->js (map-keys->camel-case new-options :html-props true)) new-children))))) (defn use-atom-fn [a getter-fn setter-fn] (let [[val set-val] (use-state (getter-fn @a))] (use-effect! (fn [] (let [id (str (random-uuid))] (add-watch a id (fn [_ _ prev-state next-state] (let [prev-value (getter-fn prev-state) next-value (getter-fn next-state)] (when-not (= prev-value next-value) (set-val next-value))))) #(remove-watch a id))) []) [val #(swap! a setter-fn %)])) (defn use-atom "(use-atom my-atom)" [a] (use-atom-fn a identity (fn [_ v] v))) (defn use-atom-in "(use-atom my-atom [:path :to :data])" [a path] (use-atom-fn a #(get-in % path) #(assoc-in %1 path %2))) (defn use-mounted [] (let [*mounted (rum/use-ref false)] (use-effect! (fn [] (rum/set-ref! *mounted true) #(rum/set-ref! *mounted false)) []) #(rum/deref *mounted)))