elm-css
elm-css copied to clipboard
Support CSS Grid Layout
I would like to start a discussion around supporting the CSS Grid features. CSS Grid is usable on over 50% of devices. Grid is also a powerful enough feature that it is the future of CSS. However, it is rather complicated, from an API perspective. Here are the properties that would need to be supported:
- display: grid
- grid-template-columns
- grid-template-rows
- grid-template-areas
- grid-template
- grid-auto-columns
- grid-auto-rows
- grid-auto-flow
- grid
- grid-row-start
- grid-column-start
- grid-row-end
- grid-column-end
- grid-row
- grid-column
- grid-area
- grid-row-gap
- grid-column-gap
- grid-gap
It also has the repeat
, minmax
, and fit-content
css functions.
I believe that also supporting justify-items
, justify-content
, align-items
, and align-content
would also be good to do, but I do think that an initial implementation could leave off some of the properties that are just shorthand properties, like grid-template
.
Some of these are fairly straightforward, like grid-gap
and friends, as they take existing types (ExplicitLength units, I believe), and have a limited number of input formats.
Things get more complicated with properties like grid-template-columns
, which has a variety of formats and limitations. Formal spec can be found here. It is further complicated by the new repeat
and minmax
functions essentially have multiple type signatures, depending on what else is around them.
I took a look at this over the weekend, so I already have a few ideas, but I will put them in a comment, so that they are not mixed up with the problem description.
Over the weekend, I started looking at grid-template-columns
, and identified a number of types that I think would be useful, along with a few options for function type signatures.
Pulling types out of the formal spec, it looked to me that all or some of the following were useful types to keep track of (the names are copied from the formal spec, so it should be possible to follow along):
- TrackListOrAutoTrackListOrNone (This is what we must ultimately turn into. It might not be needed, depending on how the property functions work)
- TrackSizeOrTrackRepeatOrLineNames
- TrackSizeOrTrackRepeat
- LineNames
- TrackSizeOrLineNames
- TrackSize
- InflexibleBreadth
- TrackBreadth
- AutoRepeat
- AutoFillOrAutoFit
- FixedSizeOrFixedRepeatOrLineNames
- FixedSizeOrFixedRepeat
- FixedSizeOrLineNames
- FixedSize
This seems like a lot of types, but I have use cases for all/most of them. Most of them are related in a tree structure, so that anything that is a TrackBreadth is automatically also a TrackSize, TrackSizeOrLineName, TrackSizeOrTrackRepeat, and TrackSizeOrTrackRepeatOrLineName.
My first draft had a single gridTemplateColumns : TrackListOrAutoTrackListOrNone compatible -> Style
function, and then three functions that create TrackListOrAutoTrackListOrNone
values (aside from none
itself), but that could and probably should be swapped around to having three gridTemplateColumns
functions, which would allow us to get rid of the top level TrackListOrAutoTrackListOrNone compatible
type. However, if you did want to explicitly set this property to "none", you would either need a custom function, or perhaps to just pass one of the functions an empty list, which is not as obvious as passing none
.
Here are the three property constructor functions I have so far:
-
trackList1 : List (TrackListOrTrackRepeat c1) -> TrackListOrAutoTrackListOrNone c2
- This simply does not accept line names, because a definition with only line names is invalid, so we need to make sure that we have at least one non-name entry. If the list is empty, we would go with "none".
TrackListOrTrackRepeat c
includes things likepx 100
andpct 60
andrepeat 3 [px 30, fr 1]
.
- This simply does not accept line names, because a definition with only line names is invalid, so we need to make sure that we have at least one non-name entry. If the list is empty, we would go with "none".
-
trackList2 : LineNames c1 -> TrackSizeOrTrackRepeat c2 -> List (TrackSizeOrTrackRepeatOrLineNames c3) -> TrackListOrAutoTrackListOrNone c4
- This version allows you to put line names in, while still requiring at least one non-name entry.
- This assumes that any adjacent LineName entries in the list get mushed together, because they are not allowed by the spec to be next to each other. Each line name group gets written as "[linename1 linename2]", so it should be as easy as scanning for and removing any instances of "] [".
- The LineNames type can be empty, so that beginning
LineNames c1
is kind of optional. Maybe another version of this function should be created that leaves off that first name group. - The main alternative I had to having a list of entriesOrNames interspersed like that was to have a list of (LineNames, TrackSizeOrTrackRepeat) tuples, but I found it to be very cumbersome to use.
-
autoTrackList : List (FixedSizeOrFixedRepeatOrLineNames c1) -> AutoRepeat c2 -> List (FixedSizeOrFixedRepeatOrLineNames c3) -> TrackListOrAutoTrackListOrNone c4
- This is the version that allows you to use the auto-repeats, which is any time you use the
repeat
css function with theauto-fit
orauto-fill
keywords. Using the auto-repeat limits what you can do elsewhere, which is why there is the giant split between the FixedSize types and the TrackSize types. - Like the previous function, this assumes that any adjacent LineNames will be put together into a single group of square brackets.
- This is the version that allows you to use the auto-repeats, which is any time you use the
There are a few CSS functions that we should mimic, which are allowed on both sides of the TrackSize/FixedSize split, so I have multiple versions of them that resolve to different things. Here are the functions for those:
-
repeat : Int -> List (TrackSize c1) -> TrackSizeOrTrackRepeat c2
- This is basically like
trackList1
, except you cannot put a repeat inside of another repeat. - It is currently taking an Int, but maybe it should take a
Number compatible
(Css.int 3 instead of 3)?
- This is basically like
-
repeat2 : Int -> LineNames c1 -> TrackSize c2 -> List (TrackSizeOrLineNames c3) -> TrackSizeOrTrackRepeat c4
- This is like
trackList2
, aside from not being able to put a repeat inside it.
- This is like
-
fixedRepeat1 : Int -> List (FixedSize c1) -> FixedSizeOrFixedRepeat c2
- Like repeat, but over on the FixedSize side of the split.
-
fixedRepeat2 : Int -> LineNames c1 -> FixedSize c2 -> List (FixedSizeOrLineNames c3) -> TrackSizeOrFixedRepeat c4
- Again, maybe there should be a version that leaves the first
LineNames c1
off of it.
- Again, maybe there should be a version that leaves the first
-
autoRepeat1
andautoRepeat2
are various shortenings ofautoRepeat3
: -
autoRepeat3 : AutoFillOrAutoFit c1 -> LineNames c2 -> FixedSize c3 -> List (FixedSizeOrLineNames c4) -> AutoRepeat c5
-
minmax1 : InflexibleBreadth c1 -> TrackBreadth c2 -> TrackSize c3
-
minmax2 : InflexibleBreadth c1 -> FixedBreadth c2 -> FixedSize c3
-
minmax3 : FixedBreadth c1 -> TrackBreadth c2 -> FixedSize c3
-
minmax2
andminmax3
are strictly less powerful thanminmax1
, but that's what is necessary for them to work with the auto-repeats. - I have concerns here, because there is very little discoverability as to when you can use which one of these.
- Perhaps FixedSize could be made a sub-set of TrackSize, and the
minmax
function would return an object that lists itself as compatible with everything it can be based on the input? But I don't know a way to encode that in the type system, because the return type would have to be listed as FixedSize compatible, not TrackSize compatible, which would seem to prevent it from being used over in those safe areas, even if it actually would work based on what you gave it.
-
-
fitContent : LengthOrPercent c1 -> TrackSize c2
- I think that
LengthOrPercent c1
is actuallyExplicitLength units
, but I didn't look closely enough to confirm it.
- I think that
Generating line names is simple:
-
lineNames : List String -> LineNames c
-
lineName : String -> LineNames c
A few new values generators need to be created:
-
fr : Float -> Fr
- `fr = flexLengthConverter Fr "fr"
- where
flexLengthConverter
is basically the same aslengthConverter
, but not compatible with nearly as many things. It gives back aFlexLength compatible
, which is a new type.
After those, the types just need to be added to the existing value generators:
- auto -> most of the TrackSize types
-
minContent
andmaxContent
-> Most of the TrackSize types -
LengthOrPercent c
(again, I think this is actuallyExplicitLength
) -> basically all of the types - global values : add TrackListOrAutoTrackListOrNone, if it still exists at the end of this.
using my first draft API, the examples from the MDN page are written as follows:
-
gridTemplateColumns none
-
gridTemplateColumns <| tracklist1 [px 100, fr 1]
-
gridTemplateColumns <| trackList2 (lineNames ["linename"]) (px 100) []
-
gridTemplateColumns <| trackList2 (lineNames ["linename"]) (px 100) [lineNames ["linename2","linename3"]]
-
gridTemplateColumns <| minmax1 (px 100) (fr 1)
-
gridTemplateColumns <| fitContent (pct 40)
-
gridTemplateColumns <| repeat 3 [px 200]
-
gridTemplateColumns <| autoTrackList [px 200] (autoRepeat1 autoFill [ px 100]) [px 300]
-
gridTemplateColumns <| autoTrackList [minmax3 (px 100) maxContent] (autoRepeat1 autoFill (px 200)) [pct 20]
(gridTemplateColumns <| autoTrackList [lineName "linename1", px 100, lineName "linename2"] (autoRepeat3 autoFit (lineNames ["linename3","linename4"]) (px 300) []) [px 100]
-
gridTemplateColumns <| autoTrackList [lineNames ["linename1","linename2"] ,px 100] (autoRepeat3 autoFit (lineName "linename1") (px 300) []] [lineName "linename3"]
All of this is just for the grid-template-columns
property, but I do think that it encompasses most of the complexity required for the entire Grid API, particularly if the more complicated shorthand functions are left off.
Thanks for your attention. This was rather long, and I appreciate your attention in getting through it. I am willing to do the work to put together the PR once the community has settled on an API for it.
Any and all feedback is appreciated.
I don't have the Elm chops to comment on implementation, but agree that CSS Grid is important!
@Enkidatron Is there any update on getting support for grids?
I would also love to have grid support in elm-css. using property
does work but the added type safety of elm-css would be much appreciated
+1 - this would be great.
Would really like to contribute to this (as I am in desperate need for a grid system in elm/css). How can I contribute?
👋 I guess this is never going to be a thing, right?
Sadly I haven’t had time to get into it, nor do I have a clue how to contribute yet. I’m currently just using a work-around with Elm’s default style
function.
No worries. I was asking because I wonder if PRs are still welcome. I see a lot of PRs which were open a long time ago and never merged. Implementing this looks like a lot of work to eventually not being merged :(
A fair concern, I’m still very inexperienced with GitHub, so I wouldn’t know if suggesting a fork is a realistic option but I guess that if this project is somewhat abandoned we could do that? Again, I do not know what “normal” procedure is in such cases!
I've found this https://package.elm-lang.org/packages/clojj/elm-css-grid/latest/SimpleGrid, which apparently implements the grid layout with elm-css 🎉
Sadly that is not the kind of implementation I'm looking for, thanks nonetheless, much appreciate it! :)