//
// Copyright (c) 2014, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 19 Dec 2014 Andy Frank Creation
//
using concurrent
**
** Style models CSS style properties for an Elem.
**
@Js class Style
{
** Private ctor.
private new make() {}
** The CSS classes for this element.
native Str[] classes
** Return true if this element has the given CSS class name,
** or false if it does not.
native Bool hasClass(Str name)
** Add the given CSS class name to this element. If this
** element already contains the given class name, then this
** method does nothing. Returns this.
native This addClass(Str name)
** Remove the given CSS class name to this element. If this
** element does not have the given class name, this method
** does nothing. Returns this.
native This removeClass(Str name)
**
** Toggle the presence of the given CSS class name based on
** the 'cond' argument:
** - 'null': remove class if present, or add if missing
** - 'true': always add class (see `addClass`)
** - 'false': always remove class(see `removeClass`)
**
This toggleClass(Str name, Bool? cond := null)
{
if (cond?.not ?: hasClass(name)) removeClass(name)
else addClass(name)
return this
}
**
** Add a psuedo-class CSS definietion to this element. A new
** class name is auto-generated and used to prefix 'name',
** 'name' must start with the ':' character. Returns the
** generated class name.
**
** style.addPseudoClass(":hover", "background: #eee")
**
Str addPseudoClass(Str name, Str css)
{
if (!name.startsWith(":"))
throw ArgErr("Pseudo-class name must start with ':'")
key := "$name/$css"
cls := pseudoCache[key]
if (cls == null)
{
cls = "dom-style-autogen-$counter.getAndIncrement"
Win.cur.doc.head.add(Elem("style") {
it->type = "text/css"
it.text = ".${cls}${name} { $css }"
})
pseudoCache[key] = cls
}
addClass(cls)
return cls
}
** Clear all style declarations.
native This clear()
** Get the computed property value.
native Obj? computed(Str name)
**
** Get the effetive style property value, which is the most
** specific style or CSS rule in effect on this node. Returns
** 'null' if no rule in effect for given property.
**
** This method is restricted to stylesheets that have originated
** from the same domain as the document. Any rules that may be
** applied from an external sheet will not be included.
**
native Obj? effective(Str name)
** Get the given property value.
** color := style["color"]
@Operator native Obj? get(Str name)
** Set the given propery value. If 'val' is null this
** property is removed.
** style["color"] = "#f00"
@Operator This set(Str name, Obj? val)
{
if (val == null) { setProp(name, null); return this }
sval := ""
switch (val?.typeof)
{
case Duration#: sval = "${val->toMillis}ms"
default: sval = val.toStr
}
if (vendor.containsKey(name))
{
setProp("-webkit-$name", sval)
setProp( "-moz-$name", sval)
setProp( "-ms-$name", sval)
}
if (vendorVals.any |v| { sval.startsWith(v) })
{
setProp(name, "-webkit-$sval")
setProp(name, "-moz-$sval")
setProp(name, "-ms-$sval")
}
setProp(name, sval)
return this
}
** Set all the given property values.
** style.setAll(["color":"#f00", "font-weight":"bold"])
This setAll(Str:Obj? map)
{
map.each |v,n| { set(n,v) }
return this
}
** Set properties via CSS text.
** style.setCss("color: #f00; font-weight: bold;")
This setCss(Str css)
{
css.split(';').each |s|
{
if (s.isEmpty) return
i := s.index(":")
n := s[0..<i].trim
v := s[i+1..-1].trim
set(n, v)
}
return this
}
**
** Get or set an attribute. Attribute names should be specifed
** in camel case:
** style->backgroundColor == style["background-color"]
**
override Obj? trap(Str name, Obj?[]? args := null)
{
name = fromCamel(name)
if (args == null || args.isEmpty) return get(name)
set(name, args.first)
return null
}
** Set CSS property.
private native Void setProp(Str name, Str? val)
** Convert camel case to hyphen notation.
private Str fromCamel(Str s)
{
h := StrBuf()
s.each |ch|
{
if (ch.isUpper) h.addChar('-').addChar(ch.lower)
else h.addChar(ch)
}
return h.toStr
}
** Convenience for `toVendor` on a list.
static internal Str[] toVendors(Str[] names)
{
acc := Str[,]
names.each |n| { acc.addAll(toVendor(n)) }
return acc
}
** Break out standard CSS property into required vendor prefixes.
static internal Str[] toVendor(Str name)
{
if (vendor.containsKey(name))
{
// 14-Aug-2015: Safari 8.0.7 chokes on foreign vendor prefixes when
// a transition animatation is used directly in the 'style' attr
w := Win.cur
if (w.isWebkit) return ["-webkit-$name"]
if (w.isFirefox) return [ "-moz-$name", name]
if (w.isIE) return [ "-ms-$name", name]
}
return [name]
}
** Property names that require vendor prefixes.
private const static Str:Str[] vendor := [:].setList([
"align-content",
"align-items",
"animation",
"animation-delay",
"animation-direction",
"animation-duration",
"animation-iteration-count",
"animation-name",
"animation-play-state",
"animation-timing-function",
"animation-fill-mode",
"flex",
"flex-direction",
"flex-wrap",
"justify-content",
"transform",
"user-select",
])
** Property values that require vendor prefixes.
private const static Str[] vendorVals := [
"linear-gradient"
]
private static const AtomicInt counter := AtomicInt(0)
private static Str:Str pseudoCache() { (pseudoCacheRef.val as Unsafe).val }
private static const AtomicRef pseudoCacheRef := AtomicRef(Unsafe(Str:Str[:]))
}