packages feed

atomic-css-0.1.0: src/Web/Atomic/Types/Rule.hs

module Web.Atomic.Types.Rule where

import Data.List qualified as L
import Data.Map.Strict (Map)
import Data.Map.Strict qualified as M
import Data.Maybe (isNothing)
import Data.String (IsString (..))
import Web.Atomic.Types.ClassName
import Web.Atomic.Types.Selector
import Web.Atomic.Types.Style


-- Rule: CSS Utility Classes  ------------------------------------------------

data Rule = Rule
  { className :: ClassName
  , selector :: RuleSelector
  , media :: [Media]
  , properties :: [Declaration]
  }
instance Eq Rule where
  r1 == r2 = ruleSelector r1 == ruleSelector r2
instance Ord Rule where
  r1 <= r2 = ruleSelector r1 <= ruleSelector r2
instance IsString Rule where
  fromString s = fromClass (fromString s)


data RuleSelector
  = CustomRule Selector
  | GeneratedRule (ClassName -> ClassName) (Selector -> Selector)
instance Semigroup RuleSelector where
  CustomRule s1 <> CustomRule s2 = CustomRule $ s1 <> s2
  GeneratedRule c1 s1 <> GeneratedRule c2 s2 = GeneratedRule (c2 . c1) (s2 . s1)
  -- ignore FromClass if CustomRule is set!
  CustomRule c <> _ = CustomRule c
  _ <> CustomRule c = CustomRule c
instance Monoid RuleSelector where
  mempty = GeneratedRule id id


-- | An empty rule that only adds the classname
fromClass :: ClassName -> Rule
fromClass cn = Rule cn mempty mempty mempty


rule :: ClassName -> [Declaration] -> Rule
rule cn = Rule cn mempty mempty


ruleMap :: [Rule] -> Map Selector Rule
ruleMap = L.foldl' (\m r -> M.insert (ruleSelector r) r m) M.empty


{- | Add a property to a class
addProp :: (ToStyleValue val) => Property -> val -> Rule -> Rule
addProp p v c =
  c{properties = Declaration p (toStyleValue v) : c.properties}
-}

-- mapSelector :: (Selector -> Selector) -> Rule -> Rule
-- mapSelector f c =
--   c
--     { selector = f c.selector
--     }

mapClassName :: (ClassName -> ClassName) -> Rule -> Rule
mapClassName f c =
  c
    { className = f c.className
    }


uniqueRules :: [Rule] -> [Rule]
uniqueRules [] = []
uniqueRules (r : rs) =
  r : replaceRules r (uniqueRules rs)


replaceRules :: Rule -> [Rule] -> [Rule]
replaceRules rnew rs =
  -- OVERRIDE RULES
  -- 1. if ANY property is set again, delete entire previous rule
  -- 2. if "manual" mode is set, pass it through!
  -- 3. if pseudo, media, etc, changes when these rules apply
  let ps = ruleProperties rnew
   in filter (not . matchesRule ps) rs
 where
  matchesRule ps r =
    (hasAnyProperty ps r || rnew.className == r.className)
      && ruleClassNameF rnew.selector "" == ruleClassNameF r.selector ""
      && isNothing (ruleCustomSelector rnew)
      && isNothing (ruleCustomSelector r)


hasAnyProperty :: [Property] -> Rule -> Bool
hasAnyProperty ps r = any hasProperty ps
 where
  hasProperty :: Property -> Bool
  hasProperty p = p `elem` ruleProperties r


ruleProperties :: Rule -> [Property]
ruleProperties r =
  fmap (\(p :. _) -> p) r.properties


lookupRule :: ClassName -> [Rule] -> Maybe Rule
lookupRule c = L.find (\r -> r.className == c)


ruleClassName :: Rule -> ClassName
ruleClassName r =
  ruleClassNameF r.selector r.className


ruleClassNameF :: RuleSelector -> ClassName -> ClassName
ruleClassNameF rs =
  case rs of
    CustomRule _ -> id
    GeneratedRule f _ -> f


ruleSelector :: Rule -> Selector
ruleSelector r =
  ruleSelectorF r.selector $ selector $ ruleClassName r


ruleSelectorF :: RuleSelector -> Selector -> Selector
ruleSelectorF rs =
  case rs of
    CustomRule s -> const s
    GeneratedRule _ f -> f


ruleCustomSelector :: Rule -> Maybe Selector
ruleCustomSelector r =
  case r.selector of
    CustomRule s -> Just s
    _ -> Nothing