/* The components/Lister listing: a filter line over one flat list of rows.

   The HOST gives the component a height — in an unbounded parent the scroller below
   never bounds itself, and the list grows instead of scrolling, which is the very bug
   this component was built to fix. */
.mf-lister {
	/* The rhythm both halves stand on, so ten categories measure exactly ten rows —
	   content alone settles at 21px, and this is the common line above it. */
	--mf-lister-row: 1.5rem;

	display: grid;
	grid-template-rows: auto 1fr; /* the filter head; the body the rail shares with the rows */
	min-height: 0; /* or the scroller below never bounds itself */
	font-family: var(--mf-font-mono);
}

.mf-lister-head {
	padding-bottom: var(--mf-space-2);
}

.mf-lister-body {
	display: grid;
	min-height: 0;

	/* The rail, then the rows — and no gap to indent past when there is no rail. */
	&:has(.mf-lister-rail) {
		grid-template-columns: auto 1fr;
		gap: var(--mf-space-3);
	}
}

/* The region the arrows drive — the rows or the rail, never the filter, which the caret
   never leaves. The caret cannot say where Tab landed, so this mark is the only thing
   that does. A rule over the column reads as the column; the other side dims to match. */
.mf-lister-list,
.mf-lister-rail {
	border-block-start: 1px solid var(--mf-lister-region, transparent);
	padding-block-start: 2px; /* air between the rule and the first row */
	transition: opacity .18s, border-color .18s;
}

.mf-lister-list.is-current,
.mf-lister-rail.is-current {
	--mf-lister-region: var(--mf-color-accent);
}

.mf-lister-body:has(.is-current) :is(.mf-lister-list, .mf-lister-rail):not(.is-current) {
	opacity: 0.85;
}

/* The frame, like `.mf-lister-list`: it holds the rule and never scrolls, and the
   categories scroll inside it. Full height rather than `align-self: start` with a
   `max-height`, which leaves the row indefinite — the scroller then sizes to its own
   content and a rail taller than the component never scrolls at all. */
.mf-lister-rail {
	display: grid;
	grid-template-rows: minmax(0, 1fr);
	min-height: 0;
	min-width: 8rem;
}

.mf-lister-facets {
	display: grid;
	align-content: start;
	min-height: 0;
	overflow: auto;
	padding-inline-start: var(--mf-space-1);
	padding-inline-end: calc(var(--mf-scroll-width) + 2px); /* the bar, and air beside it */
}

/* A `Checkbox`, boxed like `.mf-lister-row`: the same height and padding. */
.mf-lister-facet {
	min-height: var(--mf-lister-row);
	padding: 0 var(--mf-space-2);
	font-size: var(--mf-font-size-small);
	opacity: 0.8;

	&:has(input:checked) {
		color: var(--mf-color-accent);
		opacity: 1;
	}

	/* The row the rail's arrows are on — the caret stays in the filter, so nothing else
	   says where Up/Down will land. `ListerRail` marks it `.is-preview` too, which is
	   what dims the tick. */
	&.is-marked {
		background: var(--mf-color-accent-faint);
		opacity: 1;
	}
}

.mf-lister-count {
	margin-inline-start: auto;
	color: var(--mf-color-text-muted);
	font-size: var(--mf-font-size-tiny);
	font-variant-numeric: tabular-nums;
}

.mf-lister-list {
	display: grid;
	grid-template-rows: minmax(0, 1fr);
	min-height: 0;
}

/* The scroller owns the tracks, not the row: a grid per row measures its own longest
   cell, so one wider size or class would step every column after it out of line.
   `ListerRows` derives `--mf-lister-columns-default` from the columns; a host that wants
   other tracks sets `--mf-lister-columns` on any ancestor, with no specificity fight. */
