File size: 6,935 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
const C = "\u037c"
const COUNT = typeof Symbol == "undefined" ? "__" + C : Symbol.for(C)
const SET = typeof Symbol == "undefined" ? "__styleSet" + Math.floor(Math.random() * 1e8) : Symbol("styleSet")
const top = typeof globalThis != "undefined" ? globalThis : typeof window != "undefined" ? window : {}
// :: - Style modules encapsulate a set of CSS rules defined from
// JavaScript. Their definitions are only available in a given DOM
// root after it has been _mounted_ there with `StyleModule.mount`.
//
// Style modules should be created once and stored somewhere, as
// opposed to re-creating them every time you need them. The amount of
// CSS rules generated for a given DOM root is bounded by the amount
// of style modules that were used. So to avoid leaking rules, don't
// create these dynamically, but treat them as one-time allocations.
export class StyleModule {
// :: (Object<Style>, ?{finish: ?(string) → string})
// Create a style module from the given spec.
//
// When `finish` is given, it is called on regular (non-`@`)
// selectors (after `&` expansion) to compute the final selector.
constructor(spec, options) {
this.rules = []
let {finish} = options || {}
function splitSelector(selector) {
return /^@/.test(selector) ? [selector] : selector.split(/,\s*/)
}
function render(selectors, spec, target, isKeyframes) {
let local = [], isAt = /^@(\w+)\b/.exec(selectors[0]), keyframes = isAt && isAt[1] == "keyframes"
if (isAt && spec == null) return target.push(selectors[0] + ";")
for (let prop in spec) {
let value = spec[prop]
if (/&/.test(prop)) {
render(prop.split(/,\s*/).map(part => selectors.map(sel => part.replace(/&/, sel))).reduce((a, b) => a.concat(b)),
value, target)
} else if (value && typeof value == "object") {
if (!isAt) throw new RangeError("The value of a property (" + prop + ") should be a primitive value.")
render(splitSelector(prop), value, local, keyframes)
} else if (value != null) {
local.push(prop.replace(/_.*/, "").replace(/[A-Z]/g, l => "-" + l.toLowerCase()) + ": " + value + ";")
}
}
if (local.length || keyframes) {
target.push((finish && !isAt && !isKeyframes ? selectors.map(finish) : selectors).join(", ") +
" {" + local.join(" ") + "}")
}
}
for (let prop in spec) render(splitSelector(prop), spec[prop], this.rules)
}
// :: () → string
// Returns a string containing the module's CSS rules.
getRules() { return this.rules.join("\n") }
// :: () → string
// Generate a new unique CSS class name.
static newName() {
let id = top[COUNT] || 1
top[COUNT] = id + 1
return C + id.toString(36)
}
// :: (union<Document, ShadowRoot>, union<[StyleModule], StyleModule>, ?{nonce: ?string})
//
// Mount the given set of modules in the given DOM root, which ensures
// that the CSS rules defined by the module are available in that
// context.
//
// Rules are only added to the document once per root.
//
// Rule order will follow the order of the modules, so that rules from
// modules later in the array take precedence of those from earlier
// modules. If you call this function multiple times for the same root
// in a way that changes the order of already mounted modules, the old
// order will be changed.
//
// If a Content Security Policy nonce is provided, it is added to
// the `<style>` tag generated by the library.
static mount(root, modules, options) {
let set = root[SET], nonce = options && options.nonce
if (!set) set = new StyleSet(root, nonce)
else if (nonce) set.setNonce(nonce)
set.mount(Array.isArray(modules) ? modules : [modules], root)
}
}
let adoptedSet = new Map //<Document, StyleSet>
class StyleSet {
constructor(root, nonce) {
let doc = root.ownerDocument || root, win = doc.defaultView
if (!root.head && root.adoptedStyleSheets && win.CSSStyleSheet) {
let adopted = adoptedSet.get(doc)
if (adopted) return root[SET] = adopted
this.sheet = new win.CSSStyleSheet
adoptedSet.set(doc, this)
} else {
this.styleTag = doc.createElement("style")
if (nonce) this.styleTag.setAttribute("nonce", nonce)
}
this.modules = []
root[SET] = this
}
mount(modules, root) {
let sheet = this.sheet
let pos = 0 /* Current rule offset */, j = 0 /* Index into this.modules */
for (let i = 0; i < modules.length; i++) {
let mod = modules[i], index = this.modules.indexOf(mod)
if (index < j && index > -1) { // Ordering conflict
this.modules.splice(index, 1)
j--
index = -1
}
if (index == -1) {
this.modules.splice(j++, 0, mod)
if (sheet) for (let k = 0; k < mod.rules.length; k++)
sheet.insertRule(mod.rules[k], pos++)
} else {
while (j < index) pos += this.modules[j++].rules.length
pos += mod.rules.length
j++
}
}
if (sheet) {
if (root.adoptedStyleSheets.indexOf(this.sheet) < 0)
root.adoptedStyleSheets = [this.sheet, ...root.adoptedStyleSheets]
} else {
let text = ""
for (let i = 0; i < this.modules.length; i++)
text += this.modules[i].getRules() + "\n"
this.styleTag.textContent = text
let target = root.head || root
if (this.styleTag.parentNode != target)
target.insertBefore(this.styleTag, target.firstChild)
}
}
setNonce(nonce) {
if (this.styleTag && this.styleTag.getAttribute("nonce") != nonce)
this.styleTag.setAttribute("nonce", nonce)
}
}
// Style::Object<union<Style,string>>
//
// A style is an object that, in the simple case, maps CSS property
// names to strings holding their values, as in `{color: "red",
// fontWeight: "bold"}`. The property names can be given in
// camel-case—the library will insert a dash before capital letters
// when converting them to CSS.
//
// If you include an underscore in a property name, it and everything
// after it will be removed from the output, which can be useful when
// providing a property multiple times, for browser compatibility
// reasons.
//
// A property in a style object can also be a sub-selector, which
// extends the current context to add a pseudo-selector or a child
// selector. Such a property should contain a `&` character, which
// will be replaced by the current selector. For example `{"&:before":
// {content: '"hi"'}}`. Sub-selectors and regular properties can
// freely be mixed in a given object. Any property containing a `&` is
// assumed to be a sub-selector.
//
// Finally, a property can specify an @-block to be wrapped around the
// styles defined inside the object that's the property's value. For
// example to create a media query you can do `{"@media screen and
// (min-width: 400px)": {...}}`.
|