| 504 |
504 |
|
)
|
| 505 |
505 |
|
}
|
| 506 |
506 |
|
|
|
507 |
+ |
/// The whole spacing layer with the canonical density selection, as CSS.
|
|
508 |
+ |
///
|
|
509 |
+ |
/// **Density is a capability, not a device and not a width.** A narrow window
|
|
510 |
+ |
/// on a desktop still has a pointer in it and a tablet at full width still has
|
|
511 |
+ |
/// a finger, so the touch preset hangs off `(hover: none), (pointer: coarse)`
|
|
512 |
+ |
/// rather than off a breakpoint or a user-agent string. That is the question
|
|
513 |
+ |
/// the platform actually answers, and it is the one [`Density`] is asking.
|
|
514 |
+ |
///
|
|
515 |
+ |
/// `explicit_touch` names a selector an app sets when the *user* has chosen.
|
|
516 |
+ |
/// It is emitted last and therefore wins at equal specificity, because
|
|
517 |
+ |
/// detection is a default rather than a verdict: a touchscreen laptop and
|
|
518 |
+ |
/// someone who simply wants roomier targets are both real, and neither is
|
|
519 |
+ |
/// visible to a media query.
|
|
520 |
+ |
///
|
|
521 |
+ |
/// Settles a policy three consumers previously answered three ways. GoingsOn
|
|
522 |
+ |
/// sniffed the user agent behind a mode class, Balanced Breakfast used
|
|
523 |
+ |
/// `(hover: none)` alone, and audiofiles had no switch at all; the first of
|
|
524 |
+ |
/// those asked what device this is as a proxy for a capability already
|
|
525 |
+ |
/// reported.
|
|
526 |
+ |
#[must_use]
|
|
527 |
+ |
pub fn density_css(explicit_touch: Option<&str>) -> String {
|
|
528 |
+ |
let mut css = geometry_css_vars(Density::Pointer);
|
|
529 |
+ |
css.push_str("\n@media (hover: none), (pointer: coarse) {\n");
|
|
530 |
+ |
for line in gap_css_overrides(":root", Density::Touch).lines() {
|
|
531 |
+ |
css.push_str(" ");
|
|
532 |
+ |
css.push_str(line);
|
|
533 |
+ |
css.push('\n');
|
|
534 |
+ |
}
|
|
535 |
+ |
css.push_str("}\n");
|
|
536 |
+ |
if let Some(selector) = explicit_touch {
|
|
537 |
+ |
css.push_str("\n/* An explicit user choice, last so it wins over detection. */\n");
|
|
538 |
+ |
css.push_str(&gap_css_overrides(selector, Density::Touch));
|
|
539 |
+ |
}
|
|
540 |
+ |
css
|
|
541 |
+ |
}
|
|
542 |
+ |
|
| 507 |
543 |
|
/// Emit a density preset as a scoped override block.
|
| 508 |
544 |
|
///
|
| 509 |
545 |
|
/// Only the relational layer is emitted: the scale and the base do not change
|
| 524 |
560 |
|
mod tests {
|
| 525 |
561 |
|
use super::*;
|
| 526 |
562 |
|
|
|
563 |
+ |
#[test]
|
|
564 |
+ |
fn density_is_selected_by_capability_not_by_width_or_agent() {
|
|
565 |
+ |
let css = density_css(None);
|
|
566 |
+ |
assert!(css.contains("@media (hover: none), (pointer: coarse)"));
|
|
567 |
+ |
// The three things density must never be selected by.
|
|
568 |
+ |
assert!(!css.contains("max-width"), "a breakpoint crept in");
|
|
569 |
+ |
assert!(!css.contains("min-width"), "a breakpoint crept in");
|
|
570 |
+ |
assert!(!css.contains("ui-mode"), "a device mode crept in");
|
|
571 |
+ |
}
|
|
572 |
+ |
|
|
573 |
+ |
#[test]
|
|
574 |
+ |
fn an_explicit_choice_is_emitted_after_the_detection() {
|
|
575 |
+ |
let css = density_css(Some(".ui-mode-mobile"));
|
|
576 |
+ |
let media = css.find("@media").expect("media query");
|
|
577 |
+ |
let explicit = css.find(".ui-mode-mobile").expect("explicit selector");
|
|
578 |
+ |
// Equal specificity, so order is the whole mechanism: the user's
|
|
579 |
+ |
// choice has to come last or detection quietly overrides it.
|
|
580 |
+ |
assert!(explicit > media, "the explicit selector must come last");
|
|
581 |
+ |
}
|
|
582 |
+ |
|
|
583 |
+ |
#[test]
|
|
584 |
+ |
fn without_an_explicit_selector_there_are_exactly_two_presets() {
|
|
585 |
+ |
assert_eq!(density_css(None).matches("--gap-peer").count(), 2);
|
|
586 |
+ |
}
|
|
587 |
+ |
|
| 527 |
588 |
|
#[test]
|
| 528 |
589 |
|
fn the_hig_relationships_land_on_the_hig_values() {
|
| 529 |
590 |
|
// Mac OS 8 HIG, Control Layout Guidelines. The ratios are ours, but at
|