diff --git a/src/CSS.purs b/src/CSS.purs index 0fe418f..eb4b9db 100644 --- a/src/CSS.purs +++ b/src/CSS.purs @@ -16,7 +16,7 @@ import CSS.Gradient (Extend, Radial, Ramp, circle, circular, closestCorner, clos import CSS.Property (class Val, Key(..), Literal(..), Prefixed(..), Value(..), cast, noCommas, plain, quote, value, (!)) as X import CSS.Render (Inline(..), Rendered, Sheet(..), collect, collect', face, feature, frame, getInline, getSheet, imp, kframe, mediaQuery, mediaType, merger, nel, predicate, properties, putInline, putStyleSheet, query', render, renderedInline, renderedSheet, rule', rules, selector, selector', selector'', sepWith) as X import CSS.Pseudo (hover) as X -import CSS.Selector (Path(..), Predicate(..), Refinement(..), Selector(..), child, deep, element, star, with, (##), (**), (|>)) as X +import CSS.Selector (Path(..), Predicate(..), Refinement(..), Selector(..), star, element, (|*), (|>), (|+), (&), byId, byClass, pseudo, func, attr, (@=), (^=), ($=), (*=), (~=), (|=)) as X import CSS.Size (Abs, Angle(..), Deg, Rad, Rel, Size(..), deg, em, ex, nil, pct, pt, px, rad, rem, sym, vh, vmax, vmin, vw) as X import CSS.String (class IsString, fromString) as X import CSS.Stylesheet (App(..), CSS, Feature(..), Keyframes(..), MediaQuery(..), MediaType(..), NotOrOnly(..), Rule(..), StyleM(..), fontFace, importUrl, key, keyframes, keyframesFromTo, prefixed, query, rule, runS, select, (?)) as X diff --git a/src/CSS/Render.purs b/src/CSS/Render.purs index 2c03db8..73db1b1 100644 --- a/src/CSS/Render.purs +++ b/src/CSS/Render.purs @@ -3,7 +3,7 @@ module CSS.Render where import Prelude import CSS.Property (Key(..), Prefixed(..), Value(..), plain) -import CSS.Selector (Path(..), Predicate(..), Refinement(..), Selector(..), with, star, element, (**), (|>)) +import CSS.Selector (Path(..), Predicate(..), Refinement(..), Selector(..), with, star, element, (|*), (|>)) import CSS.String (fromString) import CSS.Stylesheet (CSS, StyleM, App(..), Feature(..), Keyframes(..), MediaQuery(..), MediaType(..), Rule(..), runS) import Data.Array (null, (:), drop, sort, uncons, mapMaybe) @@ -166,10 +166,10 @@ merger :: NonEmpty Array App -> Selector merger (NonEmpty x xs) = case x of Child s -> maybe s (\xs' -> merger xs' |> s) $ nel xs - Sub s -> maybe s (\xs' -> merger xs' ** s) $ nel xs - Root s -> maybe s (\xs' -> s ** merger xs') $ nel xs + Sub s -> maybe s (\xs' -> merger xs' |* s) $ nel xs + Root s -> maybe s (\xs' -> s |* merger xs') $ nel xs Pop i -> maybe (element "TODO") merger <<< nel <<< drop i $ x : xs - Self sheetRules -> maybe (star `with` sheetRules) (\xs' -> merger xs' `with` sheetRules) $ nel xs + Self sheetRules -> maybe (star `with` sheetRules) (\xs' -> merger xs' `with` sheetRules) $ nel xs predicate :: Predicate -> String predicate (Id a ) = "#" <> a diff --git a/src/CSS/Selector.purs b/src/CSS/Selector.purs index a2ce281..ecd83ac 100644 --- a/src/CSS/Selector.purs +++ b/src/CSS/Selector.purs @@ -57,24 +57,99 @@ derive instance ordSelector :: Ord Selector instance isStringSelector :: IsString Selector where fromString s = case take 1 s of - "#" -> Selector (Refinement [Id $ drop 1 s]) Star - "." -> Selector (Refinement [Class $ drop 1 s]) Star + "#" -> Selector (byId $ drop 1 s) Star + "." -> Selector (byClass $ drop 1 s) Star _ -> Selector (Refinement []) (Elem s) +-- | The star selector applies to all elements. +-- | Maps to `*` in CSS. star :: Selector star = Selector (Refinement []) Star +-- | Select elements by name. element :: String -> Selector element e = Selector (Refinement []) (Elem e) +-- | The deep selector composer. +-- | Maps to `sel1 sel2` in CSS. deep :: Selector -> Selector -> Selector deep a b = Selector (Refinement []) (Deep a b) -infix 6 deep as ** +infix 6 deep as |* +-- | The child selector composer. +-- | Maps to `sel1 > sel2` in CSS. child :: Selector -> Selector -> Selector child a b = Selector (Refinement []) (PathChild a b) infix 6 child as |> +-- | The adjacent selector composer. +-- | Maps to `sel1 + sel2` in CSS. +adjacent :: Selector -> Selector -> Selector +adjacent a b = Selector (Refinement []) (Adjacent a b) +infix 6 adjacent as |+ + +-- | The filter selector composer, adds a filter to a selector. +-- | Maps to something like `sel#filter`, `sel.filter` or `sel:filter` in CSS, +-- | depending on the filter. with :: Selector -> Refinement -> Selector with (Selector (Refinement fs) e) (Refinement ps) = Selector (Refinement (fs <> ps)) e -infix 6 with as ## +infix 6 with as & + +-- | Filter elements by id. +byId :: String -> Refinement +byId = Refinement <<< pure <<< Id + +-- | Filter elements by class. +byClass :: String -> Refinement +byClass = Refinement <<< pure <<< Class + +-- | Filter elements by pseudo selector or pseudo class. +-- | The preferred syntax is to use `:pseudo-selector` or +-- | use one of the predefined ones from `CSS.Pseudo`. +pseudo :: String -> Refinement +pseudo = Refinement <<< pure <<< Pseudo + +-- | Filter elements by pseudo selector functions. +-- | The preferred way is to use one of the predefined functions from `CSS.Pseudo`. +func :: String -> Array String -> Refinement +func f = Refinement <<< pure <<< PseudoFunc f + +-- | Filter elements based on the presence of a certain attribute. +attr :: String -> Refinement +attr = Refinement <<< pure <<< Attr + +-- | Filter elements based on the presence of a +-- | certain attribute with the specified value. +attrVal :: String -> String -> Refinement +attrVal a = Refinement <<< pure <<< AttrVal a +infix 6 attrVal as @= + +-- | Filter elements based on the presence of a certain attribute that +-- | begins with the selected value. +attrBegins :: String -> String -> Refinement +attrBegins a = Refinement <<< pure <<< AttrBegins a +infix 6 attrBegins as ^= + +-- | Filter elements based on the presence of a certain attribute that +-- | ends with the specified value. +attrEnds :: String -> String -> Refinement +attrEnds a = Refinement <<< pure <<< AttrEnds a +infix 6 attrEnds as $= + +-- | Filter elements based on the presence of a certain attribute that contains +-- | the specified value as a substring. +attrContains :: String -> String -> Refinement +attrContains a = Refinement <<< pure <<< AttrContains a +infix 6 attrContains as *= + +-- | Filter elements based on the presence of a certain attribute that +-- | have the specified value contained in a space separated list. +attrSpace :: String -> String -> Refinement +attrSpace a = Refinement <<< pure <<< AttrSpace a +infix 6 attrSpace as ~= + +-- | Filter elements based on the presence of a certain attribute that +-- | have the specified value contained in a hyphen separated list. +attrHyph :: String -> String -> Refinement +attrHyph a = Refinement <<< pure <<< AttrHyph a +infix 6 attrHyph as |= diff --git a/test/Main.purs b/test/Main.purs index d094f46..4f0300a 100644 --- a/test/Main.purs +++ b/test/Main.purs @@ -4,7 +4,7 @@ import Prelude import Effect (Effect) import Effect.Exception (error, throwException) -import CSS (Rendered, Path(..), Predicate(..), Refinement(..), Selector(..), FontFaceSrc(..), FontFaceFormat(..), renderedSheet, renderedInline, fromString, selector, block, display, render, borderBox, boxSizing, contentBox, blue, color, body, a, p, px, dashed, border, inlineBlock, red, (?), (##), (|>), (**), hover, fontFaceSrc, fontStyle, deg, zIndex, textOverflow, opacity) +import CSS (Rendered, Path(..), Predicate(..), Refinement(..), Selector(..), FontFaceSrc(..), FontFaceFormat(..), renderedSheet, renderedInline, fromString, selector, block, display, render, borderBox, boxSizing, contentBox, blue, color, body, a, p, px, dashed, border, inlineBlock, red, (?), (&), (|>), (|*), (|+), byId, byClass, (@=), (^=), ($=), (*=), (~=), (|=), hover, fontFaceSrc, fontStyle, deg, zIndex, textOverflow, opacity) import CSS.FontStyle as FontStyle import CSS.Text.Overflow as TextOverflow import Data.Maybe (Maybe(..)) @@ -48,7 +48,7 @@ withSelector :: Rendered withSelector = render do a ? do color blue - a ## hover ? do + a & hover ? do color red childSelector :: Rendered @@ -58,9 +58,43 @@ childSelector = render do deepSelector :: Rendered deepSelector = render do - p ** a ? do + p |* a ? do display block +byClassById :: Rendered +byClassById = render do + a & byClass "bar" ? color red + p & byId "foo" ? display block + +attrVal :: Rendered +attrVal = render do + p & ("foo" @= "bar") ? display block + +attrBegins :: Rendered +attrBegins = render do + p & ("foo" ^= "bar") ? display block + +attrEnds :: Rendered +attrEnds = render do + p & ("foo" $= "bar") ? display block + +attrContains :: Rendered +attrContains = render do + p & ("foo" *= "bar") ? display block + +attrSpace :: Rendered +attrSpace = render do + p & ("foo" ~= "bar") ? display block + +attrHyph :: Rendered +attrHyph = render do + p & ("foo" |= "bar") ? display block + +adjacentSelector :: Rendered +adjacentSelector = render do + a |+ a ? do + display inlineBlock + exampleFontStyle1 :: Rendered exampleFontStyle1 = render do fontStyle FontStyle.italic @@ -112,6 +146,7 @@ main = do renderedSheet withSelector `assertEqual` Just "a { color: hsl(240.0, 100.0%, 50.0%) }\na:hover { color: hsl(0.0, 100.0%, 50.0%) }\n" renderedSheet childSelector `assertEqual` Just "p > a { z-index: 9 }\n" renderedSheet deepSelector `assertEqual` Just "p a { display: block }\n" + renderedSheet adjacentSelector `assertEqual` Just "a + a { display: inline-block }\n" renderedSheet nestedNodes `assertEqual` Just "#parent { display: block }\n#parent #child { display: block }\n" @@ -127,3 +162,12 @@ main = do renderedInline exampleTextOverflow1 `assertEqual` Just "text-overflow: ellipsis" renderedInline exampleTextOverflow2 `assertEqual` Just "text-overflow: \"foobar\"" + + renderedSheet byClassById `assertEqual` Just "a.bar { color: hsl(0.0, 100.0%, 50.0%) }\np#foo { display: block }\n" + renderedSheet attrVal `assertEqual` Just "p[foo='bar'] { display: block }\n" + renderedSheet attrBegins `assertEqual` Just "p[foo^='bar'] { display: block }\n" + renderedSheet attrEnds `assertEqual` Just "p[foo$='bar'] { display: block }\n" + renderedSheet attrContains `assertEqual` Just "p[foo*='bar'] { display: block }\n" + renderedSheet attrSpace `assertEqual` Just "p[foo~='bar'] { display: block }\n" + renderedSheet attrHyph `assertEqual` Just "p[foo|='bar'] { display: block }\n" +