.mf-lister-rows {
	display: grid;
	grid-template-columns: var(--mf-lister-columns, var(--mf-lister-columns-default, auto 1fr));
	align-content: start; /* rows keep their own height; the leftover space stays below */
	column-gap: var(--mf-space-3); /* the tracks' gap, which every row's subgrid takes */
	min-height: 0;
	overflow: auto;
	scrollbar-gutter: stable; /* or the last column sits under the thumb */
}

.mf-lister-row {
	display: grid;
	grid-column: 1 / -1;
	grid-template-columns: subgrid;
	align-items: center; /* a cell taller than the line box, a chip say, still centres */
	min-height: var(--mf-lister-row);
	padding: 0 var(--mf-space-2);
	color: var(--mf-color-text);
	font-size: var(--mf-font-size-small);
	cursor: pointer;
	opacity: 0.8;

	&[aria-selected="true"] {
		color: var(--mf-color-accent);
		opacity: 1;
	}

	/* A wash rather than inverse video: the marks are tinted text, and a row that
	   repainted them would erase the one thing they say. A wash and nothing else — the
	   frame around what the hand is on is the pointer's brace, and a row drawing its own
	   put two rectangles on the same line, an inset apart. */
	&.is-active {
		background: var(--mf-color-accent-faint);
		opacity: 1;

		.mf-tag {
			opacity: 0.95;
			filter: contrast(1);
		}
	}

	&.is-disabled {
		color: var(--mf-color-text-muted);
		cursor: default;
	}
}

.mf-lister-cell {
	overflow: hidden;
	white-space: nowrap;

	&[data-mf-align="end"] {
		text-align: end;
	}

	/* The `minmax(0, 1fr)` track does the bounding: `overflow: hidden` above already
	   zeroes the automatic minimum, so a long title is clipped, not widening. */
	&[data-mf-role="text"] {
		text-overflow: ellipsis;
	}

	/* A mode string is a fixed shape and never wraps or reflows. `pre`, because a host
	   that pads a value to a column width says so in spaces, which `nowrap` would eat. */
	&[data-mf-role="mono"] {
		color: var(--mf-color-text-muted);
		font-variant-numeric: tabular-nums;
		white-space: pre;
	}

	/* A chip or a class code: only as wide as what it holds, and hung on the right — a mark
	   is the row's last column, so its track ends where the listing does. */
	&[data-mf-role="mark"] {
		justify-self: end;
		width: max-content;
	}

	/* A mark in a listing is text, not a chip: boxed, the marks crowd a line that is already
	   dense, and the tone alone carries what the border was saying. */
	.mf-tag {
		border: 0;
		padding: 0;
		opacity: 0.75;
		filter: contrast(0.5);
		transition: opacity .18s, filter .18s;
	}
}

/* The row's one target, a link or a bare press, drawn the same either way — and the
   brightest thing on the line, because it is what the row is about. Centred, not
   baselined: a glyph's baseline is its bottom edge, which would lift it and grow the
   line past the rail's centred tick. */
.mf-lister-lead {
	display: inline-flex;
	align-items: center;
	gap: var(--mf-space-2);
	max-width: 100%;
	padding: 0;
	overflow: hidden;
	color: var(--mf-color-text-title);
	line-height: inherit;
	text-align: inherit;
	text-overflow: ellipsis;
	white-space: nowrap;

	&:focus-visible {
		outline: none;
	}
}

/* Hosts tint a class glyph through this; unset, it reads as the row. */
.mf-lister-icon {
	flex: 0 0 auto;
	color: var(--mf-lister-tint, var(--mf-color-text-muted));
}

/* The facet's glyph is a `Checkbox` slot, already laid out by `checkbox.css`. */
.mf-lister-facet .mf-checkbox-icon {
	color: var(--mf-lister-tint, var(--mf-color-text-muted));
}

/* Not a row of cells: one line across the whole track set. */
.mf-lister-empty,
.mf-lister-more {
	grid-column: 1 / -1;
	padding: var(--mf-space-2);
	color: var(--mf-color-text-muted);
	font-size: var(--mf-font-size-small);
}
