Skip to content

Add more selector combinators #100

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/CSS.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/CSS/Render.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
83 changes: 79 additions & 4 deletions src/CSS/Selector.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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 |=
50 changes: 47 additions & 3 deletions test/Main.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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(..))
Expand Down Expand Up @@ -48,7 +48,7 @@ withSelector :: Rendered
withSelector = render do
a ? do
color blue
a ## hover ? do
a & hover ? do
color red

childSelector :: Rendered
Expand All @@ -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
Expand Down Expand Up @@ -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"

Expand All @@ -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"