Skip to content

implement position: absolute - #34

Open
Endika wants to merge 1 commit into
chearon:masterfrom
Endika:position-absolute
Open

implement position: absolute#34
Endika wants to merge 1 commit into
chearon:masterfrom
Endika:position-absolute

Conversation

@Endika

@Endika Endika commented Jul 29, 2026

Copy link
Copy Markdown

Closes #6.

The support matrix listed position: absolute as planned, and #6 notes that the blocker was containing
blocks being assigned during layout rather than before it. That blocker is gone since "assign the containing
blocks in the prelayout step", so this fills in the rest.

What it does

Absolutely positioned boxes are laid out against the padding box of their nearest positioned ancestor, or
the initial containing block when there is none, and they leave normal flow: they do not contribute to their
parent's block size, do not appear on a line, do not add to a line's width and do not interact with floats.
They establish a block formatting context of their own, so floats inside them are contained, and a
positioned inline is blockified the way a float is.

Sizing and placement follow CSS 2.1 §10.3.7 and §10.6.4: a size from a pair of insets, shrink-to-fit for an
auto inline size, auto-margin centering, a single auto margin taking the remainder, and the over-constrained
rules — the line-right inset is dropped in ltr, the line-left one in rtl, and the block-end one always.
Percentages in insets and sizes resolve against the containing block's padding box. When both insets on an
axis are auto the box stays at its static position, taken as the start of the line box it would have
appeared on.

The part worth reviewing closely

Layout here is logical while top/right/bottom/left are physical, so the insets are mapped through the
containing block's writing mode exactly the way margins, padding and borders already are — the logical maps
gain four inset entries per mode, with insetBlockStart being top in horizontal-tb, left in
vertical-lr and right in vertical-rl. The static position is resolved during postlayout, when every
ancestor area is already physical, so a chain that crosses writing modes costs nothing; resolving it during
layout by summing logical offsets is wrong as soon as an orthogonal block intervenes.

One thing that is easy to miss: the mark iterator only consumed a box mark when it was a float or
inline-level, so making a positioned box out-of-flow without touching it turns line building into an
infinite loop.

Deliberately out of scope

position: fixed (still listed as planned); a relatively positioned inline as a containing block, since only
block containers establish one today; and the static position accounting for content earlier on the line —
the start of the line box is used instead, which is enough for the placements the tests cover.

Tests

test/position-absolute.spec.js, wired into test/ci.js: 27 cases over containing-block resolution,
out-of-flow behaviour, sizing from insets, shrink-to-fit, auto margins, the over-constrained rules,
percentages against the padding box, static positions including rtl and vertical writing modes, replaced
boxes, formatting-context containment, blockification and paint order. npm test is 470 passing and
npm run tsc is clean.

Provenance

This implementation was written as the reference solution for a coding-challenge submission that was
rejected for being too easy — three of four independent blind solvers reproduced it. That is a reason for
confidence in the design rather than against it: four independent implementations converged on the same
architecture. The code is unencumbered.

Two pre-existing bugs surfaced while those implementations were being graded. Both reproduce on unmodified
master and I have kept them out of this PR; I will file them separately:

  1. "Unknown" line items use treeIndex === 0 as a sentinel, which resolves to the root box, so
    positionPhysicalLineItems moves the whole root. Repro: <span><div style="float:left"></div></span>.
  2. An out-of-flow box as the last child of a <span> with a line-right gap throws Assertion failed in
    LineCandidates.inlinePost. Repro: <span style="padding:4px">ab<div style="float:left"></div></span>.

@chearon

chearon commented Jul 29, 2026

Copy link
Copy Markdown
Owner

This looks promising! Before I dive into this, can you disclose if you used AI or not?

@Endika

Endika commented Jul 29, 2026

Copy link
Copy Markdown
Author

This looks promising! Before I dive into this, can you disclose if you used AI or not?

I leverage AI models for automated unit test generation, technical writing, and code documentation.

@chearon

chearon commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Cool. I got absolute positioning 90% done a year or two ago but I think I got stuck on where to save the static positions (more on that below). It'll take me a while to re-familiarize myself and finish what I'm working on, but it looks good. The other fixes you mentioned sound like a quicker review.

One thing that stands out is that my end goal for the Layout class is to only have {tree, fragments}, where fragments are glyphs/run fragments and inline fragments. I use dropflow to hold hundreds of thousands of layouts, so Layout needs to retain only what's necessary. Is it not possible to set the static position on the box areas temporarily instead of needing that map?

@Endika

Endika commented Jul 29, 2026

Copy link
Copy Markdown
Author

Apologies if you already had work in progress. I didn't see anything in the repository. Having spent some time testing it, I just thought it would be helpful to share what I've found so far.

@Endika

Endika commented Jul 29, 2026

Copy link
Copy Markdown
Author

On the map: it's only alive between line building and postlayout, and reflow() spans both, so it
can be a local there rather than a field on Layout. The box areas don't have a free slot for it layoutAbsoluteBox overwrites blockStart/lineLeft after the flow pass records them, but the
record can drop to two numbers, since the flags are just "both insets on this axis are auto" and the
area is always the nearest block container ancestor. I can push that here if you want it, or leave
it for your branch.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

position: absolute

2 participants