Skip to content

[css-grid-2] resolving line names in subgrids #2564

Open
@MatsPalmgren

Description

@MatsPalmgren

I figured it might be useful to provide some details on how the nested line name resolution works for subgrids in the prototype I implemented. Hopefully it can illuminate the issues and provide a starting point for a discussion on what the spec should say on the matter. (CAVEAT: bear in mind that for this prototype I intentionally implemented what required the least amount of work while still giving somewhat reasonable results. "Least amount of work" might be different for other UA vendors. I also haven't thought very deep about "does this make sense for authors" :-))

There are two ways to add local line names to a subgrid:

  1. using the subgrid <line-name-list>? syntax in grid-template-rows/columns,
    where <line-name-list> expands to:
    <line-name-list> = [ <line-names> | repeat([ <positive-integer> | auto-fill ], <line-names>) ]+
    auto-fill works similarly to how it works for tracks, except here we fill the extent of the grid rather than a target layout size.
  2. grid-template-areas

ISSUE 1: the first thing that needs to be addressed is how implicit lines in ancestor grids match line names. Grid 1 has this rule: "all implicit grid lines are assumed to have that name..." which doesn't work very well when looking up names from a descendant subgrid. For example:

<div style="display:grid">
  <div style="display:grid; grid:auto/subgrid [] [] [] [b]; grid-column:span 10;">
    <x style="grid-column: 1 / b">x</x>
  </div>
</div>

The outer grid has 10 implicit tracks with implicit grid lines between them. So using that rule, line 2 in the outer grid would match b, which doesn't seem desirable. So the subgrid spec needs to explicitly override that rule so that the local b line matches above.

ISSUE 2: since subgrids don't have implicit tracks, and thus there are no implicit grid lines that would match line names that otherwise have no match, we need to define how those names are resolved.

Using the example above, how should a subgrid item with grid-column: foo / span 4 be resolved? The way I handle it internally is to pretend that there are in fact implicit grid lines, and then simply clamp the result afterwords. This was the simplest to implement because it allowed me to re-use the existing line name resolution code as is for the most part. So first we resolve foo to 12, then clamp that to 11, then span 4 from there to 15, then clamp that to 11, then enforce a minimum span 1, to get the final result 10 / 11 (placing it in the last subgrid track). For grid-column: span bar 2 / foo 3 we resolve to foo 3 to 14, then clamp that to 11, then search start-wards for two matching bar lines from there, which results in -1 (the second "implicit line" on the start side) which is then clamped to 1, so this item spans the entire subgrid (1 / 11).

ISSUE 3: explicit line names in outer grids that are outside the bounds of the subgrid needs to be defined how/if they match.

I solved this case similarly to the above, so for example:

<div style="display:grid; grid: auto / repeat(20, [a] 50px) [a]">
  <div style="display:grid; grid:auto/subgrid; grid-column: span 10">
    <x style="grid-column: span a / a 18">x</x>
  </div>
</div>

we first resolve a 18 to 18 which is then clamped to 11, then we resolve span a start-wards from there, matching line 10. This however:

<div style="display:grid; grid: auto / repeat(10, 50px) repeat(10, [a] 50px) [a]">
  <div style="display:grid; grid:auto/subgrid; grid-column: span 10">
    <x style="grid-column: span a / a 8">x</x>
  </div>
</div>

resolves a 8 to 18, which is clamped to 11, but since there are no matches for a start-wards from 11 it matches the first "implicit" line on the start side, so the final result is 1 / 11.

ISSUE 4: line name matching for grid-template-areas that are (partly) outside of the bounds of the subgrid needs to be defined

The way I solved this is to view the areas themselves as sliced by the subgrid and then generate implicit area names for that slice, for example:

<div style='display:grid; grid-template-areas: "a a a a a a a a a a"'>
  <div style="display:grid; grid:auto/subgrid; grid-column: 6 / span 5">
    <x style="grid-column: a">x</x>
  </div>
</div>

Resolves to 1 / 6, because the subgrid sees a a-start line at its line 1 and a-end at line 11. That is, the areas in ancestor grids that have an edge outside the subgrid generates its corresponding implicit area name at the subgrid edge instead. This matters because for example:

<div style='display:grid; grid-template-areas: "a a a a a a a a a a"'>
  <div style="display:grid; grid:auto/subgrid [a-start] [a-start]; grid-column: 6 / span 5">
    <x style="grid-column: a-start 2">x</x>
  </div>
</div>

Resolves to 2 / 3, because the a-start of the parent grid coincides with the explicit a-start at line 1 in the subgrid.

Note that implicit area names are still generated for all grids though, so for example:

<div style="display:grid; grid-template-areas: '. . . . . . a a a a'">
  <div style="display:grid; grid:auto/subgrid; grid-column:2/-1; grid-template-areas: '. . a'">
    <div style="display:grid; grid:auto/subgrid; grid-column:2/-1; grid-template-areas: '. . a'">
      <div style="display:grid; grid:auto/subgrid; grid-column:2/-1; grid-template-areas: '. . a'">
        <x style="grid-column: a-start 4">x</x>
      </div>
    </div>
  </div>
</div>

have a-start lines at line 1 (from the outermost subgrid), line 2, line 3 (from the innermost subgrid) and line 4 (from the root grid) so a-start 4 is resolved as 4 in this case.

ISSUE 5: there is an order-dependent rule for resolving plain <custom-ident> which is ambiguous when the matching is nested

I solved it by recursing on all nested areas first, and only if none of them have a matching area name we treat it as <custom-ident> 1. So here for example,

<div style='display:grid; grid-template-areas: ". . . . a"'>
  <div style="display:grid; grid:auto/subgrid [] [a-start]; grid-column: span 10">
    <x style="grid-column: a">x</x>
  </div>
</div>

a matches the explicit a-start line because there exists an area named a, so the result is 2 / 6.

ISSUE 6: the spec needs to define where implicit area line names occurs when the relevant grid axes have opposite progression directions

For example:

<div style='display:grid; grid-template-areas: ". a a . ."'>
  <div style="display:grid; grid:auto/subgrid; grid-column: span 10; direction: rtl">
    <x style="grid-column: a-start">x</x>
  </div>

I implemented this by matching all names as viewed from the subgrid's perspective, so the implicit a-start associated with the a area occurs at the right side when resolving names within the subgrid (and vice versa for -end names). So the item above is placed at 8 / 9 (which is 3 / 4 in the outer grid). I think this makes sense if you view the areas as areas and the associated implicit line names occurs as viewed from the item we're resolving names for.

ISSUE 7: local names within the subgrid itself may be outside the subgrid bounds - the spec needs to define how these are handled. For example:

<div style='display:grid'>
  <div style="display:grid; grid:auto/subgrid [] [a] [] [a] [a] [a]; grid-column: span 2">
    <x style="grid-column: a -1">x</x>
  </div>
</div>

resolves to 2 / 3, because searching from the end starts at the end of the grid (line 3), so the trailing [a] s are disregarded (just like names outside of the subgrid bounds in ancestors are). Ditto for:

<div style="display:grid">
  <div style="display:grid; grid:auto/subgrid; grid-template-areas: '. a a a a'; grid-column: span 2">
    <x style="grid-column: a-end -1">x</x>
  </div>
</div>

This also resolves to 2 / 3.

Other than that it follows how line name resolution in general works in Grid 1.

While the above may sound complex and thus hard to implement, it actually was fairly straight-forward. Pretty much all existing line name resolution code can be used intact. I only needed to splice in a recursion step in a couple of places, and change existing clamping code to clamp to the subgrid bounds instead of a min/max constant.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions