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

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

Open
MatsPalmgren opened this Issue Apr 13, 2018 · 3 comments

Comments

Projects
None yet
5 participants
@MatsPalmgren
Copy link

MatsPalmgren commented Apr 13, 2018

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.

@fantasai

This comment has been minimized.

Copy link
Contributor

fantasai commented May 17, 2018

Thanks for filing all these detailed issues, @MatsPalmgren!
Here's some quick responses from me and @tabatkins; we'll look at these in more detail in the coming weeks. I want to also get @jensimmons and @rachelandrew digging into them before we resolve.

ISSUE 1: Agreed, clearly a bugfix change.

ISSUE 2: Agreed, this is consistent with item f in https://www.w3.org/TR/2018/WD-css-grid-2-20180427/#subgrid-items

ISSUE 3: I think in this case explicit names in the outer grid that are outside the subgrid’s bounds should not match. The only parent line names in scope are the ones where the parent and the subgrid share the line name. This was the intention of item c, “the subgridded lines automatically receive the line names specified on the parent grid” and is consistent with the idea of scoping the subgrid's line references per item b, “The grid-placement properties of the subgrid’s grid items are scoped to the lines covered by the subgrid.”

(It's not clear from your examples whether this is what you meant, or you meant the opposite. As it happens, either choice gives the results you specified. ^_^ Here's a more explicit example:

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

Here, the parent grid's line 1, 3, and 5 are named "a". The subgrid spans from lines 1-4.

If the parent grid's lines outside the subgrid's bounds are visible, then the item will anchor its grid-column-end to line 5, which is clamped to 4. Then it will seek startwards and find line 3 as the next "a" line, for a final position of 3 / 4.

If they're not visible, it'll anchor its grid-column-end to line 3 (the last visible "a" line), then span to line 1, for a final position of 1 / 3. This is the correct/intended behavior.)

ISSUE 4: This is an interesting case, I hadn't really thought about it. Named areas don't really "exist" in Grid; they're just a shorthand syntax for adding *-start and *-end line names to the grid. (Note that you can use grid-area: a solely by manually adding a-start/a-end lines to the grid, avoiding grid-template-areas entirely.) So I would have expected that we'd just inherit the -start/-end line names that happen to cross into the subgrid, without doing anything special.

If we resolve by simply ignoring the line names outside the subgrid’s spanning area, then the -start/-end lines outside the subgrid area would just get dropped. The item would then position against the subgrid's fake-implicit grid lines, and subsequently get clamped to the actual bounds of the subgrid, yielding the same behavior you propose.

ISSUE 5: If we take the position that areas generate line names which are treated just like any other line names (the current Grid 1 specified behavior), then there's no order dependence (other than the order-dependence inherent in resolving named lines). Then there can be some confusing results if names conflict, but then, that's true for inheriting line names in general.

ISSUE 6: Again, if areas just define lines to match against, and have no further independent existence, then there's no ambiguity here - the a-start and a-end lines are defined by the parent grid, and inherit down to the child (in a way that happens to give a slightly confusing result here, spanning from 1 / 2 in the outer grid).

Note that if you'd said grid-column: a, it would match the a-start and a-end lines defined by the parent grid, and then because they're in the wrong order, it would just swap them, and properly position the item in the parent's "a" area. The confusing result here is from the fact that the item is only anchoring to one of the a-* lines, and is just span 1 for the other position.

ISSUE 7: Agreed; this is already edited into the spec, I believe: https://drafts.csswg.org/css-grid-2/#subgrid-per-axis “Excess <line-names> are ignored.”

fantasai added a commit that referenced this issue Jun 11, 2018

[css-grid-2] Clarify that implicit named lines (implied via grid-temp…
…late-areas) are passed through, but the infinite names of implicit grid lines (used to resolve placement against named lines that don't exist) are not. #2564

fantasai added a commit that referenced this issue Jun 11, 2018

@fantasai

This comment has been minimized.

Copy link
Contributor

fantasai commented Jun 12, 2018

@MatsPalmgren OK, I edited in some clarifications to reflect the discussion above. Let me know if anything is still unclear or if you disagree with the spec on any point?

Also flagging for additional review by the CSSWG.

@css-meeting-bot

This comment has been minimized.

Copy link
Member

css-meeting-bot commented Aug 15, 2018

The Working Group just discussed resolving line names in subgrids.

The full IRC log of that discussion <dael> Topic: resolving line names in subgrids
<dael> github: https://github.com//issues/2564
<dael> fantasai: Just a request for
<dael> TabAtkins: For review from WG
<dael> fantasai: The way we resolved these questions there was clarifications. Linenames are passed down and the rest of the behavior falls out from that. We could add additional logic to change behavior a little but we feltthis was straightforward.
<dael> fantasai: Asking f or a look, don't need to discuss unless anyone has a comment or question
<dael> astearns: Are the possible additions documented?
<dael> fantasai: I think in the comments
<dael> astearns: Sounds good
<dael> astearns: Take this as a call to look at these changes.
<dael> rachelandrew: Read through it and it seemed to make sense to me and be explainable
<dael> astearns: Thanks
<dael> astearns: Anything else?

@astearns astearns removed the Agenda+ label Aug 15, 2018

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment