Center a collapsed list's rotated title in its column.

The title used to sit near the TOP of a tall (540px) collapsed list, with
empty space below it (.tools/collapse.png), on the desktop rule that
actually wins (.list.list-collapsed:not(.mobile-view) ...): it explicitly
pinned the title with `flex: 0 0 auto` and `align-self: flex-start` and
never grew to fill the space left after the collapse-toggle/drag-handle.
The plain (mobile-view) rule had the opposite bug - `flex: 1` fighting its
own explicit `height: auto !important`, which also kept it from growing.

Fixed both to grow (flex: 1 / flex: 1 1 auto) and center (display: flex;
align-items/justify-content: center). Centering the title text itself
inside its now-correctly-sized box needed one more fix verified with a
Playwright screenshot of a minimal reproduction: shrinking the <h2> to its
own content (so the parent's centering has something to center) rendered
NO TEXT AT ALL for a vertical writing-mode block box with width/height:
auto in a real browser. The <h2> stays 100%/100% of its parent instead and
centers its own text run with flex (display: flex; align-items/
justify-content: center on the h2 itself), which measured correctly in
both the plain and :not(.mobile-view) rule sets.

tests/collapsedListTitleCentered.test.cjs pins the fixed values in both
rule sets, comment text stripped first so an old value quoted in a
"this used to be X" explanatory comment cannot pass as evidence X is gone.

Thanks to xet7.
This commit is contained in:
Lauri Ojansivu committed 2026-09-09 02:11:23 +03:00
1 parent ad572c5b9e
commit 385f36e91a
2 files changed
+146 -13

No files matched your search

+43 -13
View File
@@ -385,8 +385,13 @@ body.mobile-mode [id^="swimlane-"] .list[style*="--list-width"]:not(.list-collap
.list.list-collapsed .list-header .list-rotated {
width: 100% !important;
max-width: 100%;
height: auto !important;
margin: 10px 0 0 0 !important;
/* No fixed height/top-margin: `flex: 1` below needs the item's main size to
come from flex-basis (0, from the shorthand), not from an explicit
`height`, or the box shrinks to its content and sits right under the
collapse-toggle/drag-handle instead of filling the rest of the column.
That is why the title used to sit near the top of a tall collapsed list
with empty space below it instead of being centered in it. */
margin: 0 !important;
position: relative !important;
overflow: hidden !important;
transform: none;
@@ -402,15 +407,24 @@ body.mobile-mode [id^="swimlane-"] .list[style*="--list-width"]:not(.list-collap
text-align: center;
overflow: hidden;
white-space: normal;
display: block !important;
/* Fills its now-stretched parent (.list-rotated, edited above) and centers
its own text run with flex: an `auto` width/height on a vertical
writing-mode block box measured EMPTY in testing (the text did not
render at all) for the equivalent :not(.mobile-view) rule further down,
so this keeps 100%/100% and centers what is INSIDE the box instead of
shrinking the box to its content. */
display: flex !important;
align-items: center;
justify-content: center;
font-size: calc(12px * var(--wekan-ui-font-scale, 1));
line-height: 1.2;
color: #333;
padding: 0;
margin: 0;
width: 100%;
max-height: 100%;
max-width: 100%;
height: 100%;
max-height: 100%;
position: static;
inset-inline-start: auto;
top: auto;
@@ -1458,31 +1472,47 @@ form.inlined-form .list-composer {
position: relative !important;
width: 100% !important;
max-width: 100% !important;
height: auto !important;
margin: 4px 0 0 0 !important;
/* This is the rule that actually wins on desktop (the extra :not() makes
it more specific than the plain/media-query versions above), and it
used to pin the title to the top-left corner of the collapsed list on
purpose (display:block, flex:0 0 auto, align-self:flex-start) - the
title should be CENTERED in the column instead, so make this a
centering flex container that grows to fill the space left after the
collapse-toggle/drag-handle, the same as the other .list-rotated rules
in this file already do. */
height: auto;
margin: 0 !important;
transform: none !important;
transform-origin: initial !important;
overflow: hidden !important;
z-index: 1 !important;
pointer-events: none !important;
display: block !important;
flex: 0 0 auto !important;
align-self: flex-start;
display: flex !important;
flex: 1 1 auto !important;
align-items: center;
justify-content: center;
}
.list.list-collapsed:not(.mobile-view) .list-header .list-rotated h2.list-header-name {
display: block !important;
/* Fills its now-stretched parent (.list-rotated, edited above) and centers
its own text run with flex rather than shrinking to content: an `auto`
width/height on a `vertical-lr` block box measured empty in testing
(the text simply did not render), so the box keeps filling 100%/100%
and centers what is INSIDE it instead. */
display: flex !important;
align-items: center;
justify-content: center;
width: 100% !important;
height: auto !important;
height: 100% !important;
margin: 0 !important;
max-height: 100% !important;
max-height: 100%;
padding: 2px 0 !important;
white-space: normal !important;
overflow: hidden !important;
transform: none !important;
writing-mode: vertical-lr !important;
text-orientation: mixed !important;
text-align: start !important;
text-align: center !important;
visibility: visible !important;
opacity: 1 !important;
}
+103
View File
@@ -0,0 +1,103 @@
'use strict';
// A collapsed list's rotated title used to sit near the TOP of the tall
// (540px) collapsed column with empty space below it, instead of being
// centered in it - visible in .tools/collapse.png. Two separate bugs, in two
// separate rule sets that both style `.list-rotated`/`.list-header-name` for
// a collapsed list (a plain rule and a more specific `:not(.mobile-view)`
// one that actually wins on desktop):
//
// 1. `.list-rotated` itself did not stretch to fill the space left after
// the collapse-toggle/drag-handle - one used `flex: 0 0 auto` outright,
// the other had an explicit `height: auto` fighting its own `flex: 1`.
// 2. The title `<h2>` filled 100%/100% of that box, which leaves the
// parent's centering nothing to center; shrinking the h2 to its content
// instead of filling 100%/100% seemed like the fix, but measured EMPTY
// in a real browser (a vertical writing-mode block box with `width:
// auto; height: auto` rendered no text at all) - so the actual fix
// keeps the h2 at 100%/100% and centers its OWN text run with flex
// instead of shrinking the box.
//
// Verified with a Playwright screenshot of a minimal reproduction against
// both the `.mobile-view` and non-mobile-view rule sets before pinning this
// as a static test (no browser here).
//
// Run: node tests/collapsedListTitleCentered.test.cjs
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const repoRoot = path.resolve(__dirname, '..');
const css = fs.readFileSync(path.join(repoRoot, 'client/components/lists/list.css'), 'utf8');
let passed = 0;
function test(name, fn) { fn(); passed += 1; console.log(' ok -', name); }
console.log('collapsedListTitleCentered:');
// Extract one `selector { ... }` block by its exact selector line, with its
// /* ... */ comments stripped - some of them explain, in prose, the OLD
// values being fixed, which must not accidentally satisfy a check looking
// for their absence.
function ruleBody(selector) {
const at = css.indexOf(`${selector} {`);
assert.ok(at !== -1, `expected to find the rule: ${selector}`);
const end = css.indexOf('\n}', at);
return css.slice(at, end).replace(/\/\*[\s\S]*?\*\//g, '');
}
test('.list-rotated grows to fill the collapsed column, in both rule sets', () => {
const plain = ruleBody('.list.list-collapsed .list-header .list-rotated');
assert.ok(/flex:\s*1\s*;/.test(plain), 'the plain rule uses flex: 1');
assert.ok(!/height:\s*auto\s*!important/.test(plain),
'and no longer fights it with an explicit height: auto !important');
const desktop = ruleBody('.list.list-collapsed:not(.mobile-view) .list-header .list-rotated');
assert.ok(/flex:\s*1 1 auto\s*!important/.test(desktop),
'the desktop (:not(.mobile-view), the one that actually wins) rule grows too');
assert.ok(!/flex:\s*0 0 auto/.test(desktop),
'the old flex: 0 0 auto (never grow) is gone');
assert.ok(!/align-self:\s*flex-start/.test(desktop),
'and the old top/start pin (align-self: flex-start) is gone');
});
test('.list-rotated centers its content, in both rule sets', () => {
for (const selector of [
'.list.list-collapsed .list-header .list-rotated',
'.list.list-collapsed:not(.mobile-view) .list-header .list-rotated',
]) {
const body = ruleBody(selector);
assert.ok(/display:\s*flex/.test(body), `${selector} is a flex container`);
assert.ok(/align-items:\s*center/.test(body) && /justify-content:\s*center/.test(body),
`${selector} centers on both axes`);
}
});
test('the title itself centers its OWN text run instead of shrinking to content', () => {
// Shrinking width/height to content measured EMPTY in a real browser for a
// vertical writing-mode block box - see the header comment. The h2 must
// stay 100%/100% and be a flex container of its own, not `display: block`.
for (const selector of [
'.list.list-collapsed .list-header .list-rotated h2.list-header-name',
'.list.list-collapsed:not(.mobile-view) .list-header .list-rotated h2.list-header-name',
]) {
const body = ruleBody(selector);
assert.ok(/display:\s*flex\s*!important/.test(body), `${selector} is display: flex`);
assert.ok(/align-items:\s*center/.test(body) && /justify-content:\s*center/.test(body),
`${selector} centers its own text run`);
assert.ok(/width:\s*100%/.test(body) && /height:\s*100%/.test(body),
`${selector} still fills its parent rather than shrinking to content`);
assert.ok(!/width:\s*auto\s*;/.test(body) && !/height:\s*auto\s*;(?!\s*!important)/.test(body),
`${selector} does not shrink its box to content`);
}
// The desktop rule's text-align used to be "start" (top/left in its
// vertical-lr writing mode), which the flex centering above replaces -
// but keep text-align: center too, for the cross-axis wrap of a long title.
const desktopH2 = ruleBody(
'.list.list-collapsed:not(.mobile-view) .list-header .list-rotated h2.list-header-name');
assert.ok(/text-align:\s*center\s*!important/.test(desktopH2),
'text-align is center, not the old start');
});
console.log(`\ncollapsedListTitleCentered: ${passed} tests passed`);