| 1425 |
1425 |
|
out.push_str("</div>");
|
| 1426 |
1426 |
|
}
|
| 1427 |
1427 |
|
|
|
1428 |
+ |
/// Write a `class="..."` attribute, prefixed, plus `current` if it is the one.
|
|
1429 |
+ |
///
|
|
1430 |
+ |
/// `current` is written bare rather than through [`class_attr`], because
|
|
1431 |
+ |
/// `showing_rules` writes `.showing-frame:not(.current)` and the state half of
|
|
1432 |
+ |
/// that pair does not move under a prefix. Same shape as makeover's `chosen`
|
|
1433 |
+ |
/// and `latched`: the thing is prefixed, the state qualifying it is not.
|
|
1434 |
+ |
fn frame_class_attr(current: bool, opts: &Emit, out: &mut String) {
|
|
1435 |
+ |
out.push_str(" class=\"");
|
|
1436 |
+ |
out.push_str(&escape(&class("showing-frame", opts)));
|
|
1437 |
+ |
if current {
|
|
1438 |
+ |
out.push_str(" current");
|
|
1439 |
+ |
}
|
|
1440 |
+ |
out.push('"');
|
|
1441 |
+ |
}
|
|
1442 |
+ |
|
|
1443 |
+ |
/// The chrome for a region showing one child at a time.
|
|
1444 |
+ |
///
|
|
1445 |
+ |
/// Derived, once, for every widget there will ever be. Nothing here reads
|
|
1446 |
+ |
/// [`quasi_router::RegionKind::Widget`]'s name, and that is the point of the
|
|
1447 |
+ |
/// whole design: a carousel, a tab group and a disclosure are one region that
|
|
1448 |
+ |
/// shows some of its children, and which idiom comes out falls out of what the
|
|
1449 |
+ |
/// children carry rather than out of what the assembly is called.
|
|
1450 |
+ |
///
|
|
1451 |
+ |
/// # The three shapes, and what picks between them
|
|
1452 |
+ |
///
|
|
1453 |
+ |
/// - Children carrying labels get a strip of them, which is
|
|
1454 |
+ |
/// [`layout::Selector::Tabs`] markup verbatim. A tab strip is already a
|
|
1455 |
+ |
/// described thing here; deriving a second spelling of one would be this
|
|
1456 |
+ |
/// renderer inventing a name makeover would then not style, which is the
|
|
1457 |
+ |
/// SSH-keys bug with extra steps.
|
|
1458 |
+ |
/// - One dismissible child with a name gets that name as a control, which is a
|
|
1459 |
+ |
/// summary line that opens.
|
|
1460 |
+ |
/// - Anything else gets previous, position, next.
|
|
1461 |
+ |
///
|
|
1462 |
+ |
/// # Why these controls are not links
|
|
1463 |
+ |
///
|
|
1464 |
+ |
/// A control here calls no route, because the description names none: what
|
|
1465 |
+ |
/// changes is which of several children already in the document is showing, and
|
|
1466 |
+ |
/// a round trip to reveal bytes the reader has downloaded is worse on every one
|
|
1467 |
+ |
/// of the three MNW galleries this was measured against. So the controls carry
|
|
1468 |
+ |
/// `data-shows` and whatever binds the region binds them, the same relationship
|
|
1469 |
+ |
/// `data-act` and `data-bespoke` already have with their host.
|
|
1470 |
+ |
///
|
|
1471 |
+ |
/// That leaves route-bound movement available and unbuilt. A description that
|
|
1472 |
+ |
/// wanted a real fragment request would carry an [`Action`], the controls would
|
|
1473 |
+ |
/// become links through [`control_tag`] exactly as a selector's options do, and
|
|
1474 |
+ |
/// nothing here would have to change shape. Nothing asks for it today.
|
|
1475 |
+ |
fn showing_html(slot: &Slot, opts: &Emit, out: &mut String) {
|
|
1476 |
+ |
let labels = slot.labels();
|
|
1477 |
+ |
let current = slot.current();
|
|
1478 |
+ |
let total = slot.body.len();
|
|
1479 |
+ |
|
|
1480 |
+ |
// A named single child that can close is a disclosure, and the check comes
|
|
1481 |
+ |
// first because such a child is also a labelled one: a strip of one tab is
|
|
1482 |
+ |
// not what a summary line is.
|
|
1483 |
+ |
if slot.showing.dismissible()
|
|
1484 |
+ |
&& total == 1
|
|
1485 |
+ |
&& let [label] = labels.as_slice()
|
|
1486 |
+ |
{
|
|
1487 |
+ |
out.push_str("<button type=\"button\"");
|
|
1488 |
+ |
// `button`, which is makeover's name for a control and carries its whole
|
|
1489 |
+ |
// interactive set. `act_html` learned this the expensive way.
|
|
1490 |
+ |
class_attr(&["button"], opts, out);
|
|
1491 |
+ |
out.push_str(" data-shows=\"0\" aria-expanded=\"");
|
|
1492 |
+ |
out.push_str(if current.is_some() {
|
|
1493 |
+ |
"true\""
|
|
1494 |
+ |
} else {
|
|
1495 |
+ |
"false\""
|
|
1496 |
+ |
});
|
|
1497 |
+ |
out.push('>');
|
|
1498 |
+ |
out.push_str(&escape(label));
|
|
1499 |
+ |
out.push_str("</button>");
|
|
1500 |
+ |
return;
|
|
1501 |
+ |
}
|
|
1502 |
+ |
|
|
1503 |
+ |
if !labels.is_empty() {
|
|
1504 |
+ |
let tab = option_class(layout::Selector::Tabs);
|
|
1505 |
+ |
out.push_str("<div");
|
|
1506 |
+ |
class_attr(&["selector"], opts, out);
|
|
1507 |
+ |
out.push_str(" data-selector=\"");
|
|
1508 |
+ |
out.push_str(tab);
|
|
1509 |
+ |
out.push_str("\" role=\"tablist\">");
|
|
1510 |
+ |
for (at, label) in labels.iter().enumerate() {
|
|
1511 |
+ |
let picked = current == Some(at);
|
|
1512 |
+ |
out.push_str("<button type=\"button\" class=\"");
|
|
1513 |
+ |
out.push_str(&escape(&class(tab, opts)));
|
|
1514 |
+ |
if picked {
|
|
1515 |
+ |
out.push_str(" chosen");
|
|
1516 |
+ |
}
|
|
1517 |
+ |
let _ = write!(out, "\" data-shows=\"{at}\" role=\"tab\" aria-selected=\"");
|
|
1518 |
+ |
out.push_str(if picked { "true\"" } else { "false\"" });
|
|
1519 |
+ |
out.push('>');
|
|
1520 |
+ |
out.push_str(&escape(label));
|
|
1521 |
+ |
out.push_str("</button>");
|
|
1522 |
+ |
}
|
|
1523 |
+ |
out.push_str("</div>");
|
|
1524 |
+ |
return;
|
|
1525 |
+ |
}
|
|
1526 |
+ |
|
|
1527 |
+ |
// Previous, position, next. In flow, under the content, nothing overlaid:
|
|
1528 |
+ |
// a terminal cannot honestly overlay anything, and the dot strip this
|
|
1529 |
+ |
// replaces had no form at all past a handful of children -- two of the
|
|
1530 |
+ |
// three galleries it shipped on are creator uploads of arbitrary length.
|
|
1531 |
+ |
out.push_str("<div");
|
|
1532 |
+ |
class_attr(&["showing"], opts, out);
|
|
1533 |
+ |
out.push('>');
|
|
1534 |
+ |
|
|
1535 |
+ |
out.push_str("<button type=\"button\"");
|
|
1536 |
+ |
class_attr(&["button"], opts, out);
|
|
1537 |
+ |
out.push_str(" data-shows=\"previous\">Prev</button>");
|
|
1538 |
+ |
|
|
1539 |
+ |
out.push('<');
|
|
1540 |
+ |
out.push_str("span");
|
|
1541 |
+ |
class_attr(&["showing-position"], opts, out);
|
|
1542 |
+ |
// Zero when a dismissible region is closed, which is a true statement about
|
|
1543 |
+ |
// how many of its children are showing and needs no glyph of its own.
|
|
1544 |
+ |
let _ = write!(out, ">{} / {total}</span>", current.map_or(0, |at| at + 1));
|
|
1545 |
+ |
|
|
1546 |
+ |
out.push_str("<button type=\"button\"");
|
|
1547 |
+ |
class_attr(&["button"], opts, out);
|
|
1548 |
+ |
out.push_str(" data-shows=\"next\">Next</button>");
|
|
1549 |
+ |
|
|
1550 |
+ |
out.push_str("</div>");
|
|
1551 |
+ |
}
|
|
1552 |
+ |
|
| 1428 |
1553 |
|
/// A region, and everything under it.
|
| 1429 |
1554 |
|
pub(crate) fn slot_html(
|
| 1430 |
1555 |
|
slot: &Slot,
|
| 1477 |
1602 |
|
out.push('"');
|
| 1478 |
1603 |
|
}
|
| 1479 |
1604 |
|
|
|
1605 |
+ |
// What a binder looks for, and the one attribute that says this region has
|
|
1606 |
+ |
// chrome to bind. It is not the widget's name: a script that moved between
|
|
1607 |
+ |
// a carousel's frames by matching `data-widget="carousel"` would have to be
|
|
1608 |
+ |
// written again for the next assembly, which is the per-widget code the
|
|
1609 |
+ |
// whole derivation exists to stop.
|
|
1610 |
+ |
//
|
|
1611 |
+ |
// Nothing is emitted for `Showing::All`, so the hook is present exactly
|
|
1612 |
+ |
// when there is something to bind.
|
|
1613 |
+ |
match slot.showing {
|
|
1614 |
+ |
layout::Showing::One => out.push_str(" data-showing=\"one\""),
|
|
1615 |
+ |
layout::Showing::AtMostOne => out.push_str(" data-showing=\"at-most-one\""),
|
|
1616 |
+ |
_ => {}
|
|
1617 |
+ |
}
|
|
1618 |
+ |
|
| 1480 |
1619 |
|
if matches!(slot.readiness, layout::Readiness::Pending) {
|
| 1481 |
1620 |
|
out.push_str(" aria-busy=\"true\"");
|
| 1482 |
1621 |
|
}
|
| 1488 |
1627 |
|
}
|
| 1489 |
1628 |
|
|
| 1490 |
1629 |
|
out.push('>');
|
| 1491 |
|
- |
for node in &slot.body {
|
| 1492 |
|
- |
node_html(node, morphs, opts, fills, out);
|
|
1630 |
+ |
if slot.showing.selective() {
|
|
1631 |
+ |
let current = slot.current();
|
|
1632 |
+ |
let labelled = !slot.labels().is_empty();
|
|
1633 |
+ |
|
|
1634 |
+ |
// A strip sits above the panes it opens and a counter row sits under
|
|
1635 |
+ |
// the content it counts. That is the only placement decision here, and
|
|
1636 |
+ |
// it is the folder semantic rather than a preference: a tab that came
|
|
1637 |
+ |
// after its pane would not read as the tab of it.
|
|
1638 |
+ |
if labelled {
|
|
1639 |
+ |
showing_html(slot, opts, out);
|
|
1640 |
+ |
}
|
|
1641 |
+ |
|
|
1642 |
+ |
// Each child is wrapped, because the rule that collapses the stack has
|
|
1643 |
+ |
// to have something to select and a described child emits whatever
|
|
1644 |
+ |
// element it is. The wrapper appears only here, so a region that shows
|
|
1645 |
+ |
// everything -- which is every region written before `Showing` existed
|
|
1646 |
+ |
// -- emits exactly the markup it always did.
|
|
1647 |
+ |
for (at, node) in slot.body.iter().enumerate() {
|
|
1648 |
+ |
out.push_str("<div");
|
|
1649 |
+ |
frame_class_attr(current == Some(at), opts, out);
|
|
1650 |
+ |
out.push('>');
|
|
1651 |
+ |
node_html(node, morphs, opts, fills, out);
|
|
1652 |
+ |
out.push_str("</div>");
|
|
1653 |
+ |
}
|
|
1654 |
+ |
|
|
1655 |
+ |
if !labelled {
|
|
1656 |
+ |
showing_html(slot, opts, out);
|
|
1657 |
+ |
}
|
|
1658 |
+ |
} else {
|
|
1659 |
+ |
for node in &slot.body {
|
|
1660 |
+ |
node_html(node, morphs, opts, fills, out);
|
|
1661 |
+ |
}
|
| 1493 |
1662 |
|
}
|
| 1494 |
1663 |
|
|
| 1495 |
1664 |
|
// The host's markup for this region, after whatever the description put
|