| 23 |
23 |
|
use std::fmt::Write as _;
|
| 24 |
24 |
|
|
| 25 |
25 |
|
use makeover_layout as layout;
|
|
26 |
+ |
// `Tone::token` is the trait method, and `data-tone` is spelled from it rather
|
|
27 |
+ |
// than from a match here, so a tone added upstream cannot be named two ways.
|
|
28 |
+ |
use makeover_layout::Intent as _;
|
| 26 |
29 |
|
use makeover_webview::Emit;
|
| 27 |
30 |
|
use makeover_webview::figure::figure_html;
|
| 28 |
31 |
|
use makeover_webview::form::{Filling, Markup, Value, escape, field_html};
|
| 29 |
32 |
|
// `Cell` is a name both crates use: makeover's is the emitted table cell, ours
|
| 30 |
33 |
|
// is the described one. Aliased rather than qualified at the call site, so the
|
| 31 |
34 |
|
// two never read as the same type.
|
| 32 |
|
- |
use makeover_webview::list::{Cell as Emitted, cells_html};
|
|
35 |
+ |
use makeover_webview::list::{Cell as Emitted, cells_html, column_classes};
|
| 33 |
36 |
|
use makeover_webview::meter::meter_html;
|
| 34 |
37 |
|
use makeover_webview::placeholder::placeholder_html;
|
| 35 |
38 |
|
use quasi_router::screen::{Act, Cells, Destination, Field, Node, Prose, Row, Slot, Tag};
|
| 52 |
55 |
|
out.push('"');
|
| 53 |
56 |
|
}
|
| 54 |
57 |
|
|
| 55 |
|
- |
/// The class suffix naming a tone.
|
|
58 |
+ |
/// Write the attribute naming a tone, if the tone is worth naming.
|
| 56 |
59 |
|
///
|
| 57 |
|
- |
/// [`Tone::Neutral`] has none: ordinary content is the default, and a class
|
| 58 |
|
- |
/// meaning "nothing unusual" is a class on every element in the document.
|
| 59 |
|
- |
fn tone_class(tone: layout::Tone) -> Option<&'static str> {
|
| 60 |
|
- |
match tone {
|
| 61 |
|
- |
layout::Tone::Neutral => None,
|
| 62 |
|
- |
layout::Tone::Info => Some("tone-info"),
|
| 63 |
|
- |
layout::Tone::Success => Some("tone-success"),
|
| 64 |
|
- |
layout::Tone::Warning => Some("tone-warning"),
|
| 65 |
|
- |
layout::Tone::Danger => Some("tone-danger"),
|
|
60 |
+ |
/// [`Tone::Neutral`] writes nothing: ordinary content is the default, and an
|
|
61 |
+ |
/// attribute meaning "nothing unusual" is an attribute on every element in the
|
|
62 |
+ |
/// document.
|
|
63 |
+ |
///
|
|
64 |
+ |
/// An attribute and not a class, which is the correction. This emitted
|
|
65 |
+ |
/// `tone-info`, `tone-success`, `tone-warning` and `tone-danger` as classes, and
|
|
66 |
+ |
/// makeover has never defined one of them: its whole vocabulary keys tone off
|
|
67 |
+ |
/// `data-tone`, from `.badge[data-tone="danger"]` to the progress fill to a
|
|
68 |
+ |
/// figure's value. So every toned thing a description produced arrived with a
|
|
69 |
+ |
/// class no stylesheet in the tree had heard of, which is why the SSH-keys tab's
|
|
70 |
+ |
/// Remove button came out the same colour as everything else.
|
|
71 |
+ |
fn tone_attr(tone: layout::Tone, out: &mut String) {
|
|
72 |
+ |
if matches!(tone, layout::Tone::Neutral) {
|
|
73 |
+ |
return;
|
| 66 |
74 |
|
}
|
|
75 |
+ |
out.push_str(" data-tone=\"");
|
|
76 |
+ |
out.push_str(tone.token());
|
|
77 |
+ |
out.push('"');
|
| 67 |
78 |
|
}
|
| 68 |
79 |
|
|
| 69 |
80 |
|
/// What goes back in the box, for a form being offered again after a refusal.
|
| 375 |
386 |
|
|
| 376 |
387 |
|
/// A control that calls a route.
|
| 377 |
388 |
|
pub(crate) fn act_html(act: &Act, morphs: bool, opts: &Emit, out: &mut String) {
|
| 378 |
|
- |
let mut classes = vec!["act"];
|
| 379 |
|
- |
if let Some(tone) = tone_class(act.tone) {
|
| 380 |
|
- |
classes.push(tone);
|
| 381 |
|
- |
}
|
| 382 |
|
- |
|
|
389 |
+ |
// `button`, which is makeover's name for this and carries its whole
|
|
390 |
+ |
// interactive set: the raised bevel, the hover fill, the pressed inset, the
|
|
391 |
+ |
// focus ring and the disabled treatment. This emitted `act`, a second name
|
|
392 |
+ |
// for the same thing that no stylesheet in the tree defined, so a described
|
|
393 |
+ |
// control rendered as unstyled text. There was never a concept here that
|
|
394 |
+ |
// `button` was not already the word for.
|
| 383 |
395 |
|
let (open, close) = control_tag(&act.action);
|
| 384 |
396 |
|
out.push_str(open);
|
| 385 |
|
- |
class_attr(&classes, opts, out);
|
|
397 |
+ |
class_attr(&["button"], opts, out);
|
|
398 |
+ |
tone_attr(act.tone, out);
|
| 386 |
399 |
|
// Named rather than found by class, so anything binding to "this is a
|
| 387 |
400 |
|
// control" survives a host setting `Emit::class_prefix`. `Fires::ClickBeside`
|
| 388 |
401 |
|
// is the first reader; a table row uses it to tell its own click apart from
|
| 673 |
686 |
|
}
|
| 674 |
687 |
|
|
| 675 |
688 |
|
Node::Text { text, tone } => {
|
| 676 |
|
- |
let mut classes = vec!["text"];
|
| 677 |
|
- |
if let Some(tone) = tone_class(*tone) {
|
| 678 |
|
- |
classes.push(tone);
|
| 679 |
|
- |
}
|
| 680 |
689 |
|
out.push_str("<p");
|
| 681 |
|
- |
class_attr(&classes, opts, out);
|
|
690 |
+ |
class_attr(&["text"], opts, out);
|
|
691 |
+ |
tone_attr(*tone, out);
|
| 682 |
692 |
|
out.push('>');
|
| 683 |
693 |
|
out.push_str(&escape(text));
|
| 684 |
694 |
|
out.push_str("</p>");
|
| 718 |
728 |
|
Node::Token(tag) => tag_html(tag, morphs, opts, out),
|
| 719 |
729 |
|
|
| 720 |
730 |
|
Node::Notice { kind, tone, text } => {
|
| 721 |
|
- |
let mut classes = vec![match kind {
|
| 722 |
|
- |
layout::Notice::Toast => "toast",
|
| 723 |
|
- |
layout::Notice::Banner => "banner",
|
| 724 |
|
- |
}];
|
| 725 |
|
- |
if let Some(tone) = tone_class(*tone) {
|
| 726 |
|
- |
classes.push(tone);
|
| 727 |
|
- |
}
|
| 728 |
731 |
|
out.push_str("<div");
|
| 729 |
|
- |
class_attr(&classes, opts, out);
|
|
732 |
+ |
class_attr(
|
|
733 |
+ |
&[match kind {
|
|
734 |
+ |
layout::Notice::Toast => "toast",
|
|
735 |
+ |
layout::Notice::Banner => "banner",
|
|
736 |
+ |
}],
|
|
737 |
+ |
opts,
|
|
738 |
+ |
out,
|
|
739 |
+ |
);
|
|
740 |
+ |
tone_attr(*tone, out);
|
| 730 |
741 |
|
// A danger or warning notice interrupts; anything else waits for a
|
| 731 |
742 |
|
// pause. The description already says which through its tone, so
|
| 732 |
743 |
|
// the renderer does not need a second field to be told.
|
| 756 |
767 |
|
field_group_html(field, morphs, opts, out);
|
| 757 |
768 |
|
}
|
| 758 |
769 |
|
out.push_str("<button type=\"submit\"");
|
| 759 |
|
- |
class_attr(&["act", "act-submit"], opts, out);
|
|
770 |
+ |
class_attr(&["button", "act-submit"], opts, out);
|
| 760 |
771 |
|
out.push('>');
|
| 761 |
772 |
|
out.push_str(&escape(submit));
|
| 762 |
773 |
|
out.push_str("</button></form>");
|
| 782 |
793 |
|
|
| 783 |
794 |
|
let (open, close) = control_tag(&rest.action);
|
| 784 |
795 |
|
out.push_str(open);
|
| 785 |
|
- |
class_attr(&["act", "rest-more"], opts, out);
|
|
796 |
+ |
class_attr(&["button", "rest-more"], opts, out);
|
| 786 |
797 |
|
action_attrs(&rest.action, Fires::Click, None, morphs, out);
|
| 787 |
798 |
|
out.push('>');
|
| 788 |
799 |
|
match rest.remaining {
|
| 803 |
814 |
|
.iter()
|
| 804 |
815 |
|
.map(quasi_router::screen::Column::as_layout)
|
| 805 |
816 |
|
.collect();
|
| 806 |
|
- |
// No inline grid tracks. `makeover_webview::list::narrowing_css`
|
| 807 |
|
- |
// emits the track list and the per-column hiding together, per
|
| 808 |
|
- |
// breakpoint, from these same columns; writing tracks here as well
|
| 809 |
|
- |
// would put the widest layout in the markup where it outranks the
|
| 810 |
|
- |
// narrow rules, which is goingson's mobile bug reintroduced from
|
| 811 |
|
- |
// the other side. The markup owes the cells their column classes
|
| 812 |
|
- |
// and nothing more, and `cells_html` is what knows those.
|
|
817 |
+ |
// No CSS travels with the table, and none can. A described table's
|
|
818 |
+ |
// columns are known here rather than at build time, so a track list
|
|
819 |
+ |
// would have to be emitted per table: a `<style>` element beside it,
|
|
820 |
+ |
// which needs `style-src 'unsafe-inline'`, or a head block, which a
|
|
821 |
+ |
// fragment swap does not carry. makeover's stylesheet lays the table
|
|
822 |
+ |
// out with `display: table` instead, aligning columns across rows
|
|
823 |
+ |
// knowing nothing about how many there are, and hides a dropped
|
|
824 |
+ |
// column by the drop class its cells carry. The markup owes the
|
|
825 |
+ |
// cells and headings those classes and nothing more.
|
| 813 |
826 |
|
out.push_str("<div role=\"table\"");
|
| 814 |
827 |
|
class_attr(&["table"], opts, out);
|
| 815 |
828 |
|
out.push('>');
|
| 819 |
832 |
|
out.push('>');
|
| 820 |
833 |
|
for (column, described) in borrowed.iter().zip(columns) {
|
| 821 |
834 |
|
out.push_str("<span role=\"columnheader\"");
|
| 822 |
|
- |
class_attr(&["table-heading"], opts, out);
|
|
835 |
+ |
// The heading carries the same classes its column's cells do,
|
|
836 |
+ |
// or the header and the body disagree about which column just
|
|
837 |
+ |
// dropped and every heading below the cut sits over the wrong
|
|
838 |
+ |
// values. `column_classes` is makeover's, so the two lists
|
|
839 |
+ |
// cannot be assembled differently in two places.
|
|
840 |
+ |
out.push_str(" class=\"");
|
|
841 |
+ |
out.push_str(&escape(&class("table-heading", opts)));
|
|
842 |
+ |
out.push(' ');
|
|
843 |
+ |
out.push_str(&escape(&column_classes(column, opts)));
|
|
844 |
+ |
out.push('"');
|
| 823 |
845 |
|
// `aria-sort` is what a table actually says about its order;
|
| 824 |
846 |
|
// the caret in `state_rules` is this renderer's expression of
|
| 825 |
847 |
|
// the same fact for everyone not using a screen reader.
|
| 928 |
950 |
|
layout::Token::Badge => "badge",
|
| 929 |
951 |
|
layout::Token::Chip { .. } => "chip",
|
| 930 |
952 |
|
}];
|
| 931 |
|
- |
if let Some(tone) = tone_class(tone) {
|
| 932 |
|
- |
classes.push(tone);
|
| 933 |
|
- |
}
|
|
953 |
+ |
// `latched`, which is what makeover styles: `.chip.latched` is the pressed
|
|
954 |
+ |
// depth a chip holds itself down with. This said `chip-latched`, a third
|
|
955 |
+ |
// name for it, and a latched chip therefore looked exactly like an
|
|
956 |
+ |
// unlatched one.
|
| 934 |
957 |
|
if latched {
|
| 935 |
|
- |
classes.push("chip-latched");
|
|
958 |
+ |
classes.push("latched");
|
| 936 |
959 |
|
}
|
| 937 |
960 |
|
|
| 938 |
961 |
|
// A badge answers no click, so it is not a button however it is styled.
|
| 951 |
974 |
|
"</span>"
|
| 952 |
975 |
|
};
|
| 953 |
976 |
|
class_attr(&classes, opts, out);
|
|
977 |
+ |
tone_attr(tone, out);
|
| 954 |
978 |
|
|
| 955 |
979 |
|
if interactive {
|
| 956 |
980 |
|
if latched {
|