All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scalacss.Pseudo.scala Maven / Gradle / Ivy

There is a newer version: 0.5.6
Show newest version
package scalacss

import scalaz.{Equal, ISet, Monoid, Order, Ordering, Semigroup}
import scalaz.std.string.stringInstance
import scalaz.std.tuple._
import scalaz.syntax.equal._

// TODO Rename PseudoXxxxx (selector probably)

/** http://www.w3.org/TR/selectors/#selector-syntax */
sealed class PseudoType(val priority: Short)
case object PseudoAttr    extends PseudoType(0)
case object PseudoClass   extends PseudoType(1)
case object PseudoElement extends PseudoType(2)

object PseudoType {
  // Class comes before Element: http://www.w3.org/TR/selectors/#selector-syntax
  implicit val psuedoTypeOrder: Order[PseudoType] = new Order[PseudoType] {
    def order(x: PseudoType, y: PseudoType): Ordering = {
      if (x == y) Ordering.EQ
      else if (x.priority < y.priority) Ordering.LT
      else Ordering.GT
    }
  }
}

/**
* A pseudo-class is used to define a special state of an element.
*/
sealed abstract class Pseudo extends Pseudo.ChainOps[Pseudo]  {
  import Pseudo.Composite

  val cssValue: String

  // apply() is used by Dsl
  final def modSelector(sel: CssSelector): CssSelector =
    sel + cssValue

  protected final def addPseudo(that: Pseudo): Pseudo =
    (this, that) match {
      // Avoid
      case (a, b: Pseudo1) if a contains b => a
      case (a: Pseudo1, b) if b contains a => b
      // Append
      case (a: Pseudo1     , b: Pseudo1     ) => Composite(a, ISet singleton b)
      case (p: Pseudo1     , Composite(h, t)) => Composite(p, t insert h)
      case (Composite(h, t), p: Pseudo1     ) => Composite(p, t insert h)
      case (Composite(a, b), Composite(c, d)) => Composite(a, b union d insert c)
    }

  @inline final def &(p: Pseudo): Pseudo =
    addPseudo(p)

  def contains(p: Pseudo1): Boolean
}

/**
* A single [[Pseudo]] instance. Not a composite.
*
* ''(Note: [[Not]] may have a nested value but is not a composite.''
*/
sealed abstract class Pseudo1(override val cssValue: String, val pseudoType: PseudoType) extends Pseudo {
  override def contains(p: Pseudo1) =
    this === p
}

object Pseudo {
  implicit val pseudoTC: Semigroup[Pseudo] with Equal[Pseudo] =
    new Semigroup[Pseudo] with Equal[Pseudo] {
      override def equalIsNatural                  = true
      override def equal (a: Pseudo, b: Pseudo)    = a == b
      override def append(a: Pseudo, b: => Pseudo) = a & b
    }

  implicit val optionPseudoTC: Monoid[Option[Pseudo]] =
    scalaz.std.option.optionMonoid

  implicit val psuedo1Order: Order[Pseudo1] =
    Order.orderBy[Pseudo1, (PseudoType, String)](p => (p.pseudoType, p.cssValue))

  final case class Composite private[scalacss](h: Pseudo1, t: ISet[Pseudo1]) extends Pseudo {
    override lazy val cssValue =
      (t insert h).foldLeft("")(_ + _.cssValue)

    override def contains(p: Pseudo1) =
      (h === p) || (t contains p)
  }

  final case class Custom(override val cssValue: String, override val pseudoType: PseudoType) extends Pseudo1(cssValue, pseudoType)

  type PseudoF = ChainOps[Pseudo] => Pseudo

  object ChainOps extends ChainOps[Pseudo] {
    protected def addPseudo(p: Pseudo) = p
  }

