| 2014 |
2014 |
|
/// branch above it is hiding it. The row is emitted regardless and marked
|
| 2015 |
2015 |
|
/// `hidden`, which is what lets `outline.js` open a branch without asking the
|
| 2016 |
2016 |
|
/// app for rows the document is already holding.
|
| 2017 |
|
- |
fn row_html(row: &Row, folded: bool, opts: &Emit, out: &mut String) {
|
| 2018 |
|
- |
let mut classes = vec!["row"];
|
|
2017 |
+ |
/// How a row is drawn: as a list item, or as a row of a declared table.
|
|
2018 |
+ |
///
|
|
2019 |
+ |
/// The 2026-09-05 collapse made a list row and a table row one type, and this
|
|
2020 |
+ |
/// is what is genuinely left of the difference. It is not a preference: the
|
|
2021 |
+ |
/// element, the class names and the gutter's wrapping are a contract with
|
|
2022 |
+ |
/// makeover's stylesheet and with the hosts that bind to them, so the two looks
|
|
2023 |
+ |
/// keep emitting exactly what they emitted before. What they no longer do is
|
|
2024 |
+ |
/// keep two copies of the walk that decides it.
|
|
2025 |
+ |
///
|
|
2026 |
+ |
/// Which look a container takes is the container's, not the row's. Today that
|
|
2027 |
+ |
/// is `Node::List` against `Node::Table`; once the grammar is one grammar
|
|
2028 |
+ |
/// (wave `bd8d5ce7`) it becomes whether the columns carry headings.
|
|
2029 |
+ |
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
2030 |
+ |
enum RowLook {
|
|
2031 |
+ |
/// A list item. Flows its cells as spans on one wrapped line.
|
|
2032 |
+ |
List,
|
|
2033 |
+ |
/// A row of a declared table. Emits a cell per column, aligned by
|
|
2034 |
+ |
/// `display: table`.
|
|
2035 |
+ |
Table,
|
|
2036 |
+ |
}
|
|
2037 |
+ |
|
|
2038 |
+ |
impl RowLook {
|
|
2039 |
+ |
/// The opening tag, without its attributes.
|
|
2040 |
+ |
const fn open_tag(self) -> &'static str {
|
|
2041 |
+ |
match self {
|
|
2042 |
+ |
Self::List => "<li",
|
|
2043 |
+ |
Self::Table => "<div role=\"row\"",
|
|
2044 |
+ |
}
|
|
2045 |
+ |
}
|
|
2046 |
+ |
|
|
2047 |
+ |
/// The closing tag.
|
|
2048 |
+ |
const fn close_tag(self) -> &'static str {
|
|
2049 |
+ |
match self {
|
|
2050 |
+ |
Self::List => "</li>",
|
|
2051 |
+ |
Self::Table => "</div>",
|
|
2052 |
+ |
}
|
|
2053 |
+ |
}
|
|
2054 |
+ |
|
|
2055 |
+ |
/// The class every row of this look carries.
|
|
2056 |
+ |
const fn base_class(self) -> &'static str {
|
|
2057 |
+ |
match self {
|
|
2058 |
+ |
Self::List => "row",
|
|
2059 |
+ |
Self::Table => "table-row",
|
|
2060 |
+ |
}
|
|
2061 |
+ |
}
|
|
2062 |
+ |
|
|
2063 |
+ |
/// The class for the row the detail side is showing.
|
|
2064 |
+ |
const fn current_class(self) -> &'static str {
|
|
2065 |
+ |
match self {
|
|
2066 |
+ |
Self::List => "row-current",
|
|
2067 |
+ |
Self::Table => "table-row-current",
|
|
2068 |
+ |
}
|
|
2069 |
+ |
}
|
|
2070 |
+ |
|
|
2071 |
+ |
/// The class for a row in a selection already in force.
|
|
2072 |
+ |
const fn chosen_class(self) -> &'static str {
|
|
2073 |
+ |
match self {
|
|
2074 |
+ |
Self::List => "row-chosen",
|
|
2075 |
+ |
Self::Table => "table-row-chosen",
|
|
2076 |
+ |
}
|
|
2077 |
+ |
}
|
|
2078 |
+ |
|
|
2079 |
+ |
/// The class for a ticked row.
|
|
2080 |
+ |
const fn selected_class(self) -> &'static str {
|
|
2081 |
+ |
match self {
|
|
2082 |
+ |
Self::List => "row-selected",
|
|
2083 |
+ |
Self::Table => "table-row-selected",
|
|
2084 |
+ |
}
|
|
2085 |
+ |
}
|
|
2086 |
+ |
}
|
|
2087 |
+ |
|
|
2088 |
+ |
/// The classes a row carries, in the order this emitter has always written
|
|
2089 |
+ |
/// them.
|
|
2090 |
+ |
///
|
|
2091 |
+ |
/// Shared by both looks since 2026-09-05. `row-nested` and `row-branch` were
|
|
2092 |
+ |
/// already spelled the same on both, which is the tell that the two functions
|
|
2093 |
+ |
/// were one function.
|
|
2094 |
+ |
fn row_classes(row: &Row, look: RowLook) -> Vec<&'static str> {
|
|
2095 |
+ |
let mut classes = vec![look.base_class()];
|
| 2019 |
2096 |
|
if row.depth.is_nested() {
|
| 2020 |
2097 |
|
classes.push("row-nested");
|
| 2021 |
2098 |
|
}
|
| 2023 |
2100 |
|
classes.push("row-branch");
|
| 2024 |
2101 |
|
}
|
| 2025 |
2102 |
|
if row.current {
|
| 2026 |
|
- |
classes.push("row-current");
|
|
2103 |
+ |
// `1894e95d`. A live selection, which is neither of the two below it:
|
|
2104 |
+ |
// not the app's pointer at one row and not a staged tick. Its own class
|
|
2105 |
+ |
// rather than reusing the tick's, because the tick's class is what a
|
|
2106 |
+ |
// commit control gathers by and these rows have nothing to commit.
|
|
2107 |
+ |
classes.push(look.current_class());
|
| 2027 |
2108 |
|
}
|
| 2028 |
|
- |
// `1894e95d`. A live selection, which is neither of the two below it: not
|
| 2029 |
|
- |
// the app's pointer at one row and not a staged tick. `row-chosen` rather
|
| 2030 |
|
- |
// than reusing `row-selected`, because the tick's class is what a commit
|
| 2031 |
|
- |
// control gathers by and these rows have nothing to commit.
|
| 2032 |
2109 |
|
if row.chosen == Some(true) {
|
| 2033 |
|
- |
classes.push("row-chosen");
|
|
2110 |
+ |
classes.push(look.chosen_class());
|
| 2034 |
2111 |
|
}
|
| 2035 |
2112 |
|
if row.selected == Some(true) {
|
| 2036 |
|
- |
classes.push("row-selected");
|
|
2113 |
+ |
classes.push(look.selected_class());
|
| 2037 |
2114 |
|
}
|
|
2115 |
+ |
classes
|
|
2116 |
+ |
}
|
| 2038 |
2117 |
|
|
| 2039 |
|
- |
out.push_str("<li");
|
| 2040 |
|
- |
class_attr(&classes, opts, out);
|
| 2041 |
|
- |
// What `Replaces::Enclosing` targets. Emitted on every row rather than only
|
| 2042 |
|
- |
// on rows that happen to hold such an act: the act is inside the row, the
|
| 2043 |
|
- |
// row cannot see it, and a walk to find out would cost more than the
|
| 2044 |
|
- |
// attribute.
|
|
2118 |
+ |
/// The row's own attributes, from `data-row` to the `>` that closes the tag.
|
|
2119 |
+ |
///
|
|
2120 |
+ |
/// Shared by both looks. Every attribute here was written twice before
|
|
2121 |
+ |
/// 2026-09-05, in two functions that had drifted: the table row emitted
|
|
2122 |
+ |
/// `data-value` and `data-change` and the list row did not, which is recorded
|
|
2123 |
+ |
/// as a finding on wave `f0dac2d6` rather than silently fixed here, because
|
|
2124 |
+ |
/// changing what a served screen emits is not a refactor.
|
|
2125 |
+ |
fn row_attrs_html(row: &Row, look: RowLook, folded: bool, out: &mut String) {
|
|
2126 |
+ |
// `Replaces::Enclosing`'s target.
|
| 2045 |
2127 |
|
out.push_str(" data-row");
|
| 2046 |
|
- |
// The address a reader links to, which is the one row member that is an
|
| 2047 |
|
- |
// `id` rather than a `data-` attribute: `Row::address` is documented as
|
| 2048 |
|
- |
// unique in the document, which is exactly what an id promises and what
|
| 2049 |
|
- |
// `data-value` beside it deliberately does not.
|
|
2128 |
+ |
if look == RowLook::Table {
|
|
2129 |
+ |
// What the app calls this row, unique within its table. A list row
|
|
2130 |
+ |
// carries the same fact and spends it on its checkbox's `value=`
|
|
2131 |
+ |
// instead; see the finding on wave `f0dac2d6`.
|
|
2132 |
+ |
if let Some(value) = &row.value {
|
|
2133 |
+ |
out.push_str(" data-value=\"");
|
|
2134 |
+ |
escape_into(value, out);
|
|
2135 |
+ |
out.push('"');
|
|
2136 |
+ |
}
|
|
2137 |
+ |
}
|
|
2138 |
+ |
// The address, which is the document's name for the row rather than the
|
|
2139 |
+ |
// app's, and so is the one that gets an `id`.
|
| 2050 |
2140 |
|
if let Some(address) = &row.address {
|
| 2051 |
2141 |
|
out.push_str(" id=\"");
|
| 2052 |
2142 |
|
escape_into(address, out);
|
| 2053 |
2143 |
|
out.push('"');
|
| 2054 |
2144 |
|
}
|
| 2055 |
|
- |
// The indent, as a number the stylesheet multiplies by whatever a step is
|
| 2056 |
|
- |
// worth. Emitted as a custom property for `--track-at`'s reason: the rule
|
| 2057 |
|
- |
// stays static while the data moves, and how wide a level is stays a size
|
| 2058 |
|
- |
// this crate does not name.
|
| 2059 |
|
- |
//
|
| 2060 |
|
- |
// `data-depth` beside it because `outline.js` reads the depth back to work
|
| 2061 |
|
- |
// out what a branch covers, and reading a style property to do arithmetic
|
| 2062 |
|
- |
// is not what a style property is.
|
|
2145 |
+ |
if look == RowLook::Table {
|
|
2146 |
+ |
// Which side of a change this line is on, when the table is a diff.
|
|
2147 |
+ |
// `19d7602d`. An attribute rather than a class, for `tone_attr`'s
|
|
2148 |
+ |
// reason: makeover keys every intent off a data attribute, so a host
|
|
2149 |
+ |
// that has never heard of a diff still gets the tones it styles.
|
|
2150 |
+ |
//
|
|
2151 |
+ |
// `None` writes nothing, which keeps every ordinary table from reading
|
|
2152 |
+ |
// as a diff whose lines are all context.
|
|
2153 |
+ |
if let Some(change) = row.change {
|
|
2154 |
+ |
out.push_str(" data-change=\"");
|
|
2155 |
+ |
out.push_str(match change {
|
|
2156 |
+ |
layout::Change::Added => "added",
|
|
2157 |
+ |
layout::Change::Removed => "removed",
|
|
2158 |
+ |
// Including a kind added to this `#[non_exhaustive]` axis that
|
|
2159 |
+ |
// this renderer has not learned: an unknown side reads as an
|
|
2160 |
+ |
// unchanged line, which draws the text and loses only the tint.
|
|
2161 |
+ |
_ => "context",
|
|
2162 |
+ |
});
|
|
2163 |
+ |
out.push_str("\" data-tone=\"");
|
|
2164 |
+ |
out.push_str(layout::Intent::token(change));
|
|
2165 |
+ |
out.push('"');
|
|
2166 |
+ |
}
|
|
2167 |
+ |
}
|
| 2063 |
2168 |
|
if row.depth.is_nested() {
|
| 2064 |
2169 |
|
let _ = write!(
|
| 2065 |
2170 |
|
out,
|
| 2068 |
2173 |
|
level = row.depth.level + 1
|
| 2069 |
2174 |
|
);
|
| 2070 |
2175 |
|
}
|
| 2071 |
|
- |
// A row under a shut branch. In the document and out of the picture: a
|
| 2072 |
|
- |
// reader who opens the branch gets it back with no request, and a screen
|
| 2073 |
|
- |
// reader is told the same thing the eye is.
|
| 2074 |
2176 |
|
if folded {
|
| 2075 |
2177 |
|
out.push_str(" hidden");
|
| 2076 |
2178 |
|
}
|
| 2077 |
2179 |
|
if row.current {
|
| 2078 |
|
- |
// `current` is what the detail side is showing: a current item within a
|
| 2079 |
|
- |
// set rather than a pressed control, and rather than anything the user
|
| 2080 |
|
- |
// ticked. That distinction is why the description carries two fields.
|
| 2081 |
2180 |
|
out.push_str(" aria-current=\"true\"");
|
| 2082 |
2181 |
|
}
|
| 2083 |
|
- |
// `aria-selected` and not `aria-current`, which is the one place the two
|
| 2084 |
|
- |
// ARIA names line up with the vocabulary's own distinction: a screen reader
|
| 2085 |
|
- |
// says "selected" of a row that is part of the set being acted on and
|
| 2086 |
|
- |
// "current" of the one the view is showing. A row that can be chosen and is
|
| 2087 |
|
- |
// not says so rather than staying silent, because "one of eleven selected"
|
| 2088 |
|
- |
// is only audible if the unselected ones are announced too.
|
| 2089 |
2182 |
|
if let Some(chosen) = row.chosen {
|
| 2090 |
2183 |
|
out.push_str(if chosen {
|
| 2091 |
2184 |
|
" aria-selected=\"true\""
|
| 2093 |
2186 |
|
" aria-selected=\"false\""
|
| 2094 |
2187 |
|
});
|
| 2095 |
2188 |
|
}
|
|
2189 |
+ |
}
|
|
2190 |
+ |
|
|
2191 |
+ |
fn row_html(row: &Row, folded: bool, opts: &Emit, out: &mut String) {
|
|
2192 |
+ |
let classes = row_classes(row, RowLook::List);
|
|
2193 |
+ |
out.push_str(RowLook::List.open_tag());
|
|
2194 |
+ |
class_attr(&classes, opts, out);
|
|
2195 |
+ |
row_attrs_html(row, RowLook::List, folded, out);
|
| 2096 |
2196 |
|
out.push('>');
|
| 2097 |
2197 |
|
|
| 2098 |
2198 |
|
// The disclosure, when the row is a branch. Before the tick and before the
|
| 2181 |
2281 |
|
.take_while(|cell| cell.key == key && cell.room() == flow)
|
| 2182 |
2282 |
|
.count();
|
| 2183 |
2283 |
|
let (group, tail) = rest.split_at(taken);
|
|
2284 |
+ |
// A list draws over the default column set, so every cell in one is
|
|
2285 |
+ |
// keyed by role. A cell keyed to a declared column has reached a list
|
|
2286 |
+ |
// row, which is a description error rather than something to render:
|
|
2287 |
+ |
// the table constructors (`Row::cells`, `at`, `cell`) produce those
|
|
2288 |
+ |
// keys and a list row is built by `Row::new` and its siblings.
|
|
2289 |
+ |
//
|
|
2290 |
+ |
// Loud in debug and benign in release, which is the bargain
|
|
2291 |
+ |
// `Table::row`'s two assertions already strike here (`d41d00a`): a
|
|
2292 |
+ |
// panic in a description is worse than a row that draws its text under
|
|
2293 |
+ |
// the wrong style.
|
|
2294 |
+ |
debug_assert!(
|
|
2295 |
+ |
matches!(key, quasi_router::CellKey::Role(_)),
|
|
2296 |
+ |
"a cell keyed to a declared column reached a list row, which draws \
|
|
2297 |
+ |
over the default column set and has no column to style it from. \
|
|
2298 |
+ |
Key: {key:?}",
|
|
2299 |
+ |
);
|
| 2184 |
2300 |
|
let role = match &key {
|
| 2185 |
2301 |
|
quasi_router::CellKey::Role(role) => *role,
|
| 2186 |
|
- |
// A declared column has no role; it styles from its column, which
|
| 2187 |
|
- |
// this walk does not hold. Wave 1a is where that is done properly.
|
| 2188 |
2302 |
|
_ => quasi_router::layout::RowPart::Primary,
|
| 2189 |
2303 |
|
};
|
| 2190 |
2304 |
|
row_part_html(row, role, flow, group, opts, out);
|
| 2207 |
2321 |
|
out.push_str("</div>");
|
| 2208 |
2322 |
|
}
|
| 2209 |
2323 |
|
|
| 2210 |
|
- |
out.push_str("</li>");
|
|
2324 |
+ |
out.push_str(RowLook::List.close_tag());
|
| 2211 |
2325 |
|
}
|
| 2212 |
2326 |
|
|
| 2213 |
2327 |
|
/// One run of consecutive row parts sharing a role.
|
| 2450 |
2564 |
|
opts: &Emit,
|
| 2451 |
2565 |
|
out: &mut String,
|
| 2452 |
2566 |
|
) {
|
| 2453 |
|
- |
let mut classes = vec!["table-row"];
|
| 2454 |
|
- |
if cells.depth.is_nested() {
|
| 2455 |
|
- |
classes.push("row-nested");
|
| 2456 |
|
- |
}
|
| 2457 |
|
- |
if cells.open.is_some() {
|
| 2458 |
|
- |
classes.push("row-branch");
|
| 2459 |
|
- |
}
|
| 2460 |
|
- |
if cells.current {
|
| 2461 |
|
- |
// `table-row-current`, matching `row-current` on a list row. It was
|
| 2462 |
|
- |
// `table-row-selected` while the field was, so the class said one thing
|
| 2463 |
|
- |
// and the `aria-current` two lines down said the other.
|
| 2464 |
|
- |
classes.push("table-row-current");
|
| 2465 |
|
- |
}
|
| 2466 |
|
- |
|
| 2467 |
|
- |
// `table-row-chosen`, shadowing `row-chosen` the way `table-row-current`
|
| 2468 |
|
- |
// shadows `row-current`. See the list row's own comment. `1894e95d`.
|
| 2469 |
|
- |
if cells.chosen == Some(true) {
|
| 2470 |
|
- |
classes.push("table-row-chosen");
|
| 2471 |
|
- |
}
|
| 2472 |
|
- |
|
| 2473 |
|
- |
if cells.selected == Some(true) {
|
| 2474 |
|
- |
classes.push("table-row-selected");
|
| 2475 |
|
- |
}
|
| 2476 |
|
- |
|
| 2477 |
|
- |
out.push_str("<div role=\"row\"");
|
|
2567 |
+ |
let classes = row_classes(cells, RowLook::Table);
|
|
2568 |
+ |
out.push_str(RowLook::Table.open_tag());
|
| 2478 |
2569 |
|
class_attr(&classes, opts, out);
|
| 2479 |
|
- |
// `Replaces::Enclosing`'s target, matching the list row's.
|
| 2480 |
|
- |
out.push_str(" data-row");
|
| 2481 |
|
- |
// What the app calls this row. `Cells::identified` is documented as the
|
| 2482 |
|
- |
// identity a renderer answers with when a host asks which row it is
|
| 2483 |
|
- |
// looking at, and this emitter only wrote it as a checkbox value, so a row
|
| 2484 |
|
- |
// that carried an identity and no tick reached the document carrying
|
| 2485 |
|
- |
// nothing -- an identity the description had said and no host could read
|
| 2486 |
|
- |
// back. MNW's file view found it: its rows are lines, and a line's identity
|
| 2487 |
|
- |
// is what `#L42` addresses.
|
| 2488 |
|
- |
//
|
| 2489 |
|
- |
// `data-value` rather than `id`, because a row identity is unique within
|
| 2490 |
|
- |
// its table and an id is unique in a document: two tables of the same
|
| 2491 |
|
- |
// things on one screen is an ordinary description and two elements under
|
| 2492 |
|
- |
// one id is markup a browser resolves by picking the first.
|
| 2493 |
|
- |
if let Some(value) = &cells.value {
|
| 2494 |
|
- |
out.push_str(" data-value=\"");
|
| 2495 |
|
- |
escape_into(value, out);
|
| 2496 |
|
- |
out.push('"');
|
| 2497 |
|
- |
}
|
| 2498 |
|
- |
// And the address, which is the case that comment names. A value is the
|
| 2499 |
|
- |
// app's name for the row and an id is the document's, so the one member
|
| 2500 |
|
- |
// that promises document uniqueness is the one that gets an id.
|
| 2501 |
|
- |
if let Some(address) = &cells.address {
|
| 2502 |
|
- |
out.push_str(" id=\"");
|
| 2503 |
|
- |
escape_into(address, out);
|
| 2504 |
|
- |
out.push('"');
|
| 2505 |
|
- |
}
|
| 2506 |
|
- |
// Which side of a change this line is on, when the table is a diff.
|
| 2507 |
|
- |
// `19d7602d`. An attribute rather than a class, for `tone_attr`'s reason:
|
| 2508 |
|
- |
// makeover keys every intent off a data attribute, and the value is the
|
| 2509 |
|
- |
// token `Change`'s `Intent` impl names, so a host that has never heard of a
|
| 2510 |
|
- |
// diff still gets the success and danger it already styles.
|
| 2511 |
|
- |
//
|
| 2512 |
|
- |
// `None` writes nothing at all, which is what keeps every ordinary table in
|
| 2513 |
|
- |
// the tree from reading as a diff whose lines are all context.
|
| 2514 |
|
- |
if let Some(change) = cells.change {
|
| 2515 |
|
- |
out.push_str(" data-change=\"");
|
| 2516 |
|
- |
out.push_str(match change {
|
| 2517 |
|
- |
layout::Change::Added => "added",
|
| 2518 |
|
- |
layout::Change::Removed => "removed",
|
| 2519 |
|
- |
// Including a kind added to this `#[non_exhaustive]` axis that this
|
| 2520 |
|
- |
// renderer has not learned: an unknown side reads as an unchanged
|
| 2521 |
|
- |
// line, which draws the text and loses only the tint.
|
| 2522 |
|
- |
_ => "context",
|
| 2523 |
|
- |
});
|
| 2524 |
|
- |
out.push_str("\" data-tone=\"");
|
| 2525 |
|
- |
out.push_str(layout::Intent::token(change));
|
| 2526 |
|
- |
out.push('"');
|
| 2527 |
|
- |
}
|
| 2528 |
|
- |
// The indent and the fold, in the list row's spelling and for its reasons.
|
| 2529 |
|
- |
// The indent lands on the row rather than on its first cell: `display:
|
| 2530 |
|
- |
// table` gives the cell no box of its own to pad without moving the column
|
| 2531 |
|
- |
// edge for every row under it.
|
| 2532 |
|
- |
if cells.depth.is_nested() {
|
| 2533 |
|
- |
let _ = write!(
|
| 2534 |
|
- |
out,
|
| 2535 |
|
- |
" data-depth=\"{depth}\" style=\"--row-depth:{depth}\" aria-level=\"{level}\"",
|
| 2536 |
|
- |
depth = cells.depth.level,
|
| 2537 |
|
- |
level = cells.depth.level + 1
|
| 2538 |
|
- |
);
|
| 2539 |
|
- |
}
|
| 2540 |
|
- |
if folded {
|
| 2541 |
|
- |
out.push_str(" hidden");
|
| 2542 |
|
- |
}
|
| 2543 |
|
- |
if cells.current {
|
| 2544 |
|
- |
out.push_str(" aria-current=\"true\"");
|
| 2545 |
|
- |
}
|
| 2546 |
|
- |
if let Some(chosen) = cells.chosen {
|
| 2547 |
|
- |
out.push_str(if chosen {
|
| 2548 |
|
- |
" aria-selected=\"true\""
|
| 2549 |
|
- |
} else {
|
| 2550 |
|
- |
" aria-selected=\"false\""
|
| 2551 |
|
- |
});
|
| 2552 |
|
- |
}
|
|
2570 |
+ |
row_attrs_html(cells, RowLook::Table, folded, out);
|
|
2571 |
+ |
// The activate route, which only a table row carries in its opening tag: a
|
|
2572 |
+ |
// list row's is written by its run instead. Kept here rather than moved
|
|
2573 |
+ |
// into the shared attrs because the two really do differ.
|
| 2553 |
2574 |
|
if let Some(action) = &cells.activate {
|
| 2554 |
|
- |
// Any control in any cell, which is acts and links plus the chips that
|
| 2555 |
|
- |
// answer a click. A badge is not one and does not earn the filter. The
|
| 2556 |
|
- |
// walk moved onto `Cell` with the run, so this reads the description's
|
| 2557 |
|
- |
// answer rather than recomputing it from members.
|
| 2558 |
2575 |
|
let carries_control = cells.cells.iter().any(Cell::carries_control);
|
| 2559 |
2576 |
|
let fires = if carries_control || cells.selected.is_some() {
|
| 2560 |
2577 |
|
Fires::ClickBeside
|
| 2677 |
2694 |
|
out.push_str("</div>");
|
| 2678 |
2695 |
|
}
|
| 2679 |
2696 |
|
|
| 2680 |
|
- |
out.push_str("</div>");
|
|
2697 |
+ |
out.push_str(RowLook::Table.close_tag());
|
| 2681 |
2698 |
|
}
|
| 2682 |
2699 |
|
|
| 2683 |
2700 |
|
/// The class saying when a region member drops.
|