Skip to main content

max / quasi

3.4 KB · 90 lines History Blame Raw
1 //! The frame a mount puts around a screen, as markup.
2 //!
3 //! [`Frame`] is host-agnostic and lives in `quasi-router`; what is here is one
4 //! host's answer to it — a row of verbs and, where the mount says it has one,
5 //! a place for a persistent notice to rest.
6 //!
7 //! # One placement, not the two that were measured
8 //!
9 //! goingson draws compose's verbs in a footer action row inside the form when
10 //! it is a modal, and in a toolbar above the form when it is a window. Both
11 //! mounts get the trailing row here, and that is the point rather than a
12 //! shortcut: two mounts supplying the same verbs should draw them in the same
13 //! place, and a renderer that reproduced the split would be describing the
14 //! accident instead of answering it. What the two mounts still differ in is
15 //! which verbs they supply, which is where the difference belongs.
16 //!
17 //! # Outside `<main>`, beside the chrome
18 //!
19 //! For the chrome's reason, one lifetime along: the screen's markup is what a
20 //! fragment replaces, and a frame survives every answer that lands in it. A
21 //! verb row inside `<main>` would be swept away by the first navigation and
22 //! rebuilt by the next, which is the per-screen repetition the member exists to
23 //! end.
24
25 use makeover_webview::Emit;
26 use quasi_router::{Frame, Node};
27
28 use crate::node::{Doc, act_html, class_into, node_html};
29
30 /// The id of the frame's status line.
31 ///
32 /// Named rather than a class, because it is a target: a route answering with a
33 /// banner about a framed screen aims it here through
34 /// [`Action::replacing`](quasi_router::Action::replacing), the same way every
35 /// other region is addressed. A mount that does not say it reports emits no
36 /// such element, and an answer aimed at one lands nowhere — visible, which is
37 /// the treatment every other address miss gets.
38 pub const STATUS_ID: &str = "quasi-frame-status";
39
40 /// The frame around a screen, or nothing at all.
41 ///
42 /// `resting` is the screen's notices this frame holds — [`Frame::holds`] is the
43 /// rule and it is the router's, so the three renderers cannot each decide which
44 /// kind of message belongs in a status line.
45 pub(crate) fn frame_html(
46 frame: &Frame,
47 resting: &[&Node],
48 opts: &Emit,
49 doc: &Doc<'_>,
50 out: &mut String,
51 ) {
52 if frame.bare() {
53 return;
54 }
55
56 out.push_str("<div class=\"");
57 class_into("frame", opts, out);
58 out.push_str("\">");
59
60 // The sink first. A status line above the verbs is the reading order the
61 // window already has -- what happened, then what to do next -- and it is
62 // the one placement decision here.
63 if frame.status {
64 out.push_str("<div id=\"");
65 out.push_str(STATUS_ID);
66 out.push_str("\" class=\"");
67 class_into("frame-status", opts, out);
68 // Polite rather than assertive: a status line reports what happened
69 // and does not interrupt what is being typed, which is the whole
70 // difference between it and the toast a modal raises.
71 out.push_str("\" role=\"status\" aria-live=\"polite\">");
72 for notice in resting {
73 node_html(notice, opts, doc, out);
74 }
75 out.push_str("</div>");
76 }
77
78 if !frame.verbs.is_empty() {
79 out.push_str("<div class=\"");
80 class_into("frame-verbs", opts, out);
81 out.push_str("\">");
82 for verb in &frame.verbs {
83 act_html(verb, opts, doc, out);
84 }
85 out.push_str("</div>");
86 }
87
88 out.push_str("</div>");
89 }
90