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

scalacss.internal.Pseudo.scala Maven / Gradle / Ivy

The newest version!
package scalacss.internal

import japgolly.univeq._
import scala.collection.immutable.SortedSet

// TODO Rename PseudoXxxxx (selector probably)

/** http://www.w3.org/TR/selectors/#selector-syntax */
sealed abstract class PseudoType(val priority: Short)

object PseudoType {
  case object Attr    extends PseudoType(0)
  case object Class   extends PseudoType(1)
  case object Element extends PseudoType(2)

  implicit def univEq: UnivEq[PseudoType] = UnivEq.derive

  implicit val ordering: Ordering[PseudoType] =
    new Ordering[PseudoType] {
      def compare(x: PseudoType, y: PseudoType): Int =
        x.priority - y.priority
    }
}

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

  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: Single) if a contains b => a
      case (a: Single, b) if b contains a => b
      // Append
      case (a: Single      , b: Single      ) => Composite(a, SortedSet.empty[Single] + b)
      case (p: Single      , Composite(h, t)) => Composite(p, t + h)
      case (Composite(h, t), p: Single      ) => Composite(p, t + h)
      case (Composite(a, b), Composite(c, d)) => Composite(a, b ++ d + c)
    }

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

  def contains(p: Single): Boolean
}

object Pseudo {

  type PseudoF = ChainOps[Pseudo] => Pseudo

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

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

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

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

  /** Selects the active link. */
  case object Active extends Single(":active", PseudoType.Class)

  /** Selects every checked <input> element. */
  case object Checked extends Single(":checked", PseudoType.Class)

  /** Selects every disabled <input> element. */
  case object Disabled extends Single(":disabled", PseudoType.Class)

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

  /** Selects every enabled <input> element. */
  case object Enabled extends Single(":enabled", PseudoType.Class)

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

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

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

  /** Selects links on mouse over. */
  case object Hover extends Single(":hover", PseudoType.Class)

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

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

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

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

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

  /** Selects all unvisited link. */
  case object Link extends Single(":link", PseudoType.Class)

  /** Selects every element that is not a <p> element. */
  final case class Not(selector: String) extends Single(s":not($selector)", PseudoType.Class)
  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
  }

  sealed abstract class NthChildBase(cls: String, query: NthQuery) extends Single(s":$cls($query)", PseudoType.Class) {
    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 Single(":only-of-type", PseudoType.Class)

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

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

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

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

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

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

  // /** 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 Single(":target", PseudoType.Class)

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

  /** Selects all visited link. */
  case object Visited extends Single(":visited", PseudoType.Class)

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

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

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

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

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

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

  final case class AttrSelector(name: String, value: String, op: String)
    extends Single(s"""[$name$op"$value"]""", PseudoType.Attr)

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

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

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

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

  implicit def univEq: UnivEq[Single] = UnivEq.derive

  implicit val order: Ordering[Single] =
    new Ordering[Single] {
      def compare(x: Single, y: Single): Int = {
        val n = PseudoType.ordering.compare(x.pseudoType, y.pseudoType)
        if (n ==* 0)
          x.cssValue compareTo y.cssValue
        else
          n
      }
    }

  // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  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 attr          (name: String, value: String): Out = addPseudo(Attr(name, value))
    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 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)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy