packages feed

miso-css-0.0.2: miso-css.cabal

cabal-version: 3.0
name:          miso-css
version:       0.0.2

synopsis:      wrapper over miso checking CSS classes applicability through dependent types
description:
    == Motivation
    #motivation#
    
    __miso-css__ is an evolutionary step ahead from
    <https://github.com/yaitskov/css-class-bindings css-class-bindings>.
    
    CSS class of an atomic selector can be applied to any DOM element, but
    that is not true for classes used in composite selectors. Rules with
    partially matched selectors are silently ignored by browser and this
    open a door for introducing bugs during consequent changes.
    Css-class-binding just cannot cope with such problem and miso-css uses
    dependent types to track what CSS classes can applied to HTML elements.
    
    == Usage
    #usage#
    
    miso-css runs a css parser to extract CSS selectors and generates
    Haskell constants for every CSS class with correspondent name that is
    found in the input. A type of such constant describes all possible ways
    the class can used in DOM.
    
    Besides that the library ships __E__ type representing an HTML element
    and a set of operators for constructing tags and combining them in DOM
    tree. E is miso
    <https://hackage-content.haskell.org/package/miso/docs/Miso-Types.html#t:View VNode>
    type protected with a few type parameters.
    
    === Composing tags
    #composing-tags#
    
    Before jumping straight to style application lets get familiar with
    syntax for tag composition because it is different in vanilla
    <https://hackage-content.haskell.org/package/miso miso>.
    
    ==== Appending a child
    #appending-a-child#
    
    > div_ </ p_
    
    > <div>
    >   <p></p>
    > </div>
    
    ==== Adding a sibling
    #adding-a-sibling#
    
    > ul_ </ li_ </ li_
    
    > <ul>
    >   <li></li>
    >   <li></li>
    > </ul>
    
    ==== Appending a child to child
    #appending-a-child-to-child#
    
    > body_ </ (section_ </ p_)
    
    > <body>
    >   <section>
    >     <p></p>
    >   </section>
    > </body>
    
    ==== Adding CDATA
    #adding-cdata#
    
    > a_ <@ "click"
    
    > <a>click</a>
    
    ==== Adding a raw miso DOM chunk
    #adding-a-raw-miso-dom-chunk#
    
    > import Miso.Html qualified as MH
    > import Miso.Html.Property qualified as MH
    >
    > go = div_ =< MH.p_ [] [ "h" ]
    
    > <div>
    >   <p>h</p>
    > </div>
    
    ==== Adding tag attribute
    #adding-tag-attribute#
    
    > a_ =<| atr @"href" "http://link.com"
    
    > <a href="http://link.com"></a>
    
    ==== Binding event handler
    #binding-event-handler#
    
    > button_ =! onClick YourActionDc
    
    ==== Applying CSS class
    #applying-css-class#
    
    > {-# LANGUAGE QuasiQuotes #-}
    > {-# OPTIONS_GHC -Wno-missing-signatures #-}
    > [css|.red { color: red; }|]
    >
    > div_ =. red
    
    > <div class="red"></div>
    
    ==== Adding tag ID
    #adding-tag-id#
    
    Handmade tag id:
    
    > div_ =# ElementId "footer"
    
    Generated tag id:
    
    > {-# LANGUAGE QuasiQuotes #-}
    > {-# OPTIONS_GHC -Wno-missing-signatures #-}
    > [css|#footer { color: red; }|]
    >
    > div_ =# Footer
    
    > <div id="footer"></div>
    
    ==== Mix all at once
    #mix-all-at-once#
    
    > {-# LANGUAGE QuasiQuotes #-}
    > {-# OPTIONS_GHC -Wno-missing-signatures #-}
    > [css|.form .red { color: red; }|]
    >
    > div_ =. form =# ElementId "footer"
    >   </ (a_ =. red =<| atr @"href" "/click.php?x=1"
    >       </ (span_ <@ "Click me"))
    
    > <div class="form" id="footer>
    >   <a class="red" href="/click.php?x=1">
    >     <span>Click me</span>
    >   </a>
    > </div>
    
    === Breaking rules
    #breaking-rules#
    
    Until now all above samples must be valid and should type check. This
    section enumerates HTML snippets with ill-applied classes, expected
    errors and comments.
    
    ==== There can be only one
    #there-can-be-only-one#
    
    An element ID can be used once in a HTML document.
    
    > div_ =# ElementId "Duncan MacLeod"
    >   </ div_ =# ElementId "Duncan MacLeod"
    
    > Couldn't match type: '[DuplicatedId "Duncan MacLeod"]
    >                with: '[]
    
    ==== Parent class is missing
    #parent-class-is-missing#
    
    > [css|.a .b {}|]
    >
    > div_ =. b
    
    The error message is a list of triples where first element is a list of
    not applied classes, ids (hashes), tag names or attribute names.
    
    > [([C "a"], [], [])]
    
    Class __a__ and __b__ are missing:
    
    > [css|.a .b .c {}|]
    >
    > div_ =. c
    
    > [ ([C "b"], [], [])
    > , ([C "a"], [], [])
    > ]
    
    ==== B element
    #b-element#
    
    When selector with a child relation is partially applied the triple
    contains B element. It is a synthetic element preventing the failed rule
    from matching later somewhere upper in DOM by an accident.
    
    > [css|.a > .b {}|]
    >
    > div_ </ div_ =. b
    
    > [([B, C "a"], [], [])]
    
    ==== One of classes is missing
    #one-of-classes-is-missing#
    
    Second element of triple is a list of applied classes. It helps to
    understand what worked out and what didn’t in a composite selector.
    
    > [css|.a.b > .c {}|]
    >
    > div_ =. a </ div_ =. c
    
    > [([B, C "b"], [C "a"], [])]
    
    ===== Sibling is missing
    #sibling-is-missing#
    
    The third element of triple explains sibling errors.
    
    > [css|.a + .b {}|]
    >
    > div_ </ div_ =. b
    
    Class __a__ is not applied:
    
    > [([B], [], [[ [B], [C "a"]]])]
    
    === Non-leaf classes constraints
    #non-leaf-classes-constraints#
    
    By default non-leaf classes in selector don’t contribute to constraints.
    e.g.
    
    > .a > .b .c
    
    @b@ doesn’t require @a@ as immediate parent it is handled by constraints
    form @c@.
    
    It is possible to generate constraints for @b@ to make checking even
    stricter, though in such mode following DOM can’t pass type check:
    
    > enableRulesForNonLeafClasses
    >
    > [css|.a > .b .c {}|]
    > test_t = testGroup cssAsLiteralText
    >   [ doNotTcNoBr [] [[[(JustNow, [B], [C "a"], [])]]] $
    >       div_ =. a </ (div_ =. b </ (div_ =. b </ div_ =. c))
    >   ]
    
    === E type
    #e-type#
    
    > data E
    >      model
    >      action
    >      (en :: Symbol)
    >      (es :: ElementStructure)
    >      (re :: Maybe Root)
    >      (ei :: Maybe Symbol)
    >      (atrs :: [Symbol])
    >      (knownIds :: KnownIDS)
    >      (cls :: [Symbol])
    >      (l :: [[[Seg]]])
    >      (children :: [[SubSeg]])
    
    First two parameters __model__ and __action__ are forwarded to miso
    <https://hackage-content.haskell.org/package/miso/docs/Miso-Types.html#t:View VNode>
    type.
    
    ==== en - tag name
    #en---tag-name#
    
    In ghci session:
    
    > :t div_
    > div_
    >   :: E model
    >        action
    >        "div"
    > ...
    
    ==== es - element structure
    #es---element-structure#
    
    Most often its value is __Composite__ which means that the element could
    have children. /Es/ parameter of /CDATA/ element is __Atomic__.
    
    ==== re - root indicator
    #re---root-indicator#
    
    It is a root tag indicator. A root tag cannot be adopted.
    
    > [css|:root > .a {}|]
    
    ==== ei - HTML tag hash
    #ei---html-tag-hash#
    
    > div_ =# ElementId "Duncan"
    
    ==== atrs - names of tag attributes
    #atrs---names-of-tag-attributes#
    
    > :t a_ =# ElementId "x" =<| atr @"href" "/click.php?x=1"
    > ...
    >        ["href", "id"]
    > ...
    
    ==== knownIds - hashes used in tag descendants
    #knownids---hashes-used-in-tag-descendants#
    
    > :t div_ =# ElementId "x" </ div_ =# ElementId "y" </ div_ =# ElementId "z"
    > ...
    >        (KnownIds '[] ["x", "y", "z"])
    > ...
    
    ==== cls - classes applied to tag
    #cls---classes-applied-to-tag#
    
    Classes applied to children and descendants are not included.
    
    > :t div_ =. a =. b </ div_ =. c
    > ...
    >        ["b", "a"]
    > ...
    
    ==== l - ancestor constraints
    #l---ancestor-constraints#
    
    The parameter describes requirements to be satisfied in ancestors of the
    tag.
    
    > [css|.a .b {}|]
    
    > :t div_ =. b
    > ...
    >        '[ '[['(AutoClean, '[], '[], '[]),
    >              '(NowOrLater, '[C "a"], '[], '[])]]]
    > ...
    
    ==== children
    #children#
    
    List of lists of children subselectors in reverse order.
    
    > [css|.a {} .b {}|]
    
    > :t div_ </ ul_ =. a =. b </ ol_ =# ElementId "x"
    > ...
    >        [[I "x", T "ol"], [T "ul", C "b", C "a"]]
    > ...
    
    === Hello World
    #hello-world#
    
    > {-# LANGUAGE QuasiQuotes #-}
    > {-# OPTIONS_GHC -Wno-missing-signatures #-}
    > module Miso.Css.Test.HelloWorld where
    >
    > import Miso ( component, App, CSS(Style), Component(styles), View )
    > import Miso.Css
    > import Prelude
    >
    > type Model = ()
    > type Action = ()
    >
    > -- default name is "cssAsLiteralText"
    > renameCssTextConst "cssFromQq"
    >
    > [css|
    > .c .b .a {
    >   color: #fc2c2c;
    > }
    > |]
    >
    > -- instead of quasi-quoted CSS
    > -- the whole CSS file can be included with:
    > --   includeCss "assets/style.css"
    >
    > app :: App Model Action
    > app = (component () pure viewModel)
    >   { styles = [ Style cssFromQq ] }
    >
    > {-
    > viewModel produce following HTML snippet:
    >
    >     <div class="c">
    >       <div class="b">
    >         <button class="a">
    >           Submit
    >         </button>
    >       </div>
    >     </div>
    >
    > html_ and body_ don't produce tags,
    > because miso mount cannot be higher than body tag.
    >
    > they serve just for type checking purpose
    > (e.g. html_ satisfies :root pseudo class)
    >
    > -}
    > viewModel :: Model -> View Model Action
    > viewModel () = toView . html_ . body_ $
    >   div_ =. c
    >   </ (div_ =. b
    >        </ (button_ =. a
    >             <@ "Submit"))
    
    == Development environment
    #development-environment#
    
    HLS should be available inside the default dev shell.
    
    > $ nix develop
    > $ emacs src/*/*/Qq.hs &
    > $ cabal build
    > $ cabal test --test-option=--hide-successes
    
    miso-css was developed with miso v1.9
homepage:      http://github.com/yaitskov/miso-css
license:       BSD-3-Clause
license-file:  LICENSE
author:        Daniil Iaitskov
maintainer:    dyaitskov@gmail.com
copyright:     Daniil Iaitkov 2026
category:      Miso, HTML, CSS, Template Haskell
build-type:    Simple
bug-reports:   https://github.com/yaitskov/miso-css/issues
extra-source-files:
  test/style.css
extra-doc-files:
  changelog.md
tested-with:
  GHC == 9.12.2

source-repository head
  type:
    git
  location:
    https://github.com/yaitskov/miso-css.git

common base
  default-language: GHC2024
  ghc-options: -Wall
  default-extensions:
    DefaultSignatures
    NoImplicitPrelude
    OverloadedRecordDot
    OverloadedStrings
    TemplateHaskell
  build-depends:
    , base >=4.7 && < 5
    , template-haskell  < 3

library
  import: base
  hs-source-dirs: src
  exposed-modules:
    Miso.Css
    Miso.Css.Escape
    Miso.Css.Event
    Miso.Css.Gen
    Miso.Css.IncludeCss
    Miso.Css.List
    Miso.Css.Miso
    Miso.Css.Operator
    Miso.Css.Parser
    Miso.Css.Prelude
    Miso.Css.Qq
    Miso.Css.Segment
    Miso.Css.Sibling
    Miso.Css.Style
    Miso.Css.Style.E
    Miso.Css.Style.AncestorConstraint
    Miso.Css.Style.OrClass
    Miso.Css.Style.PostAppend
    Miso.Css.Style.PreAppend
    Miso.Css.Tags
  build-depends:
    , add-dependent-file >= 0.0.2 && < 1
    , containers         < 1
    , css-parser         < 1
    , filepath           < 2
    , miso               >= 1.11 && < 1.12
    , mtl                < 3
    , tagged             < 1
    , template-haskell   < 3
    , text               < 3

test-suite test
  import: base
  type: exitcode-stdio-1.0
  main-is: Driver.hs
  default-extensions:
    MultilineStrings
    QuasiQuotes
  other-modules:
    Miso.Css.Test.BindEventHandlers
    Miso.Css.Test.HelloWorld
    Miso.Css.Test.IncludeCssAsserts
    Miso.Css.Test.Prelude
    Miso.Css.Test.QqAsserts
    Miso.Css.Test.Style
    Miso.Css.Test.StyleMock
    Miso.Css.Test.Th.AmoreBmoreC
    Miso.Css.Test.Th.AplusBplusC
    Miso.Css.Test.Th.DivPlusPtildeSpanMoreAplusB
    Miso.Css.Test.Th.DotAdotB
    Miso.Css.Test.Th.ForkBranchOnJnMatchAfterNol
    Miso.Css.Test.Th.ForkBranchSibling
    Miso.Css.Test.Th.PmoreXplusYplusZmoreAspaceBmoreC
    Miso.Css.Test.Th.RootMoreA
    Discovery
  hs-source-dirs:
    test
  ghc-options: -Wall -Wno-missing-signatures -rtsopts -threaded -main-is Driver
  build-depends:
    , bytestring
    , miso
    , miso-css
    , QuickCheck
    , tasty
    , tasty-discover
    , tasty-hunit
    , tasty-quickcheck