rum.cljs 3.8 KB

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