diff --git a/CHANGELOG.md b/CHANGELOG.md index e97b8ee..049dc25 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ Unreleased changes are available via `io.github.borkdude/html {:git/sha "..."}` ## Unreleased - Fix shorthand classes being dropped when the attribute map has no `:class`: `[:div.card {:id "a"}]` rendered `
`. The same path interpolated a symbol's _name_ when `:class` was bound to an expression, so `(let [x "big"] [:div.card {:class x}])` rendered `class="card x"`. +- Omit an attribute whose value is `nil` or `false`. `[:input {:disabled false}]` rendered `disabled="false"`, which — HTML boolean attributes being presence-based — disabled the element. An attribute map that now renders to nothing no longer leaves a stray space (`
`), which also fixes `[:div {}]`. +- Render `aria-*` booleans as the strings `"true"` and `"false"`. ARIA attributes are string-valued enumerations in which absence is a distinct state, so `[:button {:aria-expanded false}]` must render `aria-expanded="false"` rather than being omitted, and `true` must render `aria-expanded="true"` rather than a bare attribute (whose empty value is invalid, and falls back to the attribute's default). `nil` still omits. ## 0.2.6 diff --git a/src/borkdude/html.cljc b/src/borkdude/html.cljc index ae41c26..e1e7741 100644 --- a/src/borkdude/html.cljc +++ b/src/borkdude/html.cljc @@ -37,19 +37,29 @@ ([opts m] (let [xml? (= :xml (:mode opts))] (str/join " " - (map (fn [[k v]] - (if (and (true? v) (not xml?)) - (name k) - (str (name k) - "=" (cond (string? v) (pr-str (escape-html v)) - (keyword? v) (pr-str (name v)) - (and (map? v) (= "style" (name k))) (pr-str (escape-html (->css v))) - :else (pr-str (escape-html (str v))))))) - m)))) + (keep (fn [[k v]] + (let [k (name k) + aria? (str/starts-with? k "aria-")] + (cond + (nil? v) nil + (and (false? v) (not aria?)) nil + (and (true? v) (not xml?) (not aria?)) k + :else + (str k + "=" (cond (string? v) (pr-str (escape-html v)) + (keyword? v) (pr-str (name v)) + (and (map? v) (= "style" k)) (pr-str (escape-html (->css v))) + :else (pr-str (escape-html (str v)))))))) + m)))) ([opts m base-map] (let [m (merge base-map m)] (->attrs opts m)))) +(defn attrs-prefix + "Implementation, do not use" + [s] + (if (= "" s) "" (str " " s))) + (defn constant? [v] (not (or (seq? v) (symbol? v)))) @@ -128,8 +138,8 @@ attrs (if ?attrs (let [a (compile-attrs opts ?attrs)] (if (string? a) - (str " " a) - a)) + (attrs-prefix a) + `(attrs-prefix ~a))) "")] (if unsafe? `(->Html (str ~(first children))) @@ -137,7 +147,7 @@ nil (if (string? attrs) [(str "<" tag attrs ">")] - ["<" tag " " attrs ">"])) + ["<" tag attrs ">"])) ~@(map #(list (if xml? `xml `html) %) children) diff --git a/test/borkdude/html_test.cljc b/test/borkdude/html_test.cljc index b93bdca..d691014 100644 --- a/test/borkdude/html_test.cljc +++ b/test/borkdude/html_test.cljc @@ -81,6 +81,44 @@ "" (xml [:input {:checked true}]) + "" + (html [:input {:disabled false}]) + + "" + (html [:input {:disabled nil}]) + + "" + (xml [:input {:disabled nil}]) + + "" + (html [:input {:type "text" :disabled false}]) + + "" + (let [d false] + (html [:input {:type "text" :disabled d}])) + + "" + (html [:button {:aria-expanded true}]) + + "" + (html [:button {:aria-expanded false}]) + + "" + (let [b false] + (html [:button {:aria-expanded b}])) + + "" + (xml [:button {:aria-expanded true}]) + + "" + (html [:span {:aria-label nil}]) + + "
" + (html [:div {:role "checkbox" :aria-checked "mixed"}]) + + "
" + (html [:div {:data-open false}]) + "" (html [:a {:href (str "http://" "dude")}]) @@ -100,6 +138,9 @@ "
" (html [:div.card {}]) + "
" + (html [:div {}]) + "
" (let [x "big"] (html [:div.card {:class x}]))