Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions specs/016-visual-redesign/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,4 @@ Items from the redesign review, one pull request each, in order.
- [x] R-10 Decorative plate (WP8 above): a deterministic miniature delegation chain in the head of the explorer and breach indexes and every spec page, beside the title on wide screens, drawn in once (M4) and complete under reduced motion, with social cards for the two section roots from the same generator
- [x] R-11 Spec page composition: under the head, relations and features take the wide column and a panel beside them holds the figures (revisions, relations, features, counting up as M3) and the revision timeline (its rail drawing in as M13, the entries still so their contrast holds), sticky as on a breach page; below 60rem the panel follows the relations. Relation groups and feature topics pack into columns (`columns: 19rem`) instead of one long list; a group heading is the verb phrase alone ("Builds on", "Is built on by") and the source link follows the note. Features group by topic under a bar of how many carry each level, named in words beside it. A spec with no recorded features shows a sentence in the panel, not a zero and not an empty section. The withdrawn or expired notice moves into the main column. Baselines through the `refresh-screenshots` label
- [x] R-12 Standards map polish (asked by Jan): `layoutGraph` lays out each group of specs joined by dependencies on its own and packs the small groups and the standalone specs (one stack per body) into the columns where they sit highest, under the OAuth tree's short columns, instead of hanging them below its long second column; the drawing is about 40% shorter and the empty lower right is gone. A same-column curve in the last column bows left, inside the frame. The hover highlight no longer flashes: the island delegates pointer events on the drawing, holds the neighbourhood for 280ms while the pointer crosses a gap, redraws only when the node changes, and nodes fade with their lines over `--dur` (M19, new). The spine draw-in and halo on the map are gone (M4 keeps the plate); the spine stays drawn at full strength. Baselines through the `refresh-screenshots` label
- [x] R-13 Standards map width (asked by Jan, carried over from PR #75): the rail of toggles becomes a bar above the drawing (relations, publishing bodies, view and maturity side by side, the count and Reset under a rule), so the drawing takes the full page width and its labels render near their set size instead of at about three quarters. The card of the hovered or focused node floats beside that node, absolutely positioned with `pointer-events: none`, so it cannot steal the hover or move the drawing. Below 60rem the bar's groups wrap. Baselines through the `refresh-screenshots` label
90 changes: 60 additions & 30 deletions src/components/StandardsMap.astro
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ const filterCss = [

<div class="map-plate" id="map-plate">
<form id="map-filters" class="map-rail" method="get" action="/standards/" aria-label="Filter the map">
<fieldset>
<fieldset class="types">
<legend>Relations</legend>
{
usedTypes.map((t) => (
Expand All @@ -109,7 +109,7 @@ const filterCss = [
))
}
</fieldset>
<fieldset>
<fieldset class="view">
<legend>View</legend>
<label class="toggle">
<input type="checkbox" name="view" value="recent" />
Expand All @@ -132,12 +132,10 @@ const filterCss = [
A filter in the link needs JavaScript. The toggles still work.
</p>
</noscript>
<div class="map-card" id="map-card">
<p class="note">Hover or focus a node to read its relations here.</p>
</div>
</form>

<figure class="map" aria-labelledby="map-caption">
<div class="map-card" id="map-card" hidden></div>
<div class="plate map-frame" id="map-frame" tabindex="0" role="region" aria-label="Standards map">
<svg
id="map"
Expand Down Expand Up @@ -300,7 +298,7 @@ const filterCss = [
</figcaption>
</figure>

{/* one card per node, shown in the rail for the node under the pointer or in focus */}
{/* one card per node, shown beside the node under the pointer or in focus */}
<div class="map-cards" hidden>
{
nodes.map((n) => {
Expand Down Expand Up @@ -339,25 +337,42 @@ const filterCss = [

<style>
/*
* The plate: a rail of toggles on the left that is legend, filter and reading pane in one, and
* the drawing on the right at its full height, so the page scrolls and the frame does not.
* Below 60rem the rail folds above the drawing; below 40rem the drawing goes and the outline
* under the plate is the map.
* The plate: a bar of toggles above the drawing that is legend and filter in one, and the
* drawing below it at the full width of the page, so its labels keep their size. The card of
* the node in hover or focus floats beside that node. Below 60rem the bar's groups wrap;
* below 40rem the drawing goes and the outline under the plate is the map.
*/
.map-plate {
display: grid;
grid-template-columns: 16rem minmax(0, 1fr);
gap: var(--space-5);
align-items: start;
margin: 0 0 var(--space-7);
}
.map-rail {
position: sticky;
top: var(--space-4);
display: grid;
gap: var(--space-4);
grid-template-columns: minmax(0, 1fr) minmax(0, 1.5fr) minmax(0, 1fr);
grid-template-areas:
"types bodies view"
"types bodies maturities"
"count count count";
gap: var(--space-4) var(--space-6);
align-items: start;
font-size: var(--small);
}
.types {
grid-area: types;
}
.bodies {
grid-area: bodies;
}
.view {
grid-area: view;
}
.maturities {
grid-area: maturities;
}
.map-rail__count {
grid-area: count;
}
fieldset {
border: 0;
padding: 0;
Expand All @@ -381,7 +396,7 @@ const filterCss = [
}
.bodies {
grid-template-columns: 1fr 1fr;
column-gap: var(--space-2);
column-gap: var(--space-4);
}
.bodies legend {
grid-column: 1 / -1;
Expand Down Expand Up @@ -421,20 +436,33 @@ const filterCss = [
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: var(--space-2);
gap: var(--space-3);
max-width: none;
margin: 0;
padding-top: var(--space-3);
border-top: 1px solid var(--rule);
color: var(--ink-2);
font-size: var(--tiny);
font-variant-numeric: tabular-nums;
}
.note {
margin: 0;
}

/* the card floats beside its node; it never takes pointer events, so it cannot steal a hover */
.map-card {
border-top: 1px solid var(--rule);
padding-top: var(--space-4);
min-height: 6rem;
position: absolute;
z-index: 1;
width: 18rem;
padding: var(--space-3) var(--space-4);
background: var(--ground);
border: 1px solid var(--rule-strong);
border-radius: var(--radius);
font-size: var(--small);
pointer-events: none;
}
.map-card[hidden] {
display: none;
}
.map-card :global(.map-card__name) {
margin: 0 0 var(--space-1);
Expand All @@ -461,6 +489,7 @@ const filterCss = [
}

.map {
position: relative;
margin: 0;
min-width: 0;
}
Expand All @@ -487,22 +516,23 @@ const filterCss = [
}

@media (max-width: 60rem) {
.map-plate {
grid-template-columns: 1fr;
}
.map-rail {
position: static;
grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
align-items: start;
grid-template-areas: none;
}
.types,
.bodies,
.view,
.maturities {
grid-area: auto;
}
.map-rail__count,
.map-card {
.map-rail__count {
grid-area: auto;
grid-column: 1 / -1;
}
}
@media (max-width: 40rem) {
.map,
.map-card {
.map {
display: none;
}
.map-plate {
Expand Down
29 changes: 23 additions & 6 deletions src/components/StandardsMap.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* toggles and the outline in the DOM are the source of truth, and the toggles filter through
* stylesheet rules on their own. This island only: keeps the count, mirrors the toggles in the
* URL so a view can be shared, lets a lane label in the drawing toggle its body, shows the card
* of the node under the pointer or in focus in the rail, highlights that node's neighbourhood,
* of the node under the pointer or in focus beside it, highlights that node's neighbourhood,
* and hands the followed node or outline entry to the cross-document view transition so it
* grows into the spec page's title (plan 016, M2). Without JS the page is complete.
*/
Expand Down Expand Up @@ -88,8 +88,8 @@ onMount(() => {
});
}

// the reading pane: the node's card moves into the rail while it is hovered or focused
const hint = card?.innerHTML ?? "";
// the reading pane: the node's card floats beside the node while it is hovered or focused
const figure = card?.parentElement;
const cards = new Map(
Array.from(document.querySelectorAll<HTMLElement>(".map-cards [data-card]")).map((c) => [
c.dataset.card ?? "",
Expand All @@ -98,10 +98,27 @@ onMount(() => {
);
let shown: string | null = null;
function show(id: string | null) {
if (!card) return;
if (!card || !figure) return;
const node = id ? nodes.find((n) => n.dataset.spec === id) : undefined;
const next = id ? cards.get(id) : undefined;
card.replaceChildren(...(next ? [next.cloneNode(true)] : []));
if (!next) card.innerHTML = hint;
if (!node || !next) {
card.hidden = true;
card.replaceChildren();
return;
}
card.replaceChildren(next.cloneNode(true));
card.hidden = false;
// right of the node, or left of it where the frame ends; never below the frame
const box = node.getBoundingClientRect();
const fig = figure.getBoundingClientRect();
const gap = 8;
const right = box.right - fig.left + gap;
const left =
right + card.offsetWidth <= fig.width ? right : box.left - fig.left - gap - card.offsetWidth;
const frameBottom = (frame?.getBoundingClientRect().bottom ?? fig.bottom) - fig.top;
const top = Math.min(box.top - fig.top, frameBottom - card.offsetHeight);
card.style.left = `${Math.max(0, left)}px`;
card.style.top = `${Math.max(0, top)}px`;
}

// neighbourhood highlight
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/e2e/__screenshots__/standards.spec.ts/standards-light.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 17 additions & 6 deletions tests/e2e/standards.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,12 @@ test.describe("standards map (US1)", () => {
test.skip((page.viewportSize()?.width ?? 1280) < 640, "phones get the outline");
await expect(page.locator("#map-frame[data-enhanced]")).toBeAttached();
const map = page.locator("#map");
const [a, b] = await map.locator("a[data-spec]").evaluateAll((els) => {
// two nodes stacked in one column, so the pointer has a gap to cross between them
const pair = (els: Element[]) => {
const boxes = els.map((el) => ({
id: el.getAttribute("data-spec"),
box: el.getBoundingClientRect(),
}));
// two nodes stacked in one column, so the pointer has a gap to cross between them
for (const one of boxes)
for (const two of boxes)
if (
Expand All @@ -227,8 +227,12 @@ test.describe("standards map (US1)", () => {
)
return [one, two];
return [];
});
test.skip(!a || !b, "no two stacked nodes");
};
const [first] = await map.locator("a[data-spec]").evaluateAll(pair);
test.skip(!first, "no two stacked nodes");
// the drawing is taller than the window, so bring the pair into view before measuring it
await map.locator(`a[data-spec="${first?.id}"]`).scrollIntoViewIfNeeded();
const [a, b] = await map.locator("a[data-spec]").evaluateAll(pair);
const x = (a?.box.x ?? 0) + 40;
await page.mouse.move(x, (a?.box.y ?? 0) + 20);
await expect(map.locator("a.is-far").first()).toBeAttached();
Expand Down Expand Up @@ -302,10 +306,17 @@ test.describe("standards map (US1)", () => {
const node = map.locator(`a[data-spec="${rel?.from}"]`);
await node.scrollIntoViewIfNeeded();
await node.hover();
// the rail reads the node's relations in words (follow-up 4)
// the card beside the node reads its relations in words, and never takes the pointer
const from = nodes.find((s) => s.id === rel?.from);
await expect(page.locator("#map-card")).toContainText(from?.name ?? "");
const card = page.locator("#map-card");
await expect(card).toContainText(from?.name ?? "");
await expect(page.locator("#map-card dd").first()).toBeVisible();
expect(await card.evaluate((el) => getComputedStyle(el).pointerEvents)).toBe("none");
const [nodeBox, cardBox] = await Promise.all([node.boundingBox(), card.boundingBox()]);
const beside =
(cardBox?.x ?? 0) >= (nodeBox?.x ?? 0) + (nodeBox?.width ?? 0) ||
(cardBox?.x ?? 0) + (cardBox?.width ?? 0) <= (nodeBox?.x ?? 0);
expect(beside).toBe(true);
await expect(map.locator(`a[data-spec="${rel?.to}"]`)).toHaveClass(/is-near/);
await expect(
map.locator(`path[data-relation="${rel?.from}|${rel?.type}|${rel?.to}"]`),
Expand Down