├── .gitignore
├── LICENSE
├── Makefile
├── RATIONALE.md
├── README.md
├── bin
└── test_cljs.sh
├── circle.yml
├── project.clj
├── resources
└── public
│ └── index.html
├── scripts
├── figwheel.clj
└── test_cljs.clj
└── src
├── devcards
└── om_css
│ └── devcards
│ ├── bugs.cljs
│ ├── constants.cljc
│ ├── core.cljs
│ └── sablono.cljs
├── main
└── om_css
│ ├── core.cljc
│ ├── dom.cljc
│ ├── output_css.clj
│ └── utils.cljc
└── test
└── om_css
├── runner.cljs
├── tests.clj
└── tests.cljs
/.gitignore:
--------------------------------------------------------------------------------
1 | out
2 | /out
3 | /target
4 | /classes
5 | /checkouts
6 | pom.xml.asc
7 | *.map
8 | *.js
9 | *.jar
10 | *.class
11 | /.lein-*
12 | /.nrepl-port
13 | *init.clj
14 | .repl*
15 | node_modules
16 | .cljs_node_repl
17 | *.log
18 | resources/public/main.css
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC
2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM
3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
4 |
5 | 1. DEFINITIONS
6 |
7 | "Contribution" means:
8 |
9 | a) in the case of the initial Contributor, the initial code and
10 | documentation distributed under this Agreement, and
11 |
12 | b) in the case of each subsequent Contributor:
13 |
14 | i) changes to the Program, and
15 |
16 | ii) additions to the Program;
17 |
18 | where such changes and/or additions to the Program originate from and are
19 | distributed by that particular Contributor. A Contribution 'originates' from
20 | a Contributor if it was added to the Program by such Contributor itself or
21 | anyone acting on such Contributor's behalf. Contributions do not include
22 | additions to the Program which: (i) are separate modules of software
23 | distributed in conjunction with the Program under their own license
24 | agreement, and (ii) are not derivative works of the Program.
25 |
26 | "Contributor" means any person or entity that distributes the Program.
27 |
28 | "Licensed Patents" mean patent claims licensable by a Contributor which are
29 | necessarily infringed by the use or sale of its Contribution alone or when
30 | combined with the Program.
31 |
32 | "Program" means the Contributions distributed in accordance with this
33 | Agreement.
34 |
35 | "Recipient" means anyone who receives the Program under this Agreement,
36 | including all Contributors.
37 |
38 | 2. GRANT OF RIGHTS
39 |
40 | a) Subject to the terms of this Agreement, each Contributor hereby grants
41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to
42 | reproduce, prepare derivative works of, publicly display, publicly perform,
43 | distribute and sublicense the Contribution of such Contributor, if any, and
44 | such derivative works, in source code and object code form.
45 |
46 | b) Subject to the terms of this Agreement, each Contributor hereby grants
47 | Recipient a non-exclusive, worldwide, royalty-free patent license under
48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise
49 | transfer the Contribution of such Contributor, if any, in source code and
50 | object code form. This patent license shall apply to the combination of the
51 | Contribution and the Program if, at the time the Contribution is added by the
52 | Contributor, such addition of the Contribution causes such combination to be
53 | covered by the Licensed Patents. The patent license shall not apply to any
54 | other combinations which include the Contribution. No hardware per se is
55 | licensed hereunder.
56 |
57 | c) Recipient understands that although each Contributor grants the licenses
58 | to its Contributions set forth herein, no assurances are provided by any
59 | Contributor that the Program does not infringe the patent or other
60 | intellectual property rights of any other entity. Each Contributor disclaims
61 | any liability to Recipient for claims brought by any other entity based on
62 | infringement of intellectual property rights or otherwise. As a condition to
63 | exercising the rights and licenses granted hereunder, each Recipient hereby
64 | assumes sole responsibility to secure any other intellectual property rights
65 | needed, if any. For example, if a third party patent license is required to
66 | allow Recipient to distribute the Program, it is Recipient's responsibility
67 | to acquire that license before distributing the Program.
68 |
69 | d) Each Contributor represents that to its knowledge it has sufficient
70 | copyright rights in its Contribution, if any, to grant the copyright license
71 | set forth in this Agreement.
72 |
73 | 3. REQUIREMENTS
74 |
75 | A Contributor may choose to distribute the Program in object code form under
76 | its own license agreement, provided that:
77 |
78 | a) it complies with the terms and conditions of this Agreement; and
79 |
80 | b) its license agreement:
81 |
82 | i) effectively disclaims on behalf of all Contributors all warranties and
83 | conditions, express and implied, including warranties or conditions of title
84 | and non-infringement, and implied warranties or conditions of merchantability
85 | and fitness for a particular purpose;
86 |
87 | ii) effectively excludes on behalf of all Contributors all liability for
88 | damages, including direct, indirect, special, incidental and consequential
89 | damages, such as lost profits;
90 |
91 | iii) states that any provisions which differ from this Agreement are offered
92 | by that Contributor alone and not by any other party; and
93 |
94 | iv) states that source code for the Program is available from such
95 | Contributor, and informs licensees how to obtain it in a reasonable manner on
96 | or through a medium customarily used for software exchange.
97 |
98 | When the Program is made available in source code form:
99 |
100 | a) it must be made available under this Agreement; and
101 |
102 | b) a copy of this Agreement must be included with each copy of the Program.
103 |
104 | Contributors may not remove or alter any copyright notices contained within
105 | the Program.
106 |
107 | Each Contributor must identify itself as the originator of its Contribution,
108 | if any, in a manner that reasonably allows subsequent Recipients to identify
109 | the originator of the Contribution.
110 |
111 | 4. COMMERCIAL DISTRIBUTION
112 |
113 | Commercial distributors of software may accept certain responsibilities with
114 | respect to end users, business partners and the like. While this license is
115 | intended to facilitate the commercial use of the Program, the Contributor who
116 | includes the Program in a commercial product offering should do so in a
117 | manner which does not create potential liability for other Contributors.
118 | Therefore, if a Contributor includes the Program in a commercial product
119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend
120 | and indemnify every other Contributor ("Indemnified Contributor") against any
121 | losses, damages and costs (collectively "Losses") arising from claims,
122 | lawsuits and other legal actions brought by a third party against the
123 | Indemnified Contributor to the extent caused by the acts or omissions of such
124 | Commercial Contributor in connection with its distribution of the Program in
125 | a commercial product offering. The obligations in this section do not apply
126 | to any claims or Losses relating to any actual or alleged intellectual
127 | property infringement. In order to qualify, an Indemnified Contributor must:
128 | a) promptly notify the Commercial Contributor in writing of such claim, and
129 | b) allow the Commercial Contributor tocontrol, and cooperate with the
130 | Commercial Contributor in, the defense and any related settlement
131 | negotiations. The Indemnified Contributor may participate in any such claim
132 | at its own expense.
133 |
134 | For example, a Contributor might include the Program in a commercial product
135 | offering, Product X. That Contributor is then a Commercial Contributor. If
136 | that Commercial Contributor then makes performance claims, or offers
137 | warranties related to Product X, those performance claims and warranties are
138 | such Commercial Contributor's responsibility alone. Under this section, the
139 | Commercial Contributor would have to defend claims against the other
140 | Contributors related to those performance claims and warranties, and if a
141 | court requires any other Contributor to pay any damages as a result, the
142 | Commercial Contributor must pay those damages.
143 |
144 | 5. NO WARRANTY
145 |
146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON
147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the
151 | appropriateness of using and distributing the Program and assumes all risks
152 | associated with its exercise of rights under this Agreement , including but
153 | not limited to the risks and costs of program errors, compliance with
154 | applicable laws, damage to or loss of data, programs or equipment, and
155 | unavailability or interruption of operations.
156 |
157 | 6. DISCLAIMER OF LIABILITY
158 |
159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY
160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL,
161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION
162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY
166 | OF SUCH DAMAGES.
167 |
168 | 7. GENERAL
169 |
170 | If any provision of this Agreement is invalid or unenforceable under
171 | applicable law, it shall not affect the validity or enforceability of the
172 | remainder of the terms of this Agreement, and without further action by the
173 | parties hereto, such provision shall be reformed to the minimum extent
174 | necessary to make such provision valid and enforceable.
175 |
176 | If Recipient institutes patent litigation against any entity (including a
177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself
178 | (excluding combinations of the Program with other software or hardware)
179 | infringes such Recipient's patent(s), then such Recipient's rights granted
180 | under Section 2(b) shall terminate as of the date such litigation is filed.
181 |
182 | All Recipient's rights under this Agreement shall terminate if it fails to
183 | comply with any of the material terms or conditions of this Agreement and
184 | does not cure such failure in a reasonable period of time after becoming
185 | aware of such noncompliance. If all Recipient's rights under this Agreement
186 | terminate, Recipient agrees to cease use and distribution of the Program as
187 | soon as reasonably practicable. However, Recipient's obligations under this
188 | Agreement and any licenses granted by Recipient relating to the Program shall
189 | continue and survive.
190 |
191 | Everyone is permitted to copy and distribute copies of this Agreement, but in
192 | order to avoid inconsistency the Agreement is copyrighted and may only be
193 | modified in the following manner. The Agreement Steward reserves the right to
194 | publish new versions (including revisions) of this Agreement from time to
195 | time. No one other than the Agreement Steward has the right to modify this
196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The
197 | Eclipse Foundation may assign the responsibility to serve as the Agreement
198 | Steward to a suitable separate entity. Each new version of the Agreement will
199 | be given a distinguishing version number. The Program (including
200 | Contributions) may always be distributed subject to the version of the
201 | Agreement under which it was received. In addition, after a new version of
202 | the Agreement is published, Contributor may elect to distribute the Program
203 | (including its Contributions) under the new version. Except as expressly
204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or
205 | licenses to the intellectual property of any Contributor under this
206 | Agreement, whether expressly, by implication, estoppel or otherwise. All
207 | rights in the Program not expressly granted under this Agreement are
208 | reserved.
209 |
210 | This Agreement is governed by the laws of the State of New York and the
211 | intellectual property laws of the United States of America. No party to this
212 | Agreement will bring a legal action under this Agreement more than one year
213 | after the cause of action arose. Each party waives its rights to a jury trial
214 | in any resulting litigation.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | test:
2 | lein test
3 | lein with-profile +client-test doo node test once
4 |
--------------------------------------------------------------------------------
/RATIONALE.md:
--------------------------------------------------------------------------------
1 | ## High level ideas
2 | - CSS/SASS is not a good programming language and composition, reuse, and
3 | abstraction should happen in clojure side.
4 | - A large CSS codebase is very difficult to reason about and CSS overrides are very difficult to
5 | reason about
6 | - Components are a great way to do composition, reuse, etc
7 |
8 | ## Concrete idea
9 | - Write CSS that is one to one with components. Basically CSS rules that only apply to your
10 | component and nothing else.
11 |
12 | ## Implementation Idea
13 | - Each css rule should be prefixed by a namespace path and the component name.
14 |
15 | ```clojure
16 | (defui Foo
17 | Object
18 | (render [this]
19 | (dom/div {:class "ladder_components_Foo"}
20 | (dom/div {:class "ladder_components_Foo--section"} "section"))))
21 | ```
22 |
23 | ```css
24 | ladder_components_Foo {
25 | some: style;
26 | }
27 |
28 | ladder_components_Foo--section {
29 | more: style;
30 | }
31 | ```
32 |
33 | ## Convenience / syntactic sugar
34 |
35 | - Writing these full qualified names is prone to mistakes and is tedious.
36 | - We want to be able to enforce a correspondence between style selectors and component class names.
37 | - We also want to collocate style with components
38 | - CSS in CLJS library https://github.com/noprompt/garden
39 |
40 | Ultimately we want to get to something like this
41 |
42 | ```clojure
43 | (ns ladder.components
44 | (:require [ladder.css :as css]))
45 |
46 | (defui Foo
47 | Style
48 | (style []
49 | [[:.root {:color (:blue css)}]
50 | [:.section (merge css/default-section
51 | {:background-color :green})]])
52 | Object
53 | (render [this]
54 | (dom/div {:class :root}
55 | (dom/div {:class :section} "section"))
56 | ```
57 |
58 |
59 | ## Annoyances
60 |
61 | - We'll want to run our css through https://github.com/postcss/autoprefixer
62 | - This is a js library
63 | - Solutions: Compiler could run cljs in nodejs
64 | - Solutions: We could setup / hit autoprefixer from clojure over HTTP
65 | - Solutions: We could output a css file and have webpack compile it
66 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Om-css [](https://circleci.com/gh/ladderlife/om-css)
2 |
3 | Colocated CSS in Om Next components.
4 |
5 | ## Contents
6 |
7 | - [Installation](#installation)
8 | - [Guide](#guide)
9 | - [Getting started](#getting-started)
10 | - [Usage with boot](#usage-with-boot)
11 | - [Defining components](#defining-components)
12 | - [`defui`](#defui)
13 | - [`defcomponent`](#defcomponent)
14 | - [Referring to global classes](#referring-to-global-classes)
15 | - [User-defined variables in colocated styles](#user-defined-variables-in-colocated-styles)
16 | - [Copyright & License](#copyright--license)
17 |
18 |
19 | ## Installation
20 |
21 | Add Om-css to your dependencies:
22 |
23 | [](https://clojars.org/com.ladderlife/om-css)
24 |
25 | ## Guide
26 |
27 | ### Getting started
28 |
29 | To get started, `:require` Om-css in your project:
30 |
31 | ```clojure
32 | (ns my-ns.core
33 | (:require [om-css.core :as oc :refer-macros [defui]]
34 | [om-css.dom :as dom])
35 | ```
36 |
37 | **Note**: In order to use the colocated style capabilities provided by Om-css, using its own `defui` and `om-css.dom` is required over Om's.
38 |
39 | Om-css provides a way to collocate CSS styles in components. However, this alone is not enough to get actual stylesheet files that you can link to in your web pages. Hence, Om-css will generate such css file for you. By default, the generated file will be called `out.css` and will be output at the root of your project. You can, however, instruct Om-css to output to a particular file. Simply add a `:css-output-to` option to the ClojureScript compiler options. Below is an example. [Here](./scripts/figwheel.clj#L15)'s a real example.
40 |
41 | ```clojure
42 | :compiler {:main 'om-css-example.core
43 | :asset-path "out"
44 | :output-to "resources/public/main.js"
45 | :output-dir "resources/public/out"
46 | :source-map true
47 | :optimizations :none
48 | :css-output-to "resources/public/main.css"}
49 | ```
50 |
51 | #### Usage with boot
52 |
53 | The `:css-output-to` option of om-css is a bit of a mismatch with boot, because boot will write to an new folder after every cljs compile. The solution here is to not set the `:css-output-to` option. Then om-css will write the css file in the same place as your cljs-output. So let's say you have a `public/js/main.cljs.edn` file then boot-cljs will write to `public/js/main.js` and om-css will write to `public/js/main.outout.css`. If you would like a nicer css filename you can use the `sift` task of boot for the preceding case you could use it like this:
54 |
55 | ```clojure
56 | (deftask compile-cljs-and-css
57 | (comp
58 | (cljs :ids #{"public/js/main"})
59 | (sift :move {#"^public\/js\/main\.outout\.css$" "public/css/next.css"})))
60 | ```
61 |
62 | ### Defining components
63 |
64 | #### `defui`
65 |
66 | Components are defined as in Om Next. In addition, Om-css provides the `Style` protocol, which you must implement in order to get all the functionality Om-css provides. This protocol consists of a single function, `style`, which must return a [Garden](https://github.com/noprompt/garden) styles vector.
67 |
68 | In the example shown below, we implement a simple component that declares a style consisting of a single class, `:root`. In the component's `render` function, the props passed to React elements need not be JavaScript objects, and may instead be regular Clojure(Script) maps. The `:class` prop is special to Om-css in the sense that it will be prefixed with the namespace and component name so that there are no clashes between components that declare classes with the same names. In our simple example, `:root` will be transformed to `"my_ns_core_SimpleComponent_root"`, and Om-css will generate CSS with the same class name.
69 |
70 | ```clojure
71 | (ns my-ns.core)
72 |
73 | (defui SimpleComponent
74 | oc/Style
75 | (style [_]
76 | [[:.root {:color :yellow}]])
77 | Object
78 | (render [this]
79 | (dom/div {:class :root} ;; <= use a vector `[:one :two]` to add multiple classes to an element
80 | "Div with class :root"))
81 | ```
82 |
83 | #### `defcomponent`
84 |
85 | `defcomponent` is syntactic sugar for simple React elements. These can optionally include a Garden styles vector in their implementation, before the element implementation itself. The following example demonstrates a `defcomponent` implementation.
86 |
87 | ```clojure
88 | (defcomponent element-with-style [props children]
89 | [[:.example-class {:background-color "tomato"}]] ;; <= optional
90 | (dom/div {:class :example-class}
91 | "Nested `defcomponent` example"))
92 | ```
93 |
94 | ### Referring to global classes
95 |
96 | Collocating CSS within components is not enough for every use case. At times, you may want to use a global CSS class that is defined somewhere else. Referring to classes defined in another location is possible both in Om-css's styles vector and in the components implementation.
97 |
98 | To reference externally defined CSS classes in the colocated styles, simply use the `$` prefix instead of the normal CSS `.` prefix. To do so in the components `render` implementation, use either one of `:className` or `:class-name`. Om-css will only prefix classes that appear in the `:class` prop.
99 |
100 | The following example shows how this is done in practice. The CSS generated by the styles vector of `example-component` is what you might expect and is presented below the component implementation.
101 |
102 | ```clojure
103 | (ns my-ns.core)
104 |
105 | (defcomponent example-component [props children]
106 | [:$desktop
107 | [:.root {:background-color "tomato"}]]
108 | (dom/div {:className "desktop"}
109 | (dom/div {:class :root}
110 | "Some text")))
111 | ```
112 |
113 | ```css
114 | .desktop .my_ns_core_example-component_root {
115 | background-color: tomato;
116 | }
117 | ```
118 |
119 | ### User-defined variables in colocated styles
120 |
121 | Om-css compiles and generates the CSS at macro-expansion time. Because ClojureScript macros are written in Clojure, any functions or variables used inside the colocated style must be declared in a `.clj` or `.cljc` file (commonly a `.cljc` file is preferred so that you can also refer to those variables in your ClojureScript code). An example is presented below.
122 |
123 | ```clojure
124 | (ns my-ns.constants)
125 |
126 | (def some-style {:margin "0px"})
127 |
128 | (ns my-ns.core
129 | (:require [om-css.core :as oc :refer-macros [defui]]
130 | [om-css.dom :as dom]
131 | [my-ns.constants :as c]))
132 |
133 | (defui Foo
134 | static oc/Style
135 | (style [_]
136 | [[:.root {:background-color "tomato"}]
137 | [:.section (merge c/some-style ;; <= notice this
138 | {:background-color :green})]])
139 | Object
140 | (render [this]
141 | (dom/div {:class :root} "div with class :root"
142 | (dom/section {:class :section} "section with class :section"))))
143 | ```
144 |
145 | Check out more examples [here](./src/devcards/om_css/devcards/core.cljs).
146 |
147 |
148 | ## Copyright & License
149 |
150 | Copyright © 2016 Ladder Financial, Inc.
151 |
152 | Distributed under the Eclipse Public License (see [LICENSE](./LICENSE)).
153 |
--------------------------------------------------------------------------------
/bin/test_cljs.sh:
--------------------------------------------------------------------------------
1 | set -e
2 |
3 | lein trampoline run -m clojure.main scripts/test_cljs.clj
4 | node target/test/test.js
5 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | java:
3 | version: oraclejdk8
4 |
5 | dependencies:
6 | override:
7 | - lein with-profiles +client-test deps
8 |
9 | test:
10 | override:
11 | - make test
12 |
--------------------------------------------------------------------------------
/project.clj:
--------------------------------------------------------------------------------
1 | (defproject com.ladderlife/om-css "0.6.1-SNAPSHOT"
2 | :description "Colocated CSS in Om Next components"
3 | :url "http://github.com/ladderlife/om-css"
4 | :license {:name "Eclipse Public License"
5 | :url "http://www.eclipse.org/legal/epl-v10.html"}
6 | :repositories [["clojars" {:sign-releases false}]]
7 | :dependencies [[org.clojure/clojure "1.9.0-alpha10" :scope "provided"]
8 | [org.clojure/clojurescript "1.9.216" :scope "provided"]
9 | [org.omcljs/om "1.0.0-alpha41" :scope "provided"]
10 |
11 | [com.ladderlife/cellophane "0.3.5"]
12 | [garden "1.3.2"]
13 |
14 | [figwheel-sidecar "0.5.4-7" :scope "test"]
15 | [devcards "0.2.1-7" :scope "test"]
16 | [devcards-om-next "0.3.0" :scope "test"]]
17 | :profiles {:client-test {:dependencies [[cljsjs/react "15.3.0-0"]]
18 | :plugins [[lein-doo "0.1.7"]
19 | [lein-cljsbuild "1.1.3"]]
20 | :cljsbuild {:builds [{:id "test"
21 | :source-paths ["src/main" "src/test"]
22 | :compiler {:output-to "target/js/client_test.js"
23 | :output-dir "target/js/out"
24 | :main om-css.runner
25 | :target :nodejs
26 | :optimizations :none}}]}}
27 | :dev {:dependencies [[sablono "0.7.4"]
28 | [com.cemerick/piggieback "0.2.1"
29 | :exclusions [org.clojure/clojurescript]]]}}
30 | :jvm-opts ^:replace ["-Xmx1g" "-server"]
31 | :jar-exclusions [#"test" #"devcards" #"public" #"runner"]
32 | :source-paths ["src/main" "src/devcards" "src/test"]
33 | :test-paths ["src/test"]
34 | :clean-targets ^{:protect false} ["target"
35 | "resources/public/out"
36 | "resources/public/main.js"]
37 | :target-path "target")
38 |
--------------------------------------------------------------------------------
/resources/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | om-css
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/scripts/figwheel.clj:
--------------------------------------------------------------------------------
1 | (require '[figwheel-sidecar.repl :as r]
2 | '[figwheel-sidecar.repl-api :as ra])
3 |
4 | (ra/start-figwheel!
5 | {:figwheel-options {:css-dirs ["resources/public"]
6 | :validate-config false
7 | :validate-interactive false}
8 | :build-ids ["devcards"]
9 | :all-builds
10 | [{:id "devcards"
11 | :figwheel {:devcards true}
12 | :source-paths ["src/main" "src/devcards"]
13 | :compiler {:main 'om-css.devcards.core
14 | :asset-path "/out"
15 | :output-to "resources/public/main.js"
16 | :output-dir "resources/public/out"
17 | :css-output-to "resources/public/main.css"
18 | :parallel-build true
19 | :compiler-stats true}}]})
20 |
21 | (ra/cljs-repl)
22 |
--------------------------------------------------------------------------------
/scripts/test_cljs.clj:
--------------------------------------------------------------------------------
1 | (require '[cljs.build.api :as b])
2 |
3 | (b/build (b/inputs "src/main" "src/test")
4 | {:target :nodejs
5 | :main 'om-css.runner
6 | :output-to "target/test/test.js"
7 | :output-dir "target/test/out"
8 | :parallel-build true
9 | :compiler-stats true
10 | :static-fns true
11 | :optimizations :none})
12 |
13 |
14 | (System/exit 0)
15 |
--------------------------------------------------------------------------------
/src/devcards/om_css/devcards/bugs.cljs:
--------------------------------------------------------------------------------
1 | (ns om-css.devcards.bugs
2 | (:require-macros [devcards.core :as dc :refer [defcard deftest]]
3 | [cljs.test :refer [is testing async]])
4 | (:require [devcards-om-next.core :as don :refer-macros [defcard-om-next]]
5 | [goog.dom :as gdom]
6 | [om.next :as om]
7 | [om.dom :as om-dom]
8 | [om-css.dom :as dom]
9 | [om-css.core :as oc :refer-macros [defui defcomponent]]
10 | [om-css.devcards.constants :as c :refer [other-style]]))
11 |
12 | ;;====================
13 | ;; OMCSS-2
14 |
15 | (defcomponent omcss-2-component [{:keys [a b] :as props} children]
16 | (dom/div nil
17 | (dom/p nil (str "prop a: " a))
18 | (dom/p nil (str "prop b: " b))))
19 |
20 | (defcard omcss-2-card
21 | "Test that destructuring works in defcomponent's args"
22 | (omcss-2-component {:a 1 :b 2}))
23 |
24 | ;;====================
25 | ;; OMCSS-4
26 |
27 | (defcomponent omcss-4-component
28 | [props children]
29 | [[:.root {:color :purple}]
30 | [:.active {:font-weight :bold}]
31 | [:.section {:color :green}]]
32 | (let [x true]
33 | (dom/div
34 | {:class [:root :active]}
35 | "div with class root"
36 | (dom/hr)
37 | (dom/section {:class :section}
38 | "section with class :section"
39 | children))))
40 |
41 | (defcard omcss-4-card
42 | "Test that `let` expressions work in `defcomponent`"
43 | (omcss-4-component))
44 |
45 | ;;====================
46 | ;; OMCSS-3
47 |
48 | (defcomponent omcss-3-component [props children]
49 | [[:.root {:color :red}]]
50 | (dom/div {:class :root :class-name "inline-block"}
51 | "test"))
52 |
53 | (defcard omcss-3-card
54 | "Test that merging with regular class names works"
55 | (omcss-3-component))
56 |
57 | ;;====================
58 | ;; OMCSS-5
59 |
60 | (defcomponent omcss-5-component [props children]
61 | [:$desktop
62 | [:.root {:background-color "tomato"}]]
63 | (dom/div {:className "desktop"}
64 | (dom/div {:class :root}
65 | "test")))
66 |
67 | (defcard omcss-5-card
68 | "Test that referencing global class names works"
69 | (omcss-5-component))
70 |
71 | ;;====================
72 | ;; OMCSS-7
73 |
74 | (defcomponent omcss-7-component-1
75 | [{:keys [class] :as props} children]
76 | [:.root {:color :purple}]
77 | (dom/div
78 | {:id "omcss-7" :class (into class [:root])}
79 | "class is now [:root :root], parent's class is lost"))
80 |
81 | (defcomponent omcss-7component-2
82 | [props children]
83 | [:.root {:text-decoration :underline}]
84 | (omcss-7-component-1 {:class [:root]}))
85 |
86 | (defcard omcss-7-card
87 | "Test that assigning classes to child components works"
88 | (omcss-7component-2))
89 |
90 | (deftest omcss-7-test
91 | (let [c (gdom/getElement "omcss-7")
92 | cns (.-className c)
93 | cns (.split cns " ")]
94 | (is (not (nil? c)))
95 | (is (not (nil?
96 | (some
97 | #{"om_css_devcards_bugs_omcss-7component-2_root"}
98 | cns))))
99 | (is (not (nil?
100 | (some
101 | #{"om_css_devcards_bugs_omcss-7-component-1_root"}
102 | cns))))))
103 |
104 | ;;====================
105 | ;; OMCSS-8
106 |
107 | (defcomponent omcss-8-component [props children]
108 | (om-dom/div #js {:className "test"} "test"))
109 |
110 | (defcard omcss-8-card
111 | (omcss-8-component))
112 |
113 | ;;====================
114 | ;; OMCSS-11
115 |
116 | (defcomponent omcss-11-component [props children]
117 | [[:.root {:color :purple}]]
118 | (dom/div
119 | (merge props {:class :root})
120 | "purple"))
121 |
122 | (defcard omcss-11-card
123 | (omcss-11-component))
124 |
125 | ;;====================
126 | ;; OMCSS-12
127 |
128 | (defui ^:once OMCSS-12-Defui
129 | static oc/Style
130 | (style [_]
131 | [[:.root {:background-color "tomato"}]
132 | [:.section (merge c/some-style
133 | {:background-color :green})]])
134 | Object
135 | (render [this]
136 | (dom/div {:class :root} "div with class :root"
137 | (dom/section {:class :section} "section with class :section"))))
138 |
139 | (defcard-om-next omcss-12-defui-card
140 | OMCSS-12-Defui)
141 |
142 | (defcomponent OMCSS-12-Defcomponent [props children]
143 | [[:.root {:background-color "tomato"}]
144 | [:.section (merge c/some-style other-style
145 | {:background-color :green})]]
146 | (dom/div {:class :root} "div with class :root"
147 | (dom/section {:class :section} "section with class :section")))
148 |
149 | (defcard omcss-12-defcomponent-card
150 | OMCSS-12-Defcomponent)
151 |
152 |
153 | ;;====================
154 | ;; OMCSS-18
155 |
156 | (defn omcss-18-add-classes [{:keys [class] :as props} & classes]
157 | (merge props {:class (flatten [class classes])}))
158 |
159 | (defcomponent omcss-18-component [{:keys [class] :as props} children]
160 | [[:.test {:color :test}]]
161 | (dom/div (omcss-18-add-classes props :test) children))
162 |
163 | (defcard omcss-18-card
164 | (omcss-18-component {:id "omcss-18"} "test"))
165 |
166 | (deftest omcss-18-test
167 | (let [c (gdom/getElement "omcss-18")
168 | cns (.-className c)
169 | cns (.split cns " ")]
170 | (is (not (nil? c)))
171 | (is (not (nil?
172 | (some
173 | #{"om_css_devcards_bugs_omcss-18-component_test"}
174 | cns))))))
175 |
176 | ;;====================
177 | ;; OMCSS-20
178 |
179 | (defcomponent omcss-20-component [props children]
180 | (let [dir "even"]
181 | (dom/div
182 | {:class (if (= dir "even") [:even] [])}
183 | "asd")))
184 |
185 | (defcard omcss-20-card
186 | (omcss-20-component))
187 |
188 | ;;====================
189 | ;; OMCSS-23
190 |
191 | (defcomponent inner [props children]
192 | (dom/div props children))
193 |
194 | (defcomponent outer [props children]
195 | (dom/div props children))
196 |
197 | (defcomponent wrapper [props children]
198 | [[:.inner {}]]
199 | (let []
200 | (outer {:class :outer
201 | :id "omcss-23"}
202 | (inner {:class :inner} "inner"))))
203 |
204 | (defcard omcss-23-card
205 | (wrapper))
206 |
207 | (deftest omcss-23-test
208 | (let [c (gdom/getElement "omcss-23")
209 | cns (.-className c)
210 | cns (.split cns " ")]
211 | (is (not (nil? c)))
212 | (is (not (nil? (some #{"outer"} cns))))))
213 |
214 | ;;====================
215 | ;; OMCSS-22
216 |
217 | (defcomponent omcss-22 [props children]
218 | []
219 | [(dom/div nil "something") (dom/div "other")])
220 |
221 | (defcard omcss-22-card
222 | (dom/div nil
223 | (omcss-22)))
224 |
225 | ;;====================
226 | ;; OMCSS-25
227 |
228 | (defcomponent omcss-25-component [props children]
229 | (dom/div {:class-name nil} "omcss-25"))
230 |
231 | (defcard omcss-25-card
232 | (omcss-25-component))
233 |
234 | ;;====================
235 | ;; nested-fns in components
236 |
237 | (defcomponent nested-fn-component [_ _]
238 | [[:hi {:text-align "center"}]]
239 | (dom/div nil
240 | "something"
241 | (map-indexed
242 | (fn [index _]
243 | (dom/p {:class :hi} (str "index: " index)))
244 | [1 2 3 4])))
245 |
246 | (defcard nested-fn-card
247 | (nested-fn-component))
248 |
249 | ;;====================
250 | ;; OMCSS-32
251 |
252 | (defui OMCSS-32-Component
253 | Object
254 | (render [this]
255 | (dom/div {:ref :some-ref} "div with ref")))
256 |
257 | (def omcss-32-reconciler
258 | (om/reconciler {:state (atom nil)
259 | :parser (om/parser {:read #(do {})})}))
260 |
261 | (defcard-om-next omcss-32-card
262 | OMCSS-32-Component
263 | omcss-32-reconciler)
264 |
265 | (deftest test-omcss-32
266 | (let [c (om/class->any omcss-32-reconciler OMCSS-32-Component)]
267 | (is (some? c))
268 | (is (some? (om/react-ref c :some-ref)))))
269 |
270 | ;;====================
271 | ;; Test that #js {} as props works
272 |
273 | (defui JSObjsComponent
274 | Object
275 | (render [this]
276 | (dom/div #js {:id "js-obj-comp"} "I work")))
277 |
278 | (defcard-om-next js-objs-card
279 | JSObjsComponent)
280 |
281 |
282 | ;;====================
283 | ;; no protocol method -assoc for [object Object]
284 |
285 | (defcomponent no-protocol-defc [props children]
286 | (dom/div props children))
287 |
288 | (defcard no-protocol-card
289 | (no-protocol-defc
290 | (dom/div {:id "no-protocol-comp"} "some text")))
291 |
292 | (deftest test-no-protocol-for-object
293 | (let [c (gdom/getElement "no-protocol-comp")]
294 | (is (some? c))))
295 |
--------------------------------------------------------------------------------
/src/devcards/om_css/devcards/constants.cljc:
--------------------------------------------------------------------------------
1 | (ns om-css.devcards.constants)
2 |
3 | (def some-style
4 | {:color :yellow})
5 |
6 | (def other-style
7 | {:text-decoration :underline})
8 |
--------------------------------------------------------------------------------
/src/devcards/om_css/devcards/core.cljs:
--------------------------------------------------------------------------------
1 | (ns om-css.devcards.core
2 | (:require-macros [devcards.core :as dc :refer [defcard deftest]]
3 | [cljs.test :refer [is testing async]])
4 | (:require [devcards-om-next.core :as don :refer-macros [defcard-om-next]]
5 | [goog.dom :as gdom]
6 | [om.next :as om]
7 | [om-css.dom :as dom]
8 | [om-css.core :as oc :refer-macros [defui defcomponent]]
9 | [om-css.devcards.bugs]
10 | [om-css.devcards.sablono]))
11 |
12 | (def style-1
13 | {:text-align :center})
14 |
15 | (defui Foo
16 | static oc/Style
17 | (style [_]
18 | [[:.root {:background-color "tomato"}]
19 | [:.section (merge {} ;;style-1
20 | {:background-color :green})]])
21 | Object
22 | (render [this]
23 | (dom/div {:id "ns-test"}
24 | (dom/div {:class :root} "div with class :root"
25 | (dom/section {:class :section} "section with class :section"
26 | (dom/p {:className "preserved"
27 | :style {:background-color "turquoise"}} "paragraph with class \"preserved\""))))))
28 |
29 | (defcard-om-next foo-card
30 | Foo)
31 |
32 | (deftest namespaced-classnames-in-dom
33 | (testing "classnames are namespace qualified"
34 | (is (not (nil? (gdom/getElement "ns-test"))))
35 | (is (not (nil? (gdom/getElementByClass "om_css_devcards_core_Foo_root"))))
36 | (is (not (nil? (gdom/getElementByClass "om_css_devcards_core_Foo_section"))))
37 | (is (not (nil? (gdom/getElementByClass "preserved"))))))
38 |
39 | (defui Bar
40 | oc/Style
41 | (style [_]
42 | [[:.bar {:margin "0 auto"}]
43 | [:.other {:padding "0"}]])
44 | Object
45 | (render [this]
46 | (dom/div {:class :bar} "Bar component")))
47 |
48 | (defcard-om-next bar-card
49 | Bar)
50 |
51 | (defui ComponentWithoutStyle
52 | Object
53 | (render [this]
54 | (dom/div {:id "component-no-style"}
55 | "Component Without Style.")))
56 |
57 | (defcard-om-next card-component-no-style
58 | ComponentWithoutStyle)
59 |
60 | (defui NotStaticStyleComponent
61 | oc/Style
62 | (style [this]
63 | {:.somestyle {:background-color "red"}})
64 | Object
65 | (render [this]
66 | (dom/div {:class :somestyle}
67 | "Component that implements Style (non-static)")))
68 |
69 | (defcard-om-next card-component-no-static-style
70 | "Test that Style doesn't need to appear with `static`"
71 | NotStaticStyleComponent)
72 |
73 | (defcomponent defcomponent-example [props children]
74 | [[:.defcomponent-class {:color "green"}]]
75 | (dom/div {:class :defcomponent-class}
76 | "`defcomponent` example with class `:defcomponent-class`"))
77 |
78 | (defcard defcomponent-example-card
79 | (defcomponent-example))
80 |
81 | (defcomponent nested-defcomponent-example [props children]
82 | (dom/div {:id "nested-defcomponent" :class :nested-defcomponent}
83 | "Nested `defcomponent` example"
84 | (defcomponent-example {:class :some}
85 | "some text")))
86 |
87 | (defcard nested-defcomponent-example-card
88 | (nested-defcomponent-example))
89 |
90 | (defcomponent defcomponent-with-style [props children]
91 | [[:.example-class {:background-color "tomato"}]]
92 | (dom/div {:class :example-class}
93 | "Nested `defcomponent` example"
94 | (defcomponent-example {:class :some}
95 | "some text")))
96 |
97 | (defcard defcomponent-with-style-card
98 | (defcomponent-with-style))
99 |
100 | (deftest namespaced-classnames-in-defcomponent
101 | (testing "`defcomponent` with styles"
102 | (is (not (nil? (gdom/getElementByClass "om_css_devcards_core_defcomponent-example_defcomponent-class"))))
103 | (is (not (nil? (gdom/getElementByClass "om_css_devcards_core_defcomponent-with-style_example-class"))))))
104 |
105 | (defui MultipleClassesDefui
106 | static oc/Style
107 | (style [_]
108 | [[:.some {:background-color "tomato"}]
109 | [:.other {:color "yellow"}]])
110 | Object
111 | (render [this]
112 | (dom/div {:id "multiple-classes-test-defui"
113 | :class [:some :other]}
114 | "div with classes [:some :other]")))
115 |
116 | (defcard-om-next defui-multiple-classes
117 | "Render a `defui` component with multiple classes"
118 | MultipleClassesDefui)
119 |
120 | (deftest multiple-classnames-in-defui
121 | (testing "`defcomponent` with styles"
122 | (let [c (gdom/getElement "multiple-classes-test-defui")
123 | cns (.-className c)
124 | cns (.split cns " ")]
125 | (is (not (nil? c)))
126 | (is (= (count cns) 2))
127 | (is (= (first cns) "om_css_devcards_core_MultipleClassesDefui_some"))
128 | (is (= (second cns) "om_css_devcards_core_MultipleClassesDefui_other")))))
129 |
130 | (defcomponent MultipleClassesDefcomponent [props children]
131 | [[:.some {:background-color "tomato"}]
132 | [:.other {:color "yellow"}]]
133 | (dom/div {:id "multiple-classes-test-defcomponent"
134 | :class [:some :other]}
135 | "div with classes [:some :other]"
136 | children))
137 |
138 | (defcard defcomponent-multiple-classes
139 | "Render a `defcomponent` component with multiple classes"
140 | (MultipleClassesDefcomponent nil
141 | (dom/div nil "child")))
142 |
143 | (deftest multiple-classnames-in-defcomponent
144 | (testing "`defcomponent` with styles"
145 | (let [c (gdom/getElement "multiple-classes-test-defcomponent")
146 | cns (.-className c)
147 | cns (.split cns " ")]
148 | (is (not (nil? c)))
149 | (is (= (count cns) 2))
150 | (is (= (first cns) "om_css_devcards_core_MultipleClassesDefcomponent_some"))
151 | (is (= (second cns) "om_css_devcards_core_MultipleClassesDefcomponent_other")))))
152 |
--------------------------------------------------------------------------------
/src/devcards/om_css/devcards/sablono.cljs:
--------------------------------------------------------------------------------
1 | (ns om-css.devcards.sablono
2 | (:require-macros [devcards.core :as dc :refer [defcard deftest]]
3 | [cljs.test :refer [is testing async]])
4 | (:require [devcards-om-next.core :as don :refer-macros [defcard-om-next]]
5 | [goog.dom :as gdom]
6 | [om.next :as om]
7 | [om.dom :as om-dom]
8 | [om-css.dom :as dom]
9 | [om-css.core :as oc :refer-macros [defui defcomponent]]
10 | [sablono.core :as sab :refer [html]]))
11 |
12 | (defui ^:once SablonoDefui
13 | static oc/Style
14 | (style [_]
15 | [[:.root {:background-color "tomato"}]
16 | [:.section {:background-color :green}]])
17 | Object
18 | (render [this]
19 | (html
20 | [:div {:class :root} "div with class :root"
21 | [:section {:class :section} "section with class :section"]])))
22 |
23 | (defcard-om-next sablono-defui
24 | SablonoDefui)
25 |
26 | (defcomponent SablonoDefcomponent [props children]
27 | [[:.root {:background-color "tomato"}]
28 | [:.section {:background-color :green}]]
29 | (html
30 | [:div {:class :root} "div with class :root"
31 | [:section {:class :section} "section with class :section"]]))
32 |
33 | (defcard omcss-12-defcomponent-card
34 | SablonoDefcomponent)
35 |
--------------------------------------------------------------------------------
/src/main/om_css/core.cljc:
--------------------------------------------------------------------------------
1 | (ns om-css.core
2 | #?(:cljs (:require-macros [om-css.core :refer [defui defcomponent]]
3 | [om-css.output-css]))
4 | (:require #?@(:clj [[om-css.dom :as dom]
5 | [garden.core :as garden]
6 | [cljs.analyzer.api :as ana]]
7 | :cljs [[om.next :as om]])
8 | [clojure.string :as string]
9 | [om-css.utils :as utils #?@(:clj [:refer [if-cljs]])])
10 | #?(:clj (:import (java.io FileNotFoundException))))
11 |
12 | (defprotocol Style
13 | (style [this]))
14 |
15 | #?(:cljs
16 | (defn prefix-class-name
17 | [x class-name]
18 | "Given a component instance or a component class and a class-name,
19 | prefixes the class-name with the component info"
20 | (let [class (pr-str (cond-> x (om/component? x) type))
21 | [ns-name component-name] (string/split class #"/")
22 | info {:ns-name ns-name
23 | :component-name component-name}]
24 | (utils/format-class-name class-name info))))
25 |
26 | #?(:clj
27 | (def css (atom {})))
28 |
29 | #?(:clj
30 | (defn reshape-props [props component-info classes-seen]
31 | (cond
32 | (map? props)
33 | (let [props' (->> props
34 | (reduce
35 | (fn [m [k v]]
36 | (if (= k :class)
37 | (assoc m k (utils/format-unevaluated-class-names
38 | v component-info classes-seen))
39 | (assoc m k v)))
40 | {:omcss$info component-info}))]
41 | props')
42 |
43 | (list? props)
44 | (let [[pre post] (split-with (complement map?) props)
45 | props' (concat (map #(cond-> %
46 | (keyword? %)
47 | (utils/format-unevaluated-class-names component-info classes-seen))
48 | pre)
49 | (map #(reshape-props % component-info classes-seen) post))]
50 | props')
51 |
52 | :else props)))
53 |
54 | #?(:clj
55 | (defn reshape-render
56 | ([form component-info classes-seen]
57 | (reshape-render nil form component-info classes-seen))
58 | ([env form component-info classes-seen]
59 | (loop [dt (seq form) ret []]
60 | (if dt
61 | (let [form (first dt)]
62 | (if (and (sequential? form) (not (empty? form)))
63 | (let [[[sym props :as pre] post] (split-at 2 form)
64 | sablono? (when (and env (symbol? sym))
65 | (= (-> (ana/resolve env sym) :name)
66 | 'sablono.core/html))
67 | coll-fn? (some #{(-> (str sym)
68 | (string/split #"-")
69 | first
70 | symbol)}
71 | ;; TODO: does this need to be hardcoded?
72 | ['map 'keep 'run! 'reduce 'filter 'mapcat])
73 | props' (if (and coll-fn? (sequential? props))
74 | (reshape-render env props component-info classes-seen)
75 | (reshape-props props component-info classes-seen))
76 | props-omitted? (and (sequential? props)
77 | (let [tag (first props)]
78 | (and (or sablono? (symbol? tag) (keyword? tag))
79 | (some #{(symbol (name tag))} dom/all-tags))))
80 | pre' (if (and (= (count pre) 2)
81 | (not props-omitted?))
82 | (list sym props')
83 | (list sym))
84 | post (cond->> post
85 | props-omitted? (cons props'))]
86 | (recur (next dt)
87 | (into ret
88 | [(cond->> (concat pre'
89 | (reshape-render env post component-info classes-seen))
90 | (vector? form) (into []))])))
91 | (recur (next dt) (into ret [form]))))
92 | (seq ret))))))
93 |
94 | #?(:clj
95 | (defn reshape-defui [env forms component-info classes-seen]
96 | (letfn [(split-on-object [forms]
97 | (split-with (complement '#{Object}) forms))
98 | (split-on-render [forms]
99 | (split-with
100 | (complement #('#{render} (first %)))
101 | forms))]
102 | (when (seq forms)
103 | (let [[pre [sym & obj-forms :as post]] (split-on-object forms)
104 | ret (into [] pre)]
105 | (if (seq post)
106 | (let [[pre [render & post]] (split-on-render obj-forms)]
107 | (into (conj ret sym)
108 | (concat pre [(reshape-render env render component-info classes-seen)]
109 | post)))
110 | ret))))))
111 |
112 | #?(:clj
113 | (defn get-style-form [forms]
114 | (loop [dt forms]
115 | (when (seq dt)
116 | (let [form (first dt)]
117 | (if (and (not (sequential? form))
118 | (not (nil? form))
119 | (= (name form) "Style"))
120 | (fnext dt)
121 | (recur (rest dt))))))))
122 |
123 | #?(:clj
124 | (defn reshape-style-form [form]
125 | (drop 2 form)))
126 |
127 | #?(:clj
128 | (defn get-component-style [forms]
129 | (-> forms
130 | get-style-form
131 | reshape-style-form
132 | first)))
133 |
134 | #?(:clj
135 | (defn- munge-ns-name [ns-name]
136 | (string/replace (munge ns-name) #"\." "_")))
137 |
138 | #?(:clj
139 | (defn- format-garden-class-name [ns-name component-name cns]
140 | "generate namespace qualified classname"
141 | (reduce
142 | #(str %1 "." (munge-ns-name ns-name)
143 | "_" component-name "_" %2)
144 | "" cns)))
145 |
146 | #?(:clj
147 | (defn format-style-classes
148 | [styles ns-name component-name]
149 | (let [classes-seen (atom #{})]
150 | (letfn [(format-style-classes* [styles ns-name component-name]
151 | (->> styles
152 | (clojure.core/map
153 | #(cond
154 | (sequential? %)
155 | (format-style-classes* % ns-name component-name)
156 |
157 | (and (or (keyword? %) (string? %))
158 | (.contains (name %) "."))
159 | (let [cn (name %)
160 | cns (remove empty? (string/split cn #"\."))
161 | elem (when-not (.startsWith cn ".") (first cns))]
162 | (swap! classes-seen into
163 | (map keyword (cond-> cns
164 | (not (nil? elem)) rest)))
165 | (str elem
166 | (format-garden-class-name ns-name component-name
167 | (if elem
168 | (rest cns)
169 | cns))))
170 |
171 | (and (keyword? %) (.startsWith (name %) "$"))
172 | (str "." (subs (name %) 1))
173 |
174 | :else %))
175 | (into [])))]
176 | (let [styles (format-style-classes* styles ns-name component-name)]
177 | {:style styles
178 | :classes @classes-seen})))))
179 |
180 | #?(:clj
181 | (defn infer-requires [{env-ns :ns :as env} forms]
182 | (letfn [(split-on-symbol [form]
183 | (split-with (complement symbol?) form))]
184 | (loop [dt (seq forms) ret []]
185 | (if dt
186 | (let [form (first dt)]
187 | (cond
188 | (sequential? form)
189 | (recur (next dt) (into ret (infer-requires env form)))
190 |
191 | (symbol? form)
192 | (let [ns (some-> (namespace form) symbol)
193 | req (some->> ns
194 | (get (:requires env-ns)))]
195 | (if req
196 | ;; look in requires
197 | (recur (next dt)
198 | (conj ret `(~'require '[~req :as ~ns])))
199 | (if-not (nil? (re-find #"clojure.core/" (str (resolve form))))
200 | ;; clojure function / var
201 | (recur (next dt) ret)
202 | (do
203 | (let [sym-ns (some-> env-ns :defs form :name namespace symbol)
204 | sym-ns (when sym-ns
205 | `(~'use '~sym-ns))
206 | use-ns (when-let [kv (find (:uses env-ns) form)]
207 | `(~'use '[~(second kv) :only [~(first kv)]]))]
208 | (if (or (= sym-ns (-> env-ns :name)) use-ns)
209 | (recur (next dt)
210 | (conj ret (when sym-ns sym-ns) (when use-ns use-ns)))
211 | (recur (next dt) ret)))))))
212 | :else (recur (next dt) ret)))
213 | ret)))))
214 |
215 | #?(:clj
216 | (defn eval-component-style [style env]
217 | (let [ns-name (-> env :ns :name str)
218 | requires (cons '(clojure.core/refer 'clojure.core)
219 | (infer-requires env style))]
220 | (try
221 | (some->> style
222 | list
223 | (concat requires)
224 | (cons 'do)
225 | eval)
226 | (catch FileNotFoundException e
227 | (throw (IllegalArgumentException.
228 | "Constants must be in a .cljc file.")))))))
229 |
230 | #?(:clj
231 | (defn- get-ns-name [env]
232 | (if-let [ns (:ns env)]
233 | (str (:name ns))
234 | (str (ns-name *ns*)))))
235 |
236 | #?(:clj
237 | (defn defui* [name forms env]
238 | (let [ns-name (get-ns-name env)
239 | component-name (str name)
240 | component-style (-> forms
241 | get-component-style
242 | (eval-component-style env))
243 | {:keys [style classes]} (when component-style
244 | (format-style-classes component-style
245 | ns-name component-name))
246 | css-str (when style
247 | (garden/css style))
248 | component-info {:ns-name ns-name
249 | :component-name (str name)
250 | :classes classes}
251 | forms (reshape-defui env forms component-info classes)
252 | name (cond-> name
253 | (-> name meta :once) (vary-meta assoc :once true))]
254 | (when css-str
255 | (swap! css assoc [ns-name name] css-str))
256 | `(if-cljs
257 | (om.next/defui ~name ~@forms)
258 | (cellophane.next/defui ~name ~@forms)))))
259 |
260 | #?(:clj
261 | (defmacro defui [name & forms]
262 | (defui* name forms &env)))
263 |
264 | #?(:clj
265 | (defn defcomponent*
266 | [env name [props children :as args] component-style body]
267 | "Example usage:
268 | (defcomponent foo
269 | [props children]
270 | ;; optional styles vector
271 | [[:.foo {:color :green}]
272 | (dom/div {:class :foo}
273 | children))
274 | (foo (dom/a {:href \"http://google.com\"}))
275 | "
276 | (when-not (and (vector? args) (= (count args) 2)
277 | ;; arguments are vectors or destructuring maps
278 | (or (symbol? (first args)) (map? (first args)))
279 | (or (symbol? (second args)) (map? (second args))))
280 | (throw (IllegalArgumentException.
281 | (str "Malformed `defcomponent`. Correct syntax: "
282 | "`(defcomponent [props children] "
283 | "[:.optional {:styles :vector}]"
284 | "(dom/element {:some :props} :children))`"))))
285 | (let [ns-name (get-ns-name env)
286 | component-name (str name)
287 | component-style' (some-> component-style
288 | (eval-component-style env))
289 | {:keys [style classes]} (when component-style'
290 | (format-style-classes component-style'
291 | ns-name component-name))
292 | css-str (some-> style
293 | garden/css)
294 | component-info {:ns-name ns-name
295 | :component-name component-name
296 | :classes classes}
297 | body (if (vector? (first body))
298 | (map #(into [] (reshape-render env % component-info #{})) body)
299 | (reshape-render env body component-info classes))]
300 | (when css-str
301 | (swap! css assoc [ns-name name] css-str))
302 | `(defn ~name [& params#]
303 | (let [[props# children#] (om-css.dom/parse-params params#)
304 | ~props (assoc props# :omcss$info ~component-info)
305 | ~children children#]
306 | ~@body)))))
307 |
308 | #?(:clj
309 | (defmacro defcomponent
310 | [name props&children & [style & rest :as body]]
311 | (defcomponent* &env name props&children
312 | (when (vector? style)
313 | style)
314 | (if (vector? style)
315 | rest
316 | body))))
317 |
--------------------------------------------------------------------------------
/src/main/om_css/dom.cljc:
--------------------------------------------------------------------------------
1 | (ns om-css.dom
2 | (:refer-clojure :exclude [map meta time mask use])
3 | #?(:cljs (:require-macros [om-css.dom :refer [gen-tag-fns]]))
4 | (:require #?(:clj [cellophane.dom :as dom]
5 | :cljs [om.dom :as dom])
6 | [clojure.string :as string]
7 | [om-css.utils :as utils #?@(:clj [:refer [if-cljs]])]))
8 |
9 | ;;; generate all form tags
10 |
11 | #?(:clj
12 | (def form-tags '[input textarea option select]))
13 |
14 | #?(:clj
15 | (def all-tags
16 | ;; cellophane has these tags
17 | (cond-> dom/tags
18 | (not (some (set form-tags) dom/tags)) (concat form-tags))))
19 |
20 | #?(:clj
21 | (defmacro gen-tag-fns
22 | []
23 | `(do
24 | ~@(clojure.core/map
25 | (fn [tag]
26 | `(defn ~tag [& ~'params]
27 | (if-cljs
28 | (apply render-element ~(symbol "om.dom" (name tag)) ~'params)
29 | (apply render-element ~(symbol "cellophane.dom" (name tag)) ~'params))))
30 | all-tags))))
31 |
32 | (defn camel-case
33 | "Converts kebab-case to camelCase"
34 | [s]
35 | (string/replace s #"-(\w)" (comp string/upper-case second)))
36 |
37 | (defn- opt-key-case
38 | "Converts attributes that are kebab-case and should be camelCase"
39 | [attr]
40 | (if (or (< (count attr) 5)
41 | (case (subs attr 0 5) ("data-" "aria-") true false))
42 | attr
43 | (camel-case attr)))
44 |
45 | (defn- opt-key-alias
46 | "Converts aliased attributes"
47 | [opt]
48 | (case opt
49 | :class :className
50 | :for :htmlFor
51 | opt))
52 |
53 | (defn format-opt-key
54 | "Returns potentially formatted name for DOM element attribute.
55 | Converts kebab-case to camelCase."
56 | [opt-key]
57 | (-> opt-key
58 | opt-key-alias
59 | name
60 | opt-key-case
61 | keyword))
62 |
63 | (declare format-opts)
64 |
65 | (defn format-opt-val
66 | "Returns potentially modified value for DOM element attribute.
67 | Recursively formats map values (ie :style attribute)"
68 | [opt-val]
69 | (cond
70 | (map? opt-val) (format-opts opt-val)
71 | :else opt-val))
72 |
73 | (defn- format-attrs [attrs]
74 | "Leaves :className unchanged, formats :class accordingly. Converts :ref to string."
75 | (let [map #?(:clj clojure.core/map
76 | :cljs cljs.core/map)]
77 | (->> attrs
78 | (map
79 | (fn [[k v]]
80 | [(format-opt-key k)
81 | (condp = k
82 | :class
83 | (let [component-info (:omcss$info attrs)
84 | classes-seen (:classes component-info)]
85 | (utils/format-dom-class-names v component-info classes-seen))
86 |
87 | :ref
88 | (str v)
89 |
90 | (format-opt-val v))]))
91 | (reduce (fn [m [k v]]
92 | (if (= k :className)
93 | ;; :omcss$info might end up in classes because we're naively
94 | ;; adding it to a map that appears in props. A stronger
95 | ;; solution might be to check if such map contains the :class keyword
96 | ;; but this might introduce other edge cases. Circle back.
97 | (assoc m k
98 | (string/trim
99 | (str (m k "")
100 | (str " "
101 | (some-> v
102 | (string/replace #":omcss\$info" ""))))))
103 | (cond-> m
104 | (not= k :omcss$info) (assoc k v)))) {}))))
105 |
106 | (defn format-opts
107 | "Returns JavaScript object for React DOM attributes from opts map"
108 | [opts]
109 | (if (map? opts)
110 | (->> opts
111 | format-attrs
112 | #?(:cljs clj->js
113 | :clj (into {} (clojure.core/map
114 | (fn [[k v]]
115 | [k (cond-> v (keyword? v) name)])))))
116 | opts))
117 |
118 | (defn parse-params
119 | [params]
120 | (let [props (first params)]
121 | (update
122 | (if (or (nil? props)
123 | #?(:clj (and (map? props)
124 | (not (record? props)))
125 | :cljs (or (and (cljs.core/object? props)
126 | (not (aget props "$$typeof"))
127 | (not= (goog/typeOf (aget props "$$typeof")) "symbol"))
128 | (map? props))))
129 | [props (rest params)]
130 | [nil params])
131 | 1 flatten)))
132 |
133 | (defn render-element
134 | [render & params]
135 | (let [[attrs children] (parse-params params)]
136 | (apply render (format-opts attrs) children)))
137 |
138 | (gen-tag-fns)
139 |
140 | ;;; proxy thru to om.dom
141 | #?(:cljs
142 | (defn render
143 | [& params]
144 | (apply dom/render params)))
145 |
146 | (defn render-to-str
147 | [& params]
148 | (apply dom/render-to-str params))
149 |
150 | (defn node
151 | [& params]
152 | (apply dom/node params))
153 |
--------------------------------------------------------------------------------
/src/main/om_css/output_css.clj:
--------------------------------------------------------------------------------
1 | (ns om-css.output-css
2 | (:require [cljs.analyzer.api :as ana-api]
3 | [clojure.java.io :as io]
4 | [clojure.string :as string]))
5 |
6 | (defn setup-io! []
7 | (let [{:keys [css-output-to output-dir output-to]} (ana-api/get-options)
8 | default-fname "out.css"
9 | fname (or css-output-to
10 | (str output-dir default-fname)
11 | (string/join "/"
12 | (-> output-to
13 | (string/split #"/")
14 | pop
15 | (conj default-fname))))]
16 | (add-watch om-css.core/css :watcher
17 | (fn [k atom old-state new-state]
18 | (with-open [out ^java.io.Writer (io/make-writer fname {})]
19 | (binding [*out* out]
20 | (println (string/join "\n" (vals new-state)))
21 | (println)))))))
22 |
23 | (setup-io!)
24 |
--------------------------------------------------------------------------------
/src/main/om_css/utils.cljc:
--------------------------------------------------------------------------------
1 | (ns om-css.utils
2 | (:require [clojure.string :as string]))
3 |
4 | #?(:clj
5 | (defn- cljs-env? [env]
6 | (boolean (:ns env))))
7 |
8 | #?(:clj
9 | (defmacro if-cljs
10 | "Return `then` if we are generating cljs code and `else` for Clojure code."
11 | [then else]
12 | (if (cljs-env? &env) then else)))
13 |
14 |
15 | (defn format-class-name [class-name component-info]
16 | "generate namespace qualified classname"
17 | (if (symbol? class-name)
18 | class-name
19 | (let [ns-name (:ns-name component-info)
20 | class-name (name class-name)
21 | component-name (-> (:component-name component-info)
22 | (string/split #"/")
23 | last)]
24 | (str (string/replace (munge ns-name) #"\." "_")
25 | "_" component-name "_" class-name))))
26 |
27 |
28 | (defn format-unevaluated-class-names
29 | ([cns component-info]
30 | (format-unevaluated-class-names cns component-info true))
31 | ([cns component-info classes-seen]
32 | ;; unevaluated data structures: a list might be a function call, we
33 | ;; only support strings, vectors or keywords
34 | (cond
35 | (or (vector? cns)
36 | (string? cns)
37 | (keyword? cns))
38 | (let [cns' (map #(cond-> %
39 | (and classes-seen
40 | (or (true? classes-seen)
41 | (get classes-seen %)))
42 | (format-class-name component-info))
43 | (if (sequential? cns) cns [cns]))]
44 | (if (sequential? cns)
45 | (into [] cns')
46 | (first cns')))
47 |
48 | (map? cns)
49 | (into {} (map (fn [[k v]]
50 | [(format-unevaluated-class-names k component-info classes-seen)
51 | (format-unevaluated-class-names v component-info classes-seen)])) cns)
52 |
53 | (list? cns)
54 | (map #(format-unevaluated-class-names % component-info classes-seen) cns)
55 |
56 | :else cns)))
57 |
58 | ;; only transform keywords at runtime, vectors and strings have
59 | ;; already been prefixed at macro-expansion time
60 | (defn format-dom-class-names [cns component-info classes-seen]
61 | (->> (if (sequential? cns) cns [cns])
62 | (map #(cond-> %
63 | (keyword? %) name
64 | (get classes-seen %) (format-class-name component-info)))
65 | (string/join " ")))
66 |
--------------------------------------------------------------------------------
/src/test/om_css/runner.cljs:
--------------------------------------------------------------------------------
1 | (ns om-css.runner
2 | (:require [doo.runner :refer-macros [doo-tests]]
3 | [om-css.tests]))
4 |
5 | (doo-tests 'om-css.tests)
6 |
--------------------------------------------------------------------------------
/src/test/om_css/tests.clj:
--------------------------------------------------------------------------------
1 | (ns om-css.tests
2 | (:require [clojure.test :refer [deftest testing is are]]
3 | [om-css.core :as oc :refer [defui defcomponent]]
4 | [om-css.dom :as dom]
5 | [cellophane.next :as cellophane]
6 | [cellophane.dom :as cdom]
7 | [om-css.utils :as utils]
8 | [cljs.analyzer.api :as ana]))
9 |
10 | (def component-info
11 | {:component-name "Foo"
12 | :ns-name "ns.core"})
13 |
14 | (deftest test-reshape-render
15 | (testing "`reshape-render` adds ns & component info to props"
16 | (let [unchanged '((dom/div nil "text"))]
17 | (is (= (oc/reshape-render
18 | '((dom/div {}
19 | "Nested `defcomponent` example"))
20 | component-info nil)
21 | '((dom/div {:omcss$info {:component-name "Foo"
22 | :ns-name "ns.core"}}
23 | "Nested `defcomponent` example"))))
24 | (is (= (oc/reshape-render unchanged component-info nil) unchanged))))
25 | (testing "`reshape-render` adds namespace qualified classes (:class)"
26 | (is (= (oc/reshape-render
27 | '((dom/div {:class :bar} "bar"))
28 | component-info #{:bar})
29 | '((dom/div {:omcss$info {:component-name "Foo"
30 | :ns-name "ns.core"}
31 | :class "ns_core_Foo_bar"} "bar"))))
32 | (is (= (oc/reshape-render
33 | '((dom/div {:class :bar} "bar"
34 | (dom/p {:class :baz} "baz")))
35 | component-info #{:bar :baz})
36 | '((dom/div {:omcss$info {:component-name "Foo"
37 | :ns-name "ns.core"}
38 | :class "ns_core_Foo_bar"} "bar"
39 | (dom/p {:omcss$info {:component-name "Foo"
40 | :ns-name "ns.core"}
41 | :class "ns_core_Foo_baz"} "baz"))))))
42 | (testing "`reshape-render` preserves `:className` classnames"
43 | (is (= (oc/reshape-render
44 | '((dom/div {:className "bar"} "bar"))
45 | component-info nil)
46 | '((dom/div {:omcss$info {:component-name "Foo"
47 | :ns-name "ns.core"}
48 | :className "bar"} "bar")))))
49 | (testing "`reshape-render` preserves `:class`'s data structure"
50 | (is (= (oc/reshape-render
51 | '((dom/div {:class [:root]}))
52 | component-info #{:root})
53 | '((dom/div {:omcss$info {:component-name "Foo"
54 | :ns-name "ns.core"}
55 | :class ["ns_core_Foo_root"]})))))
56 | (testing "`reshape-render` skips `let` bindings"
57 | (let [form '((let [x true]
58 | (dom/div
59 | {:class [:root :active]}
60 | "div with class root"
61 | (dom/hr)
62 | (dom/section {:class :section}
63 | "section with class :section"
64 | children))))]
65 | (is (= (oc/reshape-render form component-info #{:root :active :section})
66 | '((let [x true]
67 | (dom/div
68 | {:class ["ns_core_Foo_root" "ns_core_Foo_active"]
69 | :omcss$info {:component-name "Foo"
70 | :ns-name "ns.core"}}
71 | "div with class root"
72 | (dom/hr)
73 | (dom/section {:class "ns_core_Foo_section"
74 | :omcss$info {:component-name "Foo"
75 | :ns-name "ns.core"}}
76 | "section with class :section"
77 | children)))))))))
78 |
79 | (deftest test-get-style
80 | (let [form '(static om/IQuery
81 | (query [this])
82 | static oc/Style
83 | (style [_]
84 | [:root {:color "#FFFFF"}
85 | :section {:background-color :green}])
86 | static om/Ident
87 | (ident [this])
88 | Object
89 | (render [this])
90 | static om/IQueryParams
91 | (params [this]))]
92 | (is (= (oc/get-style-form form)
93 | '(style [_]
94 | [:root {:color "#FFFFF"}
95 | :section {:background-color :green}])))
96 | (is (nil? (oc/get-style-form
97 | '(Object
98 | (render [this])
99 | static om/Ident
100 | (ident [this])))))
101 | (is (= (oc/get-component-style form)
102 | [:root {:color "#FFFFF"}
103 | :section {:background-color :green}]))))
104 |
105 | (deftest test-reshape-defui
106 | (let [form '(om/IQuery
107 | (query [this])
108 | om/Ident
109 | (ident [this])
110 | Object
111 | (componentWillMount [this])
112 | (render [dia]
113 | (dom/div {:class :foo} (dom/div nil "3")))
114 | static field a 3
115 | static om/IQuery
116 | (query [this] [:a]))
117 | expected '[om/IQuery
118 | (query [this])
119 | om/Ident
120 | (ident [this])
121 | Object
122 | (componentWillMount [this])
123 | (render [dia]
124 | (dom/div
125 | {:class "ns_core_Foo_foo"
126 | :omcss$info {:component-name "Foo"
127 | :ns-name "ns.core"}}
128 | (dom/div nil "3")))
129 | static field a 3
130 | static om/IQuery
131 | (query [this] [:a])]]
132 | (is (= (oc/reshape-defui nil form component-info #{:foo})
133 | expected))
134 | (is (= (oc/reshape-defui nil
135 | '(Object (render [this] (dom/div nil "foo")))
136 | component-info nil)
137 | '[Object (render [this] (dom/div nil "foo"))]))))
138 |
139 | (deftest test-infer-requires
140 | (let [env '{:ns {:name ns.core
141 | :requires {c ns.constants
142 | o ns.other}}}]
143 | (are [forms res] (= (oc/infer-requires env forms))
144 | '[[:.root {:background-color "tomato"}]
145 | [:.section (merge c/style-1 {:background-color :green})]]
146 | '[(require '[ns.constants :as c])]
147 |
148 | '[[:.root (merge o/style-2 {:background-color "tomato"})]
149 | [:.section (merge c/style-1 {:background-color :green})]]
150 | '[(require '[ns.other :as o])
151 | (require '[ns.constants :as c])]
152 |
153 | '[:$desktop
154 | [:.root (merge c/style-1 {:background-color "tomato"})]]
155 | '[(require '[ns.constants :as c])])))
156 |
157 | (deftest omcss-11
158 | (let [form1 '((dom/div (merge props {:class :root})
159 | "purple"))
160 | form2 '((dom/div (merge {:class :root} props) "purple"))]
161 | (is (= (oc/reshape-render form1 component-info #{:root})
162 | '((dom/div (merge props {:omcss$info {:component-name "Foo"
163 | :ns-name "ns.core"}
164 | :class "ns_core_Foo_root"})
165 | "purple"))))
166 | (is (= (oc/reshape-render form2 component-info #{:root})
167 | '((dom/div (merge {:omcss$info {:component-name "Foo"
168 | :ns-name "ns.core"}
169 | :class "ns_core_Foo_root"}
170 | props)
171 | "purple"))))))
172 |
173 | (deftest test-reshape-props
174 | (are [props classes res] (= (oc/reshape-props props component-info classes) res)
175 | '(merge {:class "foo"}) #{"foo"} '(merge {:omcss$info {:ns-name "ns.core"
176 | :component-name "Foo"}
177 | :class "ns_core_Foo_foo"})
178 | {:class "foo"} #{"foo"} {:omcss$info {:ns-name "ns.core"
179 | :component-name "Foo"}
180 | :class "ns_core_Foo_foo"}
181 | {:class :foo} #{:foo} {:omcss$info {:ns-name "ns.core"
182 | :component-name "Foo"}
183 | :class "ns_core_Foo_foo"}
184 | ;; TODO: is this intended behavior?
185 | ;; see OMCSS-17
186 | '(merge {:class (subs (str :foo) 1)}) #{:foo} '(merge
187 | {:omcss$info {:component-name "Foo"
188 | :ns-name "ns.core"},
189 | :class (subs (str "ns_core_Foo_foo") 1)})))
190 |
191 | (deftest test-format-class-names
192 | (are [cns res] (= (utils/format-unevaluated-class-names cns component-info) res)
193 | :foo "ns_core_Foo_foo"
194 | "foo" "ns_core_Foo_foo"
195 | [:foo] ["ns_core_Foo_foo"]
196 | ["foo"] ["ns_core_Foo_foo"]
197 | ["foo" :bar] ["ns_core_Foo_foo" "ns_core_Foo_bar"]
198 | '(keys {:root true}) '(keys {"ns_core_Foo_root" true})))
199 |
200 | (deftest test-omcss-15
201 | (let [form '((let [color :red size :xl]
202 | (dom/div {:class [color size]})))]
203 | (is (= (oc/reshape-render form component-info nil)
204 | '((let [color :red size :xl]
205 | (dom/div {:class [color size]
206 | :omcss$info {:component-name "Foo"
207 | :ns-name "ns.core"}})))))))
208 |
209 | (deftest test-omcss-17
210 | (let [form '((dom/div nil
211 | (inner {:class (flatten [:outer class])}
212 | children)))]
213 | (is (= (oc/reshape-render form
214 | {:ns-name "om-css.devcards.bugs"
215 | :component-name "outer"}
216 | #{:outer})
217 | '((dom/div nil
218 | (inner {:omcss$info {:ns-name "om-css.devcards.bugs"
219 | :component-name "outer"}
220 | :class (flatten ["om_css_devcards_bugs_outer_outer" class])}
221 | children)))))))
222 |
223 | (deftest test-omcss-20
224 | (let [form '((let [dir "even"]
225 | (dom/div
226 | {:class (if (= dir "even") [:even] [])})))]
227 | (is (= (oc/reshape-render form component-info #{:even})
228 | '((let [dir "even"]
229 | (dom/div {:omcss$info {:component-name "Foo"
230 | :ns-name "ns.core"}
231 | :class (if (= dir "even")
232 | ["ns_core_Foo_even"]
233 | [])})))))))
234 |
235 | (deftest test-format-style-classes
236 | (let [{:keys [ns-name component-name]} component-info]
237 | (testing ""
238 | (are [style res] (= (oc/format-style-classes style ns-name component-name)
239 | res)
240 | [:.root {:color :purple}] {:style [".ns_core_Foo_root"
241 | {:color :purple}]
242 | :classes #{:root}}
243 | [[:.root {:color :purple}]
244 | [:.section {:text-align :center}]] {:style [[".ns_core_Foo_root" {:color :purple}]
245 | [".ns_core_Foo_section" {:text-align :center}]]
246 | :classes #{:root :section}}))
247 | (testing "OMCSS-19"
248 | (is (= (#'oc/format-garden-class-name ns-name component-name ["root"])
249 | ".ns_core_Foo_root"))
250 | (is (= (oc/format-style-classes
251 | [:h1.root {:color "#FFFFF"}]
252 | ns-name component-name)
253 | {:style ["h1.ns_core_Foo_root" {:color "#FFFFF"}]
254 | :classes #{:root}})))))
255 |
256 | (deftest test-omcss-23
257 | (let [form '((outer
258 | {:class :outer}
259 | (inner {:class :inner} "inner")))]
260 | (is (= (oc/reshape-render form
261 | {:ns-name "om-css.devcards.bugs"
262 | :component-name "wrapper"}
263 | #{:outer :inner})
264 | '((outer {:omcss$info {:ns-name "om-css.devcards.bugs"
265 | :component-name "wrapper"}
266 | :class "om_css_devcards_bugs_wrapper_outer"}
267 | (inner {:omcss$info {:ns-name "om-css.devcards.bugs"
268 | :component-name "wrapper"}
269 | :class "om_css_devcards_bugs_wrapper_inner"} "inner")))))))
270 |
271 | (deftest test-omcss-24
272 | (let [form '(((if true dom/div dom/span) children))]
273 | (is (= (oc/reshape-render form component-info #{})
274 | form))))
275 |
276 | (deftest test-omcss-27
277 | (let [form '((dom/div (my-class :root)))]
278 | (is (= (oc/reshape-render form component-info #{:root})
279 | '((dom/div (my-class "ns_core_Foo_root")))))))
280 |
281 | (deftest test-nested-fns-inside-element
282 | (let [form '((dom/div nil
283 | "something"
284 | (map-indexed
285 | (fn [index _]
286 | (dom/p {:class :hi} (str "index: " index)))
287 | [1 2 3 4])))]
288 | (is (= (oc/reshape-render form component-info #{:hi})
289 | '((dom/div nil
290 | "something"
291 | (map-indexed
292 | (fn [index _]
293 | (dom/p {:class "ns_core_Foo_hi"
294 | :omcss$info {:component-name "Foo"
295 | :ns-name "ns.core"}}
296 | (str "index: " index)))
297 | [1 2 3 4]))))))
298 | (let [form '((dom/div nil
299 | (->> [1 2] (map my-fn))))]
300 | (is (= (oc/reshape-render form component-info #{})
301 | form))))
302 |
303 | (defui SimpleDefui
304 | oc/Style
305 | (style [_]
306 | [:.root {:color :green}])
307 | Object
308 | (render [this]
309 | (dom/div {:id "simple"
310 | :class :root}
311 | "root div")))
312 |
313 | (defcomponent SimpleDefcomponent [props children]
314 | [:.inline {:display "inline"}]
315 | (dom/div {:class :inline} "inline div"))
316 |
317 | (deftest test-om-css-cellophane
318 | (testing "cellophane & defui"
319 | (let [c ((cellophane/factory SimpleDefui))]
320 | (is (= (str (#'cdom/render-to-str* c))
321 | "root div
"))))
322 | (testing "cellophane & defcomponent"
323 | (is (= (str (#'cdom/render-to-str* (SimpleDefcomponent)))
324 | "inline div
"))))
325 |
326 | (defui LazySeqChild
327 | Object
328 | (render [this]
329 | (let [props (cellophane/props this)]
330 | (dom/div {:class (:class props)} "bar"))))
331 |
332 | (def lazy-seq-child (cellophane/factory LazySeqChild))
333 |
334 | (defui LazySeqParent
335 | oc/Style
336 | (style [_]
337 | [[:.foo {:text-align "center"}]])
338 | Object
339 | (render [this]
340 | (dom/div nil
341 | (lazy-seq-child (assoc {} :class
342 | (filter some? ["foo" nil]))))))
343 |
344 | (deftest test-om-css-cellophane-lazy-seqs
345 | (is (= (str (#'cdom/render-to-str* ((cellophane/factory LazySeqParent))))
346 | "")))
347 |
348 | (deftest test-om-css-30
349 | (is (= (oc/reshape-render '((dom/div (dom/div (dom/div {:class :component}))))
350 | component-info #{:component})
351 | '((dom/div (dom/div (dom/div {:omcss$info {:component-name "Foo", :ns-name "ns.core"}
352 | :class "ns_core_Foo_component"})))))))
353 |
354 | (deftest test-format-opts
355 | (are [opts res] (= (dom/format-opts opts) res)
356 | {:media :desktop} {:media "desktop"}
357 | {:class :foo
358 | :omcss$info (merge component-info
359 | {:classes #{:foo}})} {:className "ns_core_Foo_foo"}))
360 |
361 | (deftest test-sablono-integration
362 | (with-redefs [ana/resolve (fn [_ sym]
363 | (when (= (name sym) "html")
364 | {:arglists '([content]),
365 | :doc "Compile the Hiccup `content` into a React DOM node.",
366 | :line 10, :column 1,
367 | :file "sablono/core.clj",
368 | :name 'sablono.core/html,
369 | :ns 'sablono.core,
370 | :macro true}))]
371 | (is (= (oc/reshape-render {} '((html [:div {:class :component}]))
372 | component-info #{:component})
373 | '((html [:div {:omcss$info {:component-name "Foo", :ns-name "ns.core"}
374 | :class "ns_core_Foo_component"}]))))
375 | (is (= (oc/reshape-render {} '((sab/html [:div [:div {:class :component}]]))
376 | component-info #{:component})
377 | '((sab/html [:div [:div {:omcss$info {:component-name "Foo", :ns-name "ns.core"}
378 | :class "ns_core_Foo_component"}]]))))))
379 |
--------------------------------------------------------------------------------
/src/test/om_css/tests.cljs:
--------------------------------------------------------------------------------
1 | (ns om-css.tests
2 | (:require [cljs.test :refer-macros [deftest testing is are run-tests]]
3 | [cljsjs.react]
4 | [om-css.core :as oc :refer-macros [defui]]
5 | [om-css.utils :as utils]))
6 |
7 | (def component-info
8 | {:component-name "Foo"
9 | :ns-name "ns.core"})
10 |
11 | (deftest test-format-class-name
12 | (are [cn res] (= (utils/format-class-name cn component-info) res)
13 | :foo "ns_core_Foo_foo"
14 | "foo" "ns_core_Foo_foo")
15 | (is (= (utils/format-class-name
16 | :foo
17 | {:ns-name "ns.core"
18 | :component-name "ns.core/Foo"})
19 | "ns_core_Foo_foo")))
20 |
21 | (deftest test-format-dom-class-names
22 | (are [cns classes-seen res] (= (utils/format-dom-class-names cns component-info classes-seen) res)
23 | "ns_core_Foo_foo" #{} "ns_core_Foo_foo"
24 | :foo #{:foo} "ns_core_Foo_foo"
25 | [:foo] #{:foo} "ns_core_Foo_foo"
26 | [:foo :bar] #{:foo :bar} "ns_core_Foo_foo ns_core_Foo_bar"
27 | ["ns_core_Foo_foo"] nil "ns_core_Foo_foo"
28 | ["ns_core_Foo_foo"] nil "ns_core_Foo_foo"
29 | ["ns_core_Foo_foo" "ns_core_Foo_bar"] nil "ns_core_Foo_foo ns_core_Foo_bar"
30 | ["ns_core_Foo_foo" :bar] #{:bar} "ns_core_Foo_foo ns_core_Foo_bar"))
31 |
32 | (defui StyledComponent
33 | static oc/Style
34 | (style [this]
35 | [[:.some-class {:text-align "center"}]]))
36 |
37 | (deftest test-prefix-class-name
38 | (is (= (oc/prefix-class-name StyledComponent :some-class)
39 | "om_css_tests_StyledComponent_some-class")))
40 |
--------------------------------------------------------------------------------