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))