  /**
   * Trait providing a nice chaining DSL.
   */
  trait ChainOps[Out] {
    protected def addPseudo(p: Pseudo): Out
    final def active                                     : Out = addPseudo(Active)
    final def after                                      : Out = addPseudo(After)
    final def attrContains  (name: String, value: String): Out = addPseudo(AttrContains(name, value))
    final def attrEndsWith  (name: String, value: String): Out = addPseudo(AttrEndsWith(name, value))
    final def attrExists    (name: String)               : Out = addPseudo(AttrExists(name))
    final def attr          (name: String, value: String): Out = addPseudo(Attr(name, value))
    final def attrStartsWith(name: String, value: String): Out = addPseudo(AttrStartsWith(name, value))
    final def before                                     : Out = addPseudo(Before)
    final def checked                                    : Out = addPseudo(Checked)
    final def disabled                                   : Out = addPseudo(Disabled)
    final def empty                                      : Out = addPseudo(Empty)
    final def enabled                                    : Out = addPseudo(Enabled)
    final def firstChild                                 : Out = addPseudo(FirstChild)
    final def firstLetter                                : Out = addPseudo(FirstLetter)
    final def firstLine                                  : Out = addPseudo(FirstLine)
    final def firstOfType                                : Out = addPseudo(FirstOfType)
    final def focus                                      : Out = addPseudo(Focus)
    final def hover                                      : Out = addPseudo(Hover)
    final def inRange                                    : Out = addPseudo(InRange)
    final def invalid                                    : Out = addPseudo(Invalid)
    final def lang          (language: String)           : Out = addPseudo(Lang(language))
    final def lastChild                                  : Out = addPseudo(LastChild)
    final def lastOfType                                 : Out = addPseudo(LastOfType)
    final def link                                       : Out = addPseudo(Link)
    final def not           (f: PseudoF)                 : Out = addPseudo(Not(f))
    final def not           (selector: Pseudo)           : Out = addPseudo(Not(selector))
    final def not           (selector: String)           : Out = addPseudo(Not(selector))
    final def nthChild      (n: Int)                     : Out = addPseudo(NthChild(n.toString))
    final def nthChild      (q: NthQuery)                : Out = addPseudo(NthChild(q))
    final def nthLastChild  (n: Int)                     : Out = addPseudo(NthLastChild(n.toString))
    final def nthLastChild  (q: NthQuery)                : Out = addPseudo(NthLastChild(q))
    final def nthLastOfType (n: Int)                     : Out = addPseudo(NthLastOfType(n.toString))
    final def nthLastOfType (q: NthQuery)                : Out = addPseudo(NthLastOfType(q))
    final def nthOfType     (n: Int)                     : Out = addPseudo(NthOfType(n.toString))
    final def nthOfType     (q: NthQuery)                : Out = addPseudo(NthOfType(q))
    final def onlyChild                                  : Out = addPseudo(OnlyChild)
    final def onlyOfType                                 : Out = addPseudo(OnlyOfType)
    final def optional                                   : Out = addPseudo(Optional)
    final def outOfRange                                 : Out = addPseudo(OutOfRange)
    final def readOnly                                   : Out = addPseudo(ReadOnly)
    final def readWrite                                  : Out = addPseudo(ReadWrite)
    final def required                                   : Out = addPseudo(Required)
    final def selection                                  : Out = addPseudo(Selection)
    final def target                                     : Out = addPseudo(Target)
    final def valid                                      : Out = addPseudo(Valid)
    final def visited                                    : Out = addPseudo(Visited)
  }

  /** Selects the active link. */
  case object Active extends Pseudo1(":active", PseudoClass)

  /** Selects every checked <input> element. */
  case object Checked extends Pseudo1(":checked", PseudoClass)

  /** Selects every disabled <input> element. */
  case object Disabled extends Pseudo1(":disabled", PseudoClass)

  /** Selects every <p> element that has no children. */
  case object Empty extends Pseudo1(":empty", PseudoClass)

  /** Selects every enabled <input> element. */
  case object Enabled extends Pseudo1(":enabled", PseudoClass)

  /** Selects every <p> elements that is the first child of its parent. */
  case object FirstChild extends Pseudo1(":first-child", PseudoClass)

  /** Selects every <p> element that is the first <p> element of its parent. */
  case object FirstOfType extends Pseudo1(":first-of-type", PseudoClass)

  /** Selects the <input> element that has focus. */
  case object Focus extends Pseudo1(":focus", PseudoClass)

  /** Selects links on mouse over. */
  case object Hover extends Pseudo1(":hover", PseudoClass)

  /** Selects <input> elements with a value within a specified range. */
  case object InRange extends Pseudo1(":in-range", PseudoClass)

  /** Selects all <input> elements with an invalid value. */
  case object Invalid extends Pseudo1(":invalid", PseudoClass)

  /** Selects every <p> element with a lang attribute value starting with "it". */
  final case class Lang(language: String) extends Pseudo1(s":lang($language)", PseudoClass)

  /** Selects every <p> elements that is the last child of its parent. */
  case object LastChild extends Pseudo1(":last-child", PseudoClass)

  /** Selects every <p> element that is the last <p> element of its parent. */
  case object LastOfType extends Pseudo1(":last-of-type", PseudoClass)

