| 71 |
71 |
|
"form-interval",
|
| 72 |
72 |
|
"form-label",
|
| 73 |
73 |
|
"form-note",
|
|
74 |
+ |
"form-option-detail",
|
| 74 |
75 |
|
"form-option-reason",
|
| 75 |
76 |
|
"form-radio-group",
|
| 76 |
77 |
|
"form-radio-label",
|
| 653 |
654 |
|
// explanation behind a hover.
|
| 654 |
655 |
|
if let Some(reason) = opt.unavailable {
|
| 655 |
656 |
|
out.push_str(" disabled");
|
| 656 |
|
- |
out.push_str("><span>");
|
| 657 |
|
- |
escape_into(opt.label, out);
|
| 658 |
|
- |
out.push_str("</span><span class=\"");
|
| 659 |
|
- |
push_class(out, "form-option-reason", opts);
|
| 660 |
|
- |
out.push_str("\">");
|
| 661 |
|
- |
escape_into(reason, out);
|
| 662 |
|
- |
out.push_str("</span></label>");
|
| 663 |
|
- |
continue;
|
| 664 |
657 |
|
}
|
| 665 |
658 |
|
out.push_str("><span>");
|
| 666 |
659 |
|
escape_into(opt.label, out);
|
| 667 |
|
- |
out.push_str("</span></label>");
|
|
660 |
+ |
out.push_str("</span>");
|
|
661 |
+ |
// What picking it means, on the line under the label. `5e21dcfc`, and
|
|
662 |
+ |
// the same treatment the reason gets one line down: a radio group has
|
|
663 |
+ |
// room, so the sentence sits in its own element rather than being run
|
|
664 |
+ |
// into the label the way a `<select>`'s has to be.
|
|
665 |
+ |
//
|
|
666 |
+ |
// Before the reason, which is the order the two read in: what this
|
|
667 |
+ |
// option *is* comes ahead of why it cannot be picked, and an option
|
|
668 |
+ |
// carrying both has said two things rather than one long one.
|
|
669 |
+ |
if let Some(detail) = opt.detail {
|
|
670 |
+ |
out.push_str("<span class=\"");
|
|
671 |
+ |
push_class(out, "form-option-detail", opts);
|
|
672 |
+ |
out.push_str("\">");
|
|
673 |
+ |
escape_into(detail, out);
|
|
674 |
+ |
out.push_str("</span>");
|
|
675 |
+ |
}
|
|
676 |
+ |
if let Some(reason) = opt.unavailable {
|
|
677 |
+ |
out.push_str("<span class=\"");
|
|
678 |
+ |
push_class(out, "form-option-reason", opts);
|
|
679 |
+ |
out.push_str("\">");
|
|
680 |
+ |
escape_into(reason, out);
|
|
681 |
+ |
out.push_str("</span>");
|
|
682 |
+ |
}
|
|
683 |
+ |
out.push_str("</label>");
|
| 668 |
684 |
|
}
|
| 669 |
685 |
|
|
| 670 |
686 |
|
out.push_str("</div>");
|
| 724 |
740 |
|
// "Multi-sample: Drop a second sample onto the keyboard." and is the
|
| 725 |
741 |
|
// one place the precondition can be both attached to its option and
|
| 726 |
742 |
|
// read without a pointer.
|
| 727 |
|
- |
if let Some(reason) = opt.unavailable {
|
|
743 |
+ |
if opt.unavailable.is_some() {
|
| 728 |
744 |
|
out.push_str(" disabled");
|
| 729 |
|
- |
out.push('>');
|
| 730 |
|
- |
escape_into(opt.label, out);
|
| 731 |
|
- |
out.push_str(": ");
|
| 732 |
|
- |
escape_into(reason, out);
|
| 733 |
|
- |
out.push_str("</option>");
|
| 734 |
|
- |
continue;
|
| 735 |
745 |
|
}
|
| 736 |
746 |
|
out.push('>');
|
| 737 |
747 |
|
escape_into(opt.label, out);
|
|
748 |
+ |
// Both extra strings run into the row's text, for the reason above:
|
|
749 |
+ |
// this is the one control with nowhere else to put either of them.
|
|
750 |
+ |
// `5e21dcfc` did not invent that rule, it met it.
|
|
751 |
+ |
if let Some(detail) = opt.detail {
|
|
752 |
+ |
out.push_str(": ");
|
|
753 |
+ |
escape_into(detail, out);
|
|
754 |
+ |
}
|
|
755 |
+ |
if let Some(reason) = opt.unavailable {
|
|
756 |
+ |
out.push_str(": ");
|
|
757 |
+ |
escape_into(reason, out);
|
|
758 |
+ |
}
|
| 738 |
759 |
|
out.push_str("</option>");
|
| 739 |
760 |
|
}
|
| 740 |
761 |
|
}
|
| 1162 |
1183 |
|
css
|
| 1163 |
1184 |
|
}
|
| 1164 |
1185 |
|
|
|
1186 |
+ |
/// The rules an option's second line needs.
|
|
1187 |
+ |
///
|
|
1188 |
+ |
/// [`unit_rules`]' argument, and it is worth saying why this one is ruled where
|
|
1189 |
+ |
/// `.form-option-reason` beside it is not: that class shipped at 0.28.0, before
|
|
1190 |
+ |
/// the test the two functions above state — rule what has no app counterpart to
|
|
1191 |
+ |
/// keep — and nothing has been written against it since. This one has none
|
|
1192 |
+ |
/// either, and an unruled second line renders identically to the label it sits
|
|
1193 |
+ |
/// under, which is a worse default than the hand-written markup it replaces.
|
|
1194 |
+ |
/// MNW spells that markup `.card--selectable-desc` and mutes it.
|
|
1195 |
+ |
///
|
|
1196 |
+ |
/// Colour only, and muted, which is the same reading `.form-unit` and
|
|
1197 |
+ |
/// `.form-suggestion-detail` take: the line orients the label rather than
|
|
1198 |
+ |
/// competing with it. Nothing about placement or spacing, for `unit_rules`'
|
|
1199 |
+ |
/// reason — a magnitude asserted here belongs to `makeover-geometry`.
|
|
1200 |
+ |
pub(crate) fn option_detail_rules(opts: &Emit) -> String {
|
|
1201 |
+ |
let detail = class("form-option-detail", opts);
|
|
1202 |
+ |
let mut css = String::new();
|
|
1203 |
+ |
let _ = writeln!(css, ".{detail} {{\n color: var(--content-muted);\n}}");
|
|
1204 |
+ |
css
|
|
1205 |
+ |
}
|
|
1206 |
+ |
|
| 1165 |
1207 |
|
/// The rules a field's suggestion list needs.
|
| 1166 |
1208 |
|
///
|
| 1167 |
1209 |
|
/// [`editor_rules`]' precedent and its argument: the class names this module's
|
| 1879 |
1921 |
|
assert!(html.contains("disabled"), "{html}");
|
| 1880 |
1922 |
|
}
|
| 1881 |
1923 |
|
|
|
1924 |
+ |
#[test]
|
|
1925 |
+ |
fn an_option_can_say_what_picking_it_means() {
|
|
1926 |
+ |
// makeover-layout 0.39.0. A radio group has room, so the line gets its
|
|
1927 |
+ |
// own element under the label, and it is muted rather than unruled: an
|
|
1928 |
+ |
// unruled second line renders identically to the label above it, which
|
|
1929 |
+ |
// is a worse default than the markup this replaces.
|
|
1930 |
+ |
let options = [
|
|
1931 |
+ |
Choice::new("16", "Basic").detailing("$16/mo. Fits text, blogs, newsletters."),
|
|
1932 |
+ |
Choice::new("24", "Small Files"),
|
|
1933 |
+ |
];
|
|
1934 |
+ |
let f = Field::radio("tier", "Content tier", &options);
|
|
1935 |
+ |
let html = field_html(&f, &Filling::default(), &Emit::default());
|
|
1936 |
+ |
|
|
1937 |
+ |
assert!(html.contains("form-option-detail"), "{html}");
|
|
1938 |
+ |
assert!(
|
|
1939 |
+ |
html.contains(">$16/mo. Fits text, blogs, newsletters.</span>"),
|
|
1940 |
+ |
"{html}"
|
|
1941 |
+ |
);
|
|
1942 |
+ |
// One option carries it and the other does not, so the class appears
|
|
1943 |
+ |
// once rather than on every label.
|
|
1944 |
+ |
assert_eq!(html.matches("form-option-detail").count(), 1, "{html}");
|
|
1945 |
+ |
assert!(
|
|
1946 |
+ |
option_detail_rules(&Emit::default()).contains("var(--content-muted)"),
|
|
1947 |
+ |
"the line orients the label rather than competing with it"
|
|
1948 |
+ |
);
|
|
1949 |
+ |
}
|
|
1950 |
+ |
|
|
1951 |
+ |
#[test]
|
|
1952 |
+ |
fn an_option_reads_what_it_is_before_why_it_cannot_be_picked() {
|
|
1953 |
+ |
// Two different sentences, drawn in the order they read in. A tier that
|
|
1954 |
+ |
// is sold out is still a tier the reader is owed a description of.
|
|
1955 |
+ |
let options = [Choice::new("24", "Small Files")
|
|
1956 |
+ |
.detailing("$24/mo. Fits audio, plugins, binaries.")
|
|
1957 |
+ |
.unless("Sold out while the founder window is open.")];
|
|
1958 |
+ |
let f = Field::radio("tier", "Content tier", &options);
|
|
1959 |
+ |
let html = field_html(&f, &Filling::default(), &Emit::default());
|
|
1960 |
+ |
|
|
1961 |
+ |
let detail = html.find("form-option-detail").expect("the detail");
|
|
1962 |
+ |
let reason = html.find("form-option-reason").expect("the reason");
|
|
1963 |
+ |
assert!(detail < reason, "{html}");
|
|
1964 |
+ |
assert!(html.contains(" disabled"), "{html}");
|
|
1965 |
+ |
|
|
1966 |
+ |
// A `<select>` has room for neither element, so both run into the
|
|
1967 |
+ |
// row's own text in the same order.
|
|
1968 |
+ |
let f = Field::select("tier", "Content tier", &options);
|
|
1969 |
+ |
let html = field_html(&f, &Filling::default(), &Emit::default());
|
|
1970 |
+ |
assert!(
|
|
1971 |
+ |
html.contains(concat!(
|
|
1972 |
+ |
">Small Files: $24/mo. Fits audio, plugins, binaries.",
|
|
1973 |
+ |
": Sold out while the founder window is open.</option>"
|
|
1974 |
+ |
)),
|
|
1975 |
+ |
"{html}"
|
|
1976 |
+ |
);
|
|
1977 |
+ |
}
|
|
1978 |
+ |
|
| 1882 |
1979 |
|
#[test]
|
| 1883 |
1980 |
|
fn a_radio_group_is_named_by_its_label_instead_of_pointing_at_it() {
|
| 1884 |
1981 |
|
// The association inverts, and getting it wrong is silent: a
|