rum.cljs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. (ns frontend.rum
  2. (:require [clojure.string :as s]
  3. [clojure.set :as set]
  4. [clojure.walk :as w]
  5. [rum.core :refer [use-state use-effect!] :as rum]
  6. [cljs-bean.core :as bean]))
  7. ;; copy from https://github.com/priornix/antizer/blob/35ba264cf48b84e6597743e28b3570d8aa473e74/src/antizer/core.cljs
  8. (defn kebab-case->camel-case
  9. "Converts from kebab case to camel case, eg: on-click to onClick"
  10. [input]
  11. (let [words (s/split input #"-")
  12. capitalize (->> (rest words)
  13. (map #(apply str (s/upper-case (first %)) (rest %))))]
  14. (apply str (first words) capitalize)))
  15. (defn map-keys->camel-case
  16. "Stringifys all the keys of a cljs hashmap and converts them
  17. from kebab case to camel case. If :html-props option is specified,
  18. then rename the html properties values to their dom equivalent
  19. before conversion"
  20. [data & {:keys [html-props]}]
  21. (let [convert-to-camel (fn [[key value]]
  22. [(kebab-case->camel-case (name key)) value])]
  23. (w/postwalk (fn [x]
  24. (if (map? x)
  25. (let [new-map (if html-props
  26. (set/rename-keys x {:class :className :for :htmlFor})
  27. x)]
  28. (into {} (map convert-to-camel new-map)))
  29. x))
  30. data)))
  31. ;; adapted from https://github.com/tonsky/rum/issues/20
  32. (defn adapt-class
  33. ([react-class]
  34. (adapt-class react-class false))
  35. ([react-class skip-opts-transform?]
  36. (fn [& args]
  37. (let [[opts children] (if (map? (first args))
  38. [(first args) (rest args)]
  39. [{} args])
  40. type# (first children)
  41. ;; we have to make sure to check if the children is sequential
  42. ;; as a list can be returned, eg: from a (for)
  43. new-children (if (sequential? type#)
  44. (let [result (daiquiri.interpreter/interpret children)]
  45. (if (sequential? result)
  46. result
  47. [result]))
  48. children)
  49. ;; convert any options key value to a react element, if
  50. ;; a valid html element tag is used, using sablono
  51. vector->react-elems (fn [[key val]]
  52. (if (sequential? val)
  53. [key (daiquiri.interpreter/interpret val)]
  54. [key val]))
  55. new-options (into {}
  56. (if skip-opts-transform?
  57. opts
  58. (map vector->react-elems opts)))]
  59. (apply js/React.createElement react-class
  60. ;; sablono html-to-dom-attrs does not work for nested hashmaps
  61. (bean/->js (map-keys->camel-case new-options :html-props true))
  62. new-children)))))
  63. (defn use-atom-fn
  64. [a getter-fn setter-fn]
  65. (let [[val set-val] (use-state (getter-fn @a))]
  66. (use-effect!
  67. (fn []
  68. (let [id (str (random-uuid))]
  69. (add-watch a id (fn [_ _ prev-state next-state]
  70. (let [prev-value (getter-fn prev-state)
  71. next-value (getter-fn next-state)]
  72. (when-not (= prev-value next-value)
  73. (set-val next-value)))))
  74. #(remove-watch a id)))
  75. [])
  76. [val #(swap! a setter-fn %)]))
  77. (defn use-atom
  78. "(use-atom my-atom)"
  79. [a]
  80. (use-atom-fn a identity (fn [_ v] v)))
  81. (defn use-atom-in
  82. "(use-atom my-atom [:path :to :data])"
  83. [a path]
  84. (use-atom-fn a #(get-in % path) #(assoc-in %1 path %2)))
  85. (defn use-mounted
  86. []
  87. (let [*mounted (rum/use-ref false)]
  88. (use-effect!
  89. (fn []
  90. (rum/set-ref! *mounted true)
  91. #(rum/set-ref! *mounted false))
  92. [])
  93. #(rum/deref *mounted)))