  /** Selects all unvisited link. */
  case object Link extends Pseudo1(":link", PseudoClass)

  /** Selects every element that is not a <p> element. */
  final case class Not(selector: String) extends Pseudo1(s":not($selector)", PseudoClass)
  object Not {
    def apply(selector: Pseudo): Not = Not(selector.cssValue)
    def apply(f: PseudoF)      : Not = Not(f(ChainOps))
  }

  type NthQuery = String

  object NthChildBase {
    val queryPattern = """^((\+|-)?\d+|odd|even)$""".r.pattern
    val fQueryPattern = """^((\+|-)?\d*)?n((\+|-)\d+)?$""".r.pattern
  }

  abstract class NthChildBase(cls: String, query: NthQuery) extends Pseudo1(s":$cls($query)", PseudoClass) {
    require(
      NthChildBase.queryPattern.matcher(query).matches() || NthChildBase.fQueryPattern.matcher(query).matches(),
      s"Invalid NthQuery: '$query'")
  }

  /** Selects every <p> element that is the second child of its parent. */
  final case class NthChild(query: NthQuery) extends NthChildBase("nth-child", query)

  /** Selects every <p> element that is the second child of its parent, counting from the last child. */
  final case class NthLastChild(query: NthQuery) extends NthChildBase("nth-last-child", query)

  /** Selects every <p> element that is the second <p> element of its parent, counting from the last child. */
  final case class NthLastOfType(query: NthQuery) extends NthChildBase("nth-last-of-type", query)

  /** Selects every <p> element that is the second <p> element of its parent. */
  final case class NthOfType(query: NthQuery) extends NthChildBase("nth-of-type", query)

  /** Selects every <p> element that is the only <p> element of its parent. */
  case object OnlyOfType extends Pseudo1(":only-of-type", PseudoClass)

  /** Selects every <p> element that is the only child of its parent. */
  case object OnlyChild extends Pseudo1(":only-child", PseudoClass)

  /** Selects <input> elements with no "required" attribute. */
  case object Optional extends Pseudo1(":optional", PseudoClass)

  /** Selects <input> elements with a value outside a specified range. */
  case object OutOfRange extends Pseudo1(":out-of-range", PseudoClass)

  /** Selects <input> elements with a "readonly" attribute specified. */
  case object ReadOnly extends Pseudo1(":read-only", PseudoClass)

  /** Selects <input> elements with no "readonly" attribute. */
  case object ReadWrite extends Pseudo1(":read-write", PseudoClass)

  /** Selects <input> elements with a "required" attribute specified. */
  case object Required extends Pseudo1(":required", PseudoClass)

  // /** Selects the document's root element. */
  // case object Root extends Pseudo1(":root")

  /** Selects the current active #news element (clicked on a URL containing that anchor name). */
  case object Target extends Pseudo1(":target", PseudoClass)

  /** Selects all <input> elements with a valid value. */
  case object Valid extends Pseudo1(":valid", PseudoClass)

  /** Selects all visited link. */
  case object Visited extends Pseudo1(":visited", PseudoClass)


  /** Insert content after every <p> element. */
  case object After extends Pseudo1("::after", PseudoElement)

  /** Insert content before every <p> element. */
  case object Before extends Pseudo1("::before", PseudoElement)

  /** Selects the first letter of every <p> element. */
  case object FirstLetter extends Pseudo1("::first-letter", PseudoElement)

  /** Selects the first line of every <p> element. */
  case object FirstLine extends Pseudo1("::first-line", PseudoElement)

  /** Selects the portion of an element that is selected by a user  . */
  case object Selection extends Pseudo1("::selection", PseudoElement)


  class AttrSelector(name: String, value: String, op: String) extends Pseudo1(s"""[$name$op"$value"]""", PseudoAttr)

  /** Selects all elements with a name attribute. */
  case class AttrExists(name: String) extends Pseudo1(s"[$name]", PseudoAttr)

  /** Selects all elements with a name="value". */
  case class Attr(name: String, value: String) extends AttrSelector(name, value, "=")

  /** Selects all elements with a name containing the word value. */
  case class AttrContains(name: String, value: String) extends AttrSelector(name, value, "~=")

  /** Selects all elements with a name starting with value. */
  case class AttrStartsWith(name: String, value: String) extends AttrSelector(name, value, "|=")

  /** Selects all elements with a name ends with value. */
  case class AttrEndsWith(name: String, value: String) extends AttrSelector(name, value, "$=")
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy