| 843 |
843 |
|
Tel,
|
| 844 |
844 |
|
/// Several lines of text.
|
| 845 |
845 |
|
Textarea,
|
| 846 |
|
- |
/// One of a fixed set.
|
|
846 |
+ |
/// One of a fixed set, offered behind a control that shows one at a time.
|
| 847 |
847 |
|
Select,
|
|
848 |
+ |
/// One of a fixed set, with every option on screen at once.
|
|
849 |
+ |
///
|
|
850 |
+ |
/// Not a presentation of [`Select`](Self::Select), which is the reading to
|
|
851 |
+ |
/// resist: what differs is a property of the *question*. A choice that is
|
|
852 |
+ |
/// consequential or irreversible has to be readable without opening
|
|
853 |
+ |
/// anything, because a closed control shows one option and hides the rest,
|
|
854 |
+ |
/// and the one it shows is whichever was current before the user had read
|
|
855 |
+ |
/// the alternatives. audiofiles asks whether a library copies samples into
|
|
856 |
+ |
/// its store or references them where they lie — which cannot be changed
|
|
857 |
+ |
/// afterwards — and had already promoted that out of a checkbox by hand,
|
|
858 |
+ |
/// with a comment giving this reason, before the description could say it.
|
|
859 |
+ |
///
|
|
860 |
+ |
/// It is also the one HTML input type this enum was missing. Everything
|
|
861 |
+ |
/// else here is an `<input type=...>`, a `<select>` or a `<textarea>`, and
|
|
862 |
+ |
/// the hole was `radio`.
|
|
863 |
+ |
///
|
|
864 |
+ |
/// Added 0.8.1, from audiofiles' Add Library form.
|
|
865 |
+ |
Radio,
|
| 848 |
866 |
|
/// On or off.
|
| 849 |
867 |
|
Checkbox,
|
| 850 |
868 |
|
/// Carried through the form and never shown.
|
| 869 |
887 |
|
/// A checkbox labels itself on the right of the box; everything else takes
|
| 870 |
888 |
|
/// a label above. Both webview apps already do this and both special-case
|
| 871 |
889 |
|
/// it inline, which is the tell that it belongs in the description.
|
|
890 |
+ |
///
|
|
891 |
+ |
/// A [`Radio`](Self::Radio) is not one of them, and the near-miss is worth
|
|
892 |
+ |
/// naming: its *options* each label themselves, but the field still asks a
|
|
893 |
+ |
/// question above them, so the group takes a label like everything else.
|
| 872 |
894 |
|
#[must_use]
|
| 873 |
895 |
|
pub const fn labels_itself(self) -> bool {
|
| 874 |
896 |
|
matches!(self, Self::Checkbox)
|
| 875 |
897 |
|
}
|
|
898 |
+ |
|
|
899 |
+ |
/// Whether the kind reads [`Field::options`].
|
|
900 |
+ |
///
|
|
901 |
+ |
/// Two kinds do, so the pair is named once here rather than spelled out at
|
|
902 |
+ |
/// each renderer and again in [`Field::options`]' own doc, where "every
|
|
903 |
+ |
/// kind but `Select`" was true for exactly one release. A third
|
|
904 |
+ |
/// option-taking kind should land here and nowhere else.
|
|
905 |
+ |
#[must_use]
|
|
906 |
+ |
pub const fn offers_options(self) -> bool {
|
|
907 |
+ |
matches!(self, Self::Select | Self::Radio)
|
|
908 |
+ |
}
|
| 876 |
909 |
|
}
|
| 877 |
910 |
|
|
| 878 |
|
- |
/// One option a [`FieldKind::Select`] offers.
|
|
911 |
+ |
/// One option offered by a field [`FieldKind::offers_options`] accepts.
|
| 879 |
912 |
|
///
|
| 880 |
913 |
|
/// Two strings, because the submitted value and the read label are different
|
| 881 |
914 |
|
/// facts and every renderer that has tried to collapse them has had to
|
| 953 |
986 |
|
pub placeholder: Option<&'a str>,
|
| 954 |
987 |
|
/// The options offered, in the order they are offered.
|
| 955 |
988 |
|
///
|
| 956 |
|
- |
/// Empty for every kind but [`FieldKind::Select`]. A select described with
|
| 957 |
|
- |
/// no options is sayable on purpose: it is what an app with an
|
| 958 |
|
- |
/// unfinished-loading option list actually has, and a renderer showing an
|
| 959 |
|
- |
/// empty select says so on screen rather than in a log.
|
|
989 |
+ |
/// Empty for every kind [`FieldKind::offers_options`] rejects. A field
|
|
990 |
+ |
/// described with no options is sayable on purpose: it is what an app with
|
|
991 |
+ |
/// an unfinished-loading option list actually has, and a renderer showing
|
|
992 |
+ |
/// an empty control says so on screen rather than in a log.
|
| 960 |
993 |
|
///
|
| 961 |
994 |
|
/// Which option is *current* is not here. That is the value, and the value
|
| 962 |
995 |
|
/// is renderer state.
|
| 986 |
1019 |
|
|
| 987 |
1020 |
|
/// A select offering the given options.
|
| 988 |
1021 |
|
///
|
| 989 |
|
- |
/// The one kind that is under-described by [`Field::new`], so it gets a
|
|
1022 |
+ |
/// One of the two kinds under-described by [`Field::new`], so it gets a
|
| 990 |
1023 |
|
/// constructor rather than leaving every call site to remember that a
|
| 991 |
1024 |
|
/// select with an empty `options` renders as an empty select.
|
| 992 |
1025 |
|
#[must_use]
|
| 993 |
1026 |
|
pub const fn select(name: &'a str, label: &'a str, options: &'a [Choice<'a>]) -> Self {
|
|
1027 |
+ |
Self::offering(FieldKind::Select, name, label, options)
|
|
1028 |
+ |
}
|
|
1029 |
+ |
|
|
1030 |
+ |
/// A radio group offering the given options.
|
|
1031 |
+ |
///
|
|
1032 |
+ |
/// The other. Same hazard as [`select`](Self::select) and a worse one: a
|
|
1033 |
+ |
/// radio group with no options draws nothing at all, so a call site that
|
|
1034 |
+ |
/// forgot them has an empty rectangle rather than a visibly empty control.
|
|
1035 |
+ |
#[must_use]
|
|
1036 |
+ |
pub const fn radio(name: &'a str, label: &'a str, options: &'a [Choice<'a>]) -> Self {
|
|
1037 |
+ |
Self::offering(FieldKind::Radio, name, label, options)
|
|
1038 |
+ |
}
|
|
1039 |
+ |
|
|
1040 |
+ |
/// The shared body of the two constructors that take options.
|
|
1041 |
+ |
///
|
|
1042 |
+ |
/// Private, and keyed on the kind rather than exposed, because the two
|
|
1043 |
+ |
/// public names are the point: a call site says which question it is
|
|
1044 |
+ |
/// asking, not which flag it is setting.
|
|
1045 |
+ |
const fn offering(
|
|
1046 |
+ |
kind: FieldKind,
|
|
1047 |
+ |
name: &'a str,
|
|
1048 |
+ |
label: &'a str,
|
|
1049 |
+ |
options: &'a [Choice<'a>],
|
|
1050 |
+ |
) -> Self {
|
| 994 |
1051 |
|
Self {
|
| 995 |
|
- |
kind: FieldKind::Select,
|
| 996 |
|
- |
name,
|
| 997 |
|
- |
label,
|
| 998 |
|
- |
hint: None,
|
| 999 |
|
- |
error: None,
|
| 1000 |
|
- |
placeholder: None,
|
| 1001 |
1052 |
|
options,
|
| 1002 |
|
- |
required: false,
|
| 1003 |
|
- |
extended: false,
|
|
1053 |
+ |
..Self::new(kind, name, label)
|
| 1004 |
1054 |
|
}
|
| 1005 |
1055 |
|
}
|
| 1006 |
1056 |
|
|
| 1413 |
1463 |
|
assert_ne!(spelled.value, spelled.label);
|
| 1414 |
1464 |
|
}
|
| 1415 |
1465 |
|
|
|
1466 |
+ |
#[test]
|
|
1467 |
+ |
fn a_radio_asks_the_same_question_as_a_select_and_is_not_the_same_kind() {
|
|
1468 |
+ |
// Both offer a fixed set and both read `options`, so the two
|
|
1469 |
+ |
// constructors differ in exactly one thing. That one thing is the
|
|
1470 |
+ |
// point: a renderer decides whether the alternatives are readable
|
|
1471 |
+ |
// without opening anything, and it can only decide that if the
|
|
1472 |
+ |
// description said which question was asked.
|
|
1473 |
+ |
let styles = [
|
|
1474 |
+ |
Choice {
|
|
1475 |
+ |
value: "copy",
|
|
1476 |
+ |
label: "Copy samples in",
|
|
1477 |
+ |
},
|
|
1478 |
+ |
Choice {
|
|
1479 |
+ |
value: "reference",
|
|
1480 |
+ |
label: "Reference in place",
|
|
1481 |
+ |
},
|
|
1482 |
+ |
];
|
|
1483 |
+ |
let radio = Field::radio("storage", "Storage style", &styles);
|
|
1484 |
+ |
let select = Field::select("storage", "Storage style", &styles);
|
|
1485 |
+ |
|
|
1486 |
+ |
assert_eq!(radio.kind, FieldKind::Radio);
|
|
1487 |
+ |
assert_ne!(radio.kind, select.kind);
|
|
1488 |
+ |
assert_eq!(radio.options, select.options);
|
|
1489 |
+ |
assert_eq!(Field { kind: select.kind, ..radio }, select);
|
|
1490 |
+ |
}
|
|
1491 |
+ |
|
|
1492 |
+ |
#[test]
|
|
1493 |
+ |
fn exactly_the_option_taking_kinds_say_so() {
|
|
1494 |
+ |
// The renderers branch on this rather than on a list of their own, so
|
|
1495 |
+ |
// a kind added without a decision here renders its options nowhere.
|
|
1496 |
+ |
assert!(FieldKind::Select.offers_options());
|
|
1497 |
+ |
assert!(FieldKind::Radio.offers_options());
|
|
1498 |
+ |
for kind in [
|
|
1499 |
+ |
FieldKind::Text,
|
|
1500 |
+ |
FieldKind::Secret,
|
|
1501 |
+ |
FieldKind::Number,
|
|
1502 |
+ |
FieldKind::Email,
|
|
1503 |
+ |
FieldKind::Url,
|
|
1504 |
+ |
FieldKind::Tel,
|
|
1505 |
+ |
FieldKind::Textarea,
|
|
1506 |
+ |
FieldKind::Checkbox,
|
|
1507 |
+ |
FieldKind::Hidden,
|
|
1508 |
+ |
] {
|
|
1509 |
+ |
assert!(!kind.offers_options(), "{kind:?} does not offer options");
|
|
1510 |
+ |
}
|
|
1511 |
+ |
}
|
|
1512 |
+ |
|
|
1513 |
+ |
#[test]
|
|
1514 |
+ |
fn a_radio_group_takes_a_label_even_though_its_options_carry_their_own() {
|
|
1515 |
+ |
// The near-miss: each option is labelled beside its own button, so a
|
|
1516 |
+ |
// renderer could plausibly read the group as self-labelling and drop
|
|
1517 |
+ |
// the question. Checkbox is the only kind that does that.
|
|
1518 |
+ |
assert!(!FieldKind::Radio.labels_itself());
|
|
1519 |
+ |
assert!(FieldKind::Checkbox.labels_itself());
|
|
1520 |
+ |
}
|
|
1521 |
+ |
|
| 1416 |
1522 |
|
#[test]
|
| 1417 |
1523 |
|
fn a_select_with_no_options_is_sayable() {
|
| 1418 |
1524 |
|
// An app whose option list has not loaded has exactly this. Making it
|