diff --git a/core/src/uix/compiler/alpha.cljs b/core/src/uix/compiler/alpha.cljs
index cc48d706..0d5473a2 100644
--- a/core/src/uix/compiler/alpha.cljs
+++ b/core/src/uix/compiler/alpha.cljs
@@ -19,28 +19,43 @@
"Reagent element should be Hiccup wrapped with r/as-element, i.e. (r/as-element [" name-str "])")))))
true)
+(defn- normalise-args [component-type js-props props-children]
+ (if (= 2 (.-length ^js props-children))
+ #js [component-type js-props (aget props-children 1)]
+ #js [component-type js-props]))
+
(defn- uix-component-element [component-type ^js props-children children]
(let [props (aget props-children 0)
js-props (if-some [key (:key props)]
#js {:key key :argv (dissoc props :key)}
#js {:argv props})
- args (if (= 2 (.-length props-children))
- #js [component-type js-props (aget props-children 1)]
- #js [component-type js-props])]
+ args (normalise-args component-type js-props props-children)]
(.apply react/createElement nil (.concat args children))))
(defn- react-component-element [component-type ^js props-children children]
(let [js-props (-> (aget props-children 0)
(attrs/interpret-attrs #js [] true)
(aget 0))
- args (if (= 2 (.-length props-children))
- #js [component-type js-props (aget props-children 1)]
- #js [component-type js-props])]
+ args (normalise-args component-type js-props props-children)]
+ (.apply react/createElement nil (.concat args children))))
+
+(defn- dynamic-element [component-type ^js props-children children]
+ (let [tag-id-class (attrs/parse-tag component-type)
+ js-props (-> (aget props-children 0)
+ (attrs/interpret-attrs tag-id-class false)
+ (aget 0))
+ tag (aget tag-id-class 0)
+ args (normalise-args tag js-props props-children)]
(.apply react/createElement nil (.concat args children))))
(defn component-element [^clj component-type props-children children]
(when ^boolean goog.DEBUG
(validate-component component-type))
- (if (.-uix-component? component-type)
+ (cond
+ (.-uix-component? component-type)
(uix-component-element component-type props-children children)
- (react-component-element component-type props-children children)))
+
+ (keyword? component-type)
+ (dynamic-element component-type props-children children)
+
+ :else (react-component-element component-type props-children children)))
diff --git a/core/src/uix/compiler/attributes.cljs b/core/src/uix/compiler/attributes.cljs
index 6482216e..e136a353 100644
--- a/core/src/uix/compiler/attributes.cljs
+++ b/core/src/uix/compiler/attributes.cljs
@@ -129,7 +129,26 @@
([a b & rst]
(reduce class-names (class-names a b) rst)))
-(defn set-id-class
+(def re-tag
+ "HyperScript tag pattern :div :div#id.class etc."
+ #"([^\.#]*)(?:#([^\.#]+))?(?:\.([^#]+))?")
+
+(defn parse-tag
+ "Takes HyperScript tag (:div#id.class) and returns parsed tag, id and class fields,
+ and boolean indicating if tag name is a custom element (a custom DOM element that has hyphen in the name)"
+ [tag]
+ (let [tag-str (name tag)]
+ (when (and (not (re-matches re-tag tag-str))
+ (re-find #"[#\.]" tag-str))
+ ;; Throwing NPE here because shadow catches those to bring up error view in a browser
+ (throw (js/Error. (str "Invalid tag name (found: " tag-str "). Make sure that the name matches the format and ordering is correct `:tag#id.class`"))))
+ (let [[tag id class-name] (next (re-matches re-tag tag-str))
+ tag (if (= "" tag) "div" tag)
+ class-name (when-not (nil? class-name)
+ (str/replace class-name #"\." " "))]
+ #js [tag id class-name (some? (re-find #"-" tag))])))
+
+(defn- set-id-class
"Takes attributes map and parsed tag, and returns attributes merged with class names and id"
[props id-class]
(let [props-class (get props :class)
diff --git a/core/test/uix/core_test.cljs b/core/test/uix/core_test.cljs
index 511c6cdf..034a25ce 100644
--- a/core/test/uix/core_test.cljs
+++ b/core/test/uix/core_test.cljs
@@ -186,5 +186,27 @@
(is (= "HELLO! 2" (.-textContent root)))
(react-dom/unmountComponentAtNode root)))
+(defui dyn-uix-comp [props]
+ ($ :button props))
+
+(defn dyn-react-comp [^js props]
+ ($ :button
+ {:title (.-title props)
+ :children (.-children props)}))
+
+(deftest test-dynamic-element
+ (testing "dynamic element as a keyword"
+ (let [as :button#btn.action]
+ (is (= ""
+ (t/as-string ($ as {:title "hey"} "hey"))))))
+ (testing "dynamic element as uix component"
+ (let [as dyn-uix-comp]
+ (is (= ""
+ (t/as-string ($ as {:title "hey"} "hey"))))))
+ (testing "dynamic element as react component"
+ (let [as dyn-react-comp]
+ (is (= ""
+ (t/as-string ($ as {:title "hey"} "hey")))))))
+
(defn -main []
(run-tests))