//! A proportion, rendered as a bar. //! //! The third phase-B emitter, beside [`form`](crate::form) and //! [`list`](crate::list). It is much the smallest, and it is here rather than in //! the app because the trough it fills has been in phase A since before anything //! could describe one: `progress_rules` emitted `.progress` and //! `.progress-fill[data-tone]` for every tone while the only way to say "3 of 7" //! was to concatenate it into a heading. //! //! # What the pair buys, at the last layer //! //! `makeover_layout::Meter` carries `done` and `total` rather than a percentage, //! and the reason shows up here. A bar that is full because it landed exactly //! and a bar that is full because it ran over are the same width and are not the //! same fact, so the width is not allowed to be the only thing emitted. The //! over-run leaves as `data-over="true"`, and the accessible name keeps both //! true numbers. //! //! No CSS rule is emitted for `data-over`. What an over-run should look like is //! app taste — goingson already says it with `Tone::Danger` — and a renderer //! that picked a stripe for everyone would be decorating rather than describing. use crate::form::escape; use crate::{Emit, class}; use makeover_layout::{Intent, Meter, Tone}; use std::fmt::Write as _; /// The accessible name for a meter: the two numbers, and the noun if it has one. /// /// The description carries the noun alone, so the sentence is built here. That /// is the whole reason `Meter::label` is not the assembled string: a tooltip /// wants "3 of 7 subtasks" and a terminal at one line wants "3/7", and a /// description that shipped either one would have chosen for both. /// /// The true `done` is used, not the clamped one. This is the text that says an /// over-run happened. #[must_use] pub fn meter_text(meter: &Meter<'_>) -> String { match meter.label { Some(label) => format!("{} of {} {label}", meter.done, meter.total), None => format!("{} of {}", meter.done, meter.total), } } /// A meter as a filled trough. /// /// ``` /// use makeover_layout::{Meter, Tone}; /// use makeover_webview::{Emit, meter::meter_html}; /// /// let meter = Meter::new(3, 7).tone(Tone::Success).label("subtasks"); /// let html = meter_html(&meter, &Emit::default()); /// /// assert!(html.contains(r#"aria-label="3 of 7 subtasks""#)); /// assert!(html.contains(r#"data-tone="success""#)); /// assert!(html.contains("width: 42%")); /// ``` /// /// `aria-valuenow` is clamped to `aria-valuemax`, because a value outside the /// range is invalid ARIA and a screen reader is entitled to ignore the whole /// element. The unclamped truth is in the accessible name, which is read either /// way. #[must_use] pub fn meter_html(meter: &Meter<'_>, opts: &Emit) -> String { let progress = class("progress", opts); let fill = class("progress-fill", opts); let reported = meter.done.min(meter.total); let mut html = format!( "