-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support dynamic "Style Containers" for alternate style mounting #14
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
422e7a0
Refactor to support a dynamic "style container" to render CSS into
dhleong f336ad5
Fix factory fn name extraction in non-jvm context
dhleong c158de5
Fix incorrect namespace reference
dhleong 3fbf0d6
Fix missing spade.runtime ns reference in JVM context
dhleong ff6f06e
Implement React Context-based IStyleContainer-providing solution
dhleong e9812c7
Refactor IStyleContainer and its implementations out of the runtime ns
dhleong 0e38162
Fix old, incorrect ns reference
dhleong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
(ns spade.container) | ||
|
||
(defprotocol IStyleContainer | ||
"The IStyleContainer represents anything that can be used by Spade to | ||
'mount' styles for access by Spade style components." | ||
(mount-style! | ||
[this style-name css] | ||
"Ensure the style with the given name and CSS is available")) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
(ns spade.container.alternate | ||
"The AlternateStyleContainer may be used when a preferred container | ||
is not always available." | ||
(:require [spade.container :as sc :refer [IStyleContainer]])) | ||
|
||
(deftype AlternateStyleContainer [get-preferred fallback] | ||
IStyleContainer | ||
(mount-style! | ||
[_ style-name css] | ||
(or (when-let [preferred (get-preferred)] | ||
(sc/mount-style! preferred style-name css)) | ||
(sc/mount-style! fallback style-name css)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
(ns spade.container.atom | ||
"The AtomStyleContainer renders styles into an atom it is provided with." | ||
(:require [spade.container :refer [IStyleContainer]])) | ||
|
||
(deftype AtomStyleContainer [styles-atom] | ||
IStyleContainer | ||
(mount-style! [_ style-name css] | ||
(swap! styles-atom assoc style-name css))) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
(ns spade.container.dom | ||
"The DomStyleContainer renders styles into DOM elements. References to those | ||
elements are stored in a `styles` atom, or `*injected-styles*` if that is | ||
not provided. Similarly, if no `target-dom` is provided, the `document.head` | ||
element is used." | ||
(:require [spade.container :refer [IStyleContainer]])) | ||
|
||
(defonce ^:dynamic *injected-styles* (atom nil)) | ||
|
||
(defn- perform-update! [obj css] | ||
(set! (.-innerHTML (:element obj)) css)) | ||
|
||
(defn update! [styles-container id css] | ||
(swap! styles-container update id | ||
(fn update-injected-style [obj] | ||
(when-not (= (:source obj) css) | ||
(perform-update! obj css)) | ||
(assoc obj :source css)))) | ||
|
||
(defn inject! [target-dom styles-container id css] | ||
(let [element (doto (js/document.createElement "style") | ||
(.setAttribute "spade-id" (str id))) | ||
obj {:element element | ||
:source css | ||
:id id}] | ||
(assert (some? target-dom) | ||
"An <head> element or target DOM is required to inject the style.") | ||
|
||
(.appendChild target-dom element) | ||
|
||
(swap! styles-container assoc id obj) | ||
(perform-update! obj css))) | ||
|
||
(deftype DomStyleContainer [target-dom styles] | ||
IStyleContainer | ||
(mount-style! [_ style-name css] | ||
(let [resolved-container (or styles | ||
*injected-styles*)] | ||
(if (contains? @resolved-container style-name) | ||
(update! resolved-container style-name css) | ||
|
||
(let [resolved-dom (or (when (ifn? target-dom) | ||
(target-dom)) | ||
target-dom | ||
(.-head js/document))] | ||
(inject! resolved-dom resolved-container style-name css)))))) | ||
|
||
(defn create-container | ||
"Create a DomStyleContainer. With no args, the default is created, which | ||
renders into the `document.head` element. For rendering into a custom | ||
target, such as when using Shadow DOM, you may provide a custom | ||
`target-dom`: this may either be the element itself, or a function which | ||
returns that element. | ||
|
||
If you also wish to provide your own storage for the style references, you | ||
may use the 3-arity version and provide an atom." | ||
([] (create-container nil)) | ||
([target-dom] (create-container target-dom (when target-dom | ||
(atom nil)))) | ||
([target-dom styles-container] | ||
(->DomStyleContainer target-dom styles-container))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
(ns spade.react | ||
(:require [react :as react] | ||
[spade.container :refer [IStyleContainer]] | ||
[spade.container.alternate :refer [->AlternateStyleContainer]] | ||
[spade.container.dom :as dom] | ||
[spade.runtime :refer [*style-container*]])) | ||
|
||
(defonce context (react/createContext nil)) | ||
(defonce Provider (.-Provider context)) | ||
|
||
(defn- provided-container [] | ||
;; NOTE: This hack is inspired by ReactN: | ||
;; https://charles-stover.medium.com/how-reactn-hacks-react-context-9d112397f003 | ||
(or (.-_currentValue2 context) | ||
(.-_currentValue context))) | ||
|
||
;; NOTE: As soon as this namespace is used, we "upgrade" the global style-container | ||
;; to also check the react context | ||
(set! *style-container* | ||
(->AlternateStyleContainer | ||
provided-container | ||
*style-container*)) | ||
|
||
(defn with-style-container | ||
"Uses the provided IStyleContainer to render the styles of the children | ||
elements. This is a reagent-style React component, meant to wrap whatever | ||
part of your app needs to have its styled rendered elsewhere (often the | ||
root of the app), eg: | ||
|
||
[with-style-container container | ||
[your-view]] | ||
|
||
Note that behavior in the presence of a reactively changing `container` is | ||
undefined. You should expect to declare one container per render." | ||
[^IStyleContainer container & children] | ||
(into [:> Provider {:value container}] | ||
children)) | ||
|
||
(defn with-dom | ||
"A convenience for rendering Spade styles into an alternate dom target. | ||
The first argument may either be an actual DOM element, or a function which | ||
returns one. | ||
|
||
See `with-style-container`, which this uses under the hood." | ||
[get-dom-target & _children] | ||
(let [container (dom/create-container get-dom-target)] | ||
(fn with-dom-render [_ & children] | ||
(into [with-style-container container] | ||
children)))) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
(ns spade.runtime | ||
(:require [clojure.string :as str] | ||
[garden.core :as garden] | ||
[garden.types :refer [->CSSFunction]] | ||
[spade.container :as sc] | ||
[spade.runtime.defaults :as defaults])) | ||
|
||
(defonce ^:dynamic *css-compile-flags* | ||
{:pretty-print? #? (:cljs goog.DEBUG | ||
:clj false)}) | ||
|
||
(defonce ^:dynamic *style-container* (defaults/create-container)) | ||
|
||
(defn ->css-var [n] | ||
(->CSSFunction "var" n)) | ||
|
||
(defn compile-css [elements] | ||
(garden/css *css-compile-flags* elements)) | ||
|
||
(defn- compose-names [{style-name :name composed :composes}] | ||
(if-not composed | ||
style-name | ||
(str/join " " | ||
(->> | ||
(if (seq? composed) | ||
(into composed style-name) | ||
[composed style-name]) | ||
(map (fn [item] | ||
(cond | ||
(string? item) item | ||
|
||
; unpack a defattrs | ||
(and (map? item) | ||
(string? (:class item))) | ||
(:class item) | ||
|
||
:else | ||
(throw (ex-info | ||
(str "Invalid argument to :composes key:" | ||
item) | ||
{}))))))))) | ||
|
||
(defn ensure-style! [mode base-style-name factory params] | ||
(let [{css :css style-name :name :as info} (apply factory base-style-name params params)] | ||
|
||
(sc/mount-style! *style-container* style-name css) | ||
|
||
(case mode | ||
:attrs {:class (compose-names info)} | ||
(:class :keyframes) (compose-names info) | ||
:global css))) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this
*injected-styles*
shared between containers ? If so would that work for the case where you have the same style being injected into two different target containers ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
*injected-styles*
is not shared! We may even be able to get rid of it, but basically it is the default storage atom for the default DOM container. In other words, every container created by(dom/create-container)
(IE without any args) will share this atom and store style elements in<head>
. Any container created by(dom/create-container element)
will have its own styles storage atom created by default. Finally, you can provide your own storage atom if you wish for some reason.