| 270 |
270 |
|
// rules are as generated as the ones here.
|
| 271 |
271 |
|
pub use makeover_geometry::{CSS_LAYER, in_css_layer};
|
| 272 |
272 |
|
use makeover_layout::{
|
| 273 |
|
- |
Bevel, CellPart, Depth, Fill, Intent, RowPart, Selector, State, Token, Tone,
|
|
273 |
+ |
Bevel, CellPart, Depth, Fill, Intent, RowPart, Selector, Sort, State, Token, Tone,
|
| 274 |
274 |
|
};
|
| 275 |
275 |
|
use makeover_touch::Affordance;
|
| 276 |
276 |
|
use std::fmt::Write as _;
|
| 311 |
311 |
|
}
|
| 312 |
312 |
|
}
|
| 313 |
313 |
|
|
|
314 |
+ |
/// A string as CSS escapes, for a `content` value.
|
|
315 |
+ |
///
|
|
316 |
+ |
/// `\u{25B2}` becomes `\25B2`. Emitted escaped rather than literally so the
|
|
317 |
+ |
/// stylesheet is ASCII whatever the description spells: a `content` string is
|
|
318 |
+ |
/// read by whatever encoding the consumer serves the file as, and a caret that
|
|
319 |
+ |
/// depends on that is a caret that works on one machine.
|
|
320 |
+ |
///
|
|
321 |
+ |
/// Terminated by the closing quote at every site here. A CSS hex escape takes
|
|
322 |
+ |
/// up to six digits and ends at the first character that cannot be one, so an
|
|
323 |
+ |
/// escape followed by more text would need a space that these do not.
|
|
324 |
+ |
fn css_escape(text: &str) -> String {
|
|
325 |
+ |
text.chars().fold(String::new(), |mut out, c| {
|
|
326 |
+ |
let _ = write!(out, "\\{:X}", c as u32);
|
|
327 |
+ |
out
|
|
328 |
+ |
})
|
|
329 |
+ |
}
|
|
330 |
+ |
|
| 314 |
331 |
|
/// The CSS custom property holding a bevel's composition.
|
| 315 |
332 |
|
#[must_use]
|
| 316 |
333 |
|
pub fn bevel_var(bevel: Bevel) -> &'static str {
|
| 1287 |
1304 |
|
// heading with no caret at all. A consumer should not have to know that, and
|
| 1288 |
1305 |
|
// with the space emitted here there is nothing left for one to add.
|
| 1289 |
1306 |
|
//
|
| 1290 |
|
- |
// The unsorted arm reserves the same box with the glyph hidden, so pressing
|
| 1291 |
|
- |
// a heading does not reflow the row it sits in. Hidden rather than sized:
|
| 1292 |
|
- |
// the reservation is the caret's own width, so it names no magnitude and
|
| 1293 |
|
- |
// stays out of `makeover-geometry`'s territory.
|
|
1307 |
+ |
// Three states, three tones, on the convention in wiki
|
|
1308 |
+ |
// `three-tone-convention`. A column in force is `content`; a column offering
|
|
1309 |
+ |
// to reorder and not doing it now is `content-secondary`, because it still
|
|
1310 |
+ |
// answers a press; a column that is not sortable emits no caret at all and
|
|
1311 |
+ |
// takes nothing. The idle arm used to hide its glyph and reserve the box,
|
|
1312 |
+ |
// which cost a reflow-free press and said nothing. It draws now, and the
|
|
1313 |
+ |
// reservation stops being a thing to get right.
|
|
1314 |
+ |
//
|
|
1315 |
+ |
// The glyph is `Sort::glyph`, escaped rather than written: a CSS `content`
|
|
1316 |
+ |
// string cannot carry the character literally through this file's own
|
|
1317 |
+ |
// escaping, and spelling it here as well would put the third copy back that
|
|
1318 |
+ |
// `makeover-layout` 0.27.5 exists to remove.
|
| 1294 |
1319 |
|
let _ = writeln!(
|
| 1295 |
1320 |
|
css,
|
| 1296 |
1321 |
|
".{heading}[data-sortable]::after \
|
| 1297 |
|
- |
{{\n content: \" \\2191\";\n visibility: hidden;\n}}"
|
|
1322 |
+ |
{{\n content: \" {}\";\n color: var(--content-secondary);\n}}",
|
|
1323 |
+ |
css_escape(Sort::Ascending.glyph())
|
| 1298 |
1324 |
|
);
|
| 1299 |
|
- |
for (direction, caret) in [("ascending", "\\2191"), ("descending", "\\2193")] {
|
|
1325 |
+ |
for direction in [Sort::Ascending, Sort::Descending] {
|
| 1300 |
1326 |
|
let _ = writeln!(
|
| 1301 |
1327 |
|
css,
|
| 1302 |
|
- |
".{heading}[aria-sort=\"{direction}\"]::after \
|
| 1303 |
|
- |
{{\n content: \" {caret}\";\n visibility: visible;\n}}"
|
|
1328 |
+ |
".{heading}[aria-sort=\"{}\"]::after \
|
|
1329 |
+ |
{{\n content: \" {}\";\n color: var(--content);\n}}",
|
|
1330 |
+ |
direction.as_str(),
|
|
1331 |
+ |
css_escape(direction.glyph())
|
| 1304 |
1332 |
|
);
|
| 1305 |
1333 |
|
}
|
| 1306 |
1334 |
|
css
|
| 2109 |
2137 |
|
}
|
| 2110 |
2138 |
|
|
| 2111 |
2139 |
|
#[test]
|
| 2112 |
|
- |
fn the_caret_brings_its_own_gap_and_its_own_reserved_box() {
|
|
2140 |
+ |
fn the_caret_brings_its_own_gap_and_is_the_glyph_the_description_names() {
|
| 2113 |
2141 |
|
let css = stylesheet(&Emit::default());
|
| 2114 |
2142 |
|
|
| 2115 |
2143 |
|
// The space is inside the glyph, which is what the other two renderers
|
| 2116 |
2144 |
|
// write. Emitted bare, every consumer has to add it back, and the
|
| 2117 |
2145 |
|
// obvious way to add it -- `content` in an app stylesheet, which is
|
| 2118 |
2146 |
|
// unlayered and so outranks this sheet -- deletes the caret instead.
|
| 2119 |
|
- |
assert!(css.contains("content: \" \\2191\";"), "{css}");
|
| 2120 |
|
- |
assert!(css.contains("content: \" \\2193\";"), "{css}");
|
|
2147 |
+ |
assert!(css.contains("content: \" \\25B2\";"), "{css}");
|
|
2148 |
+ |
assert!(css.contains("content: \" \\25BC\";"), "{css}");
|
| 2121 |
2149 |
|
assert!(!css.contains("content: \"\\2"), "{css}");
|
| 2122 |
2150 |
|
|
| 2123 |
|
- |
// The unsorted arm holds the box open so pressing a heading does not
|
| 2124 |
|
- |
// move the row, and the sorted arms have to turn the glyph back on
|
| 2125 |
|
- |
// after it: same specificity, so order is what decides.
|
| 2126 |
|
- |
let reserve = css
|
|
2151 |
+ |
// The arrows this renderer used to draw alone are gone. Composition
|
|
2152 |
+ |
// rather than agreement: the glyph comes from `Sort::glyph`, so a
|
|
2153 |
+ |
// fourth spelling cannot appear here without appearing everywhere.
|
|
2154 |
+ |
assert!(!css.contains("2191") && !css.contains("2193"), "{css}");
|
|
2155 |
+ |
assert!(css.contains(&css_escape(Sort::Ascending.glyph())), "{css}");
|
|
2156 |
+ |
|
|
2157 |
+ |
// Three states, three tones. An idle sortable heading draws its caret
|
|
2158 |
+ |
// now rather than reserving a hidden box for it, so there is no
|
|
2159 |
+ |
// visibility to order and no reflow left to guard against; what
|
|
2160 |
+ |
// separates the states is the colour, and the sorted arms come after
|
|
2161 |
+ |
// the idle one because the specificity is the same.
|
|
2162 |
+ |
let idle = css
|
| 2127 |
2163 |
|
.find(".table-heading[data-sortable]::after")
|
| 2128 |
|
- |
.expect("the reserved box is emitted");
|
|
2164 |
+ |
.expect("the idle caret is emitted");
|
| 2129 |
2165 |
|
let sorted = css
|
| 2130 |
2166 |
|
.find(".table-heading[aria-sort=\"ascending\"]::after")
|
| 2131 |
2167 |
|
.expect("the ascending caret is emitted");
|
| 2132 |
|
- |
assert!(reserve < sorted, "{css}");
|
|
2168 |
+ |
assert!(idle < sorted, "{css}");
|
| 2133 |
2169 |
|
assert!(
|
| 2134 |
|
- |
css[reserve..sorted].contains("visibility: hidden;"),
|
|
2170 |
+ |
css[idle..sorted].contains("color: var(--content-secondary);"),
|
| 2135 |
2171 |
|
"{css}"
|
| 2136 |
2172 |
|
);
|
| 2137 |
|
- |
assert!(css[sorted..].contains("visibility: visible;"), "{css}");
|
|
2173 |
+ |
assert!(css[sorted..].contains("color: var(--content);"), "{css}");
|
|
2174 |
+ |
assert!(!css.contains("visibility: hidden;"), "{css}");
|
| 2138 |
2175 |
|
}
|
| 2139 |
2176 |
|
|
| 2140 |
2177 |
|
#[test]
|