Keep a connected group's inner corners round
tests / feature (8.4) (push) Successful in 1m17s
tests / feature (8.5) (push) Successful in 1m21s
tests / browser (chrome, chromium) (push) Successful in 3m55s
tests / browser (firefox, firefox) (push) Successful in 5m34s
tests / lint (push) Successful in 1m5s
tests / browser (safari, webkit) (push) Successful in 5m54s

Connected segments and split halves wrote their full corners as
--md-sys-shape-corner-full, 9999px, beside 8px inner corners on the same
element. A box whose radii add up to more than its side has every radius
scaled by one factor, so the inner corners shrank to a hundredth of a pixel
and drew square. Full is now half the size's height (--group-full), which no
side can overflow. A browser test fails on any radius in a group or split
button larger than half its height.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
This commit is contained in:
Andreas Reinhold / reini
2026-09-13 22:46:10 +02:00
co-authored by Claude Opus 5
parent a980f5b0d0
commit b1fc0c9cfa
2 changed files with 28 additions and 12 deletions
+17 -10
View File
@@ -15,18 +15,25 @@
* *
* Split button (`<x-split-button>`): the same idea for two halves; the trailing half turns * Split button (`<x-split-button>`): the same idea for two halves; the trailing half turns
* round and its chevron turns over while its menu is open (SplitButton*Tokens). * round and its chevron turns over while its menu is open (SplitButton*Tokens).
*
* "Full" here is half the size's height (`--group-full`), never `--md-sys-shape-corner-full`'s
* 9999px. One element mixes full outer corners with small inner ones, and when a box's radii
* add up to more than its side, CSS scales every radius by the same factor: a 9999px corner
* beside an 8px one shrank the 8px one to a hundredth of a pixel, so the inner corners drew
* square.
*/ */
[data-button-group] { [data-button-group] {
--group-inner: var(--md-sys-shape-corner-sm); --group-inner: var(--md-sys-shape-corner-sm);
--group-inner-pressed: var(--md-sys-shape-corner-xs); --group-inner-pressed: var(--md-sys-shape-corner-xs);
--group-full: 1.25rem;
} }
[data-button-group][data-size='xs'] { --group-pad: 0.75rem; --group-grow: 4px; --group-inner: var(--md-sys-shape-corner-xs); --group-inner-pressed: 2px; } [data-button-group][data-size='xs'] { --group-pad: 0.75rem; --group-grow: 4px; --group-inner: var(--md-sys-shape-corner-xs); --group-inner-pressed: 2px; --group-full: 1rem; }
[data-button-group][data-size='sm'] { --group-pad: 1rem; --group-grow: 6px; } [data-button-group][data-size='sm'] { --group-pad: 1rem; --group-grow: 6px; --group-full: 1.25rem; }
[data-button-group][data-size='md'] { --group-pad: 1.5rem; --group-grow: 8px; } [data-button-group][data-size='md'] { --group-pad: 1.5rem; --group-grow: 8px; --group-full: 1.75rem; }
[data-button-group][data-size='lg'] { --group-pad: 3rem; --group-grow: 16px; --group-inner: var(--md-sys-shape-corner-lg); --group-inner-pressed: var(--md-sys-shape-corner-md); } [data-button-group][data-size='lg'] { --group-pad: 3rem; --group-grow: 16px; --group-inner: var(--md-sys-shape-corner-lg); --group-inner-pressed: var(--md-sys-shape-corner-md); --group-full: 3rem; }
[data-button-group][data-size='xl'] { --group-pad: 4rem; --group-grow: 20px; --group-inner: var(--md-sys-shape-corner-lg-increased); --group-inner-pressed: var(--md-sys-shape-corner-lg); } [data-button-group][data-size='xl'] { --group-pad: 4rem; --group-grow: 20px; --group-inner: var(--md-sys-shape-corner-lg-increased); --group-inner-pressed: var(--md-sys-shape-corner-lg); --group-full: 4.25rem; }
[data-button-group='standard'] > :not([data-icon-button]):active:not(:disabled, [aria-disabled='true']) { [data-button-group='standard'] > :not([data-icon-button]):active:not(:disabled, [aria-disabled='true']) {
padding-inline: calc(var(--group-pad) + var(--group-grow)); padding-inline: calc(var(--group-pad) + var(--group-grow));
@@ -60,19 +67,19 @@
[data-button-group='connected'] > :is([aria-pressed='true'], :has(:checked)), [data-button-group='connected'] > :is([aria-pressed='true'], :has(:checked)),
[data-split='trailing'][aria-expanded='true'] { [data-split='trailing'][aria-expanded='true'] {
--group-corner: var(--md-sys-shape-corner-full); --group-corner: var(--group-full);
} }
[data-button-group='connected'] > :first-child, [data-button-group='connected'] > :first-child,
[data-split='leading'] { [data-split='leading'] {
border-start-start-radius: var(--md-sys-shape-corner-full); border-start-start-radius: var(--group-full);
border-end-start-radius: var(--md-sys-shape-corner-full); border-end-start-radius: var(--group-full);
} }
[data-button-group='connected'] > :last-child, [data-button-group='connected'] > :last-child,
[data-split='trailing'] { [data-split='trailing'] {
border-start-end-radius: var(--md-sys-shape-corner-full); border-start-end-radius: var(--group-full);
border-end-end-radius: var(--md-sys-shape-corner-full); border-end-end-radius: var(--group-full);
} }
[data-split='trailing'] svg { [data-split='trailing'] svg {
+11 -2
View File
@@ -218,7 +218,7 @@ it('moves a connected group\'s choice with the arrow keys', function () {
$page->keys(':focus', 'ArrowLeft') $page->keys(':focus', 'ArrowLeft')
->assertScript($checked('dark')) ->assertScript($checked('dark'))
->assertScript("getComputedStyle(document.querySelector('#buttons input[value=\"dark\"]').parentElement).borderTopLeftRadius === '9999px'"); ->assertScript("(el => getComputedStyle(el).borderTopLeftRadius === (el.offsetHeight / 2) + 'px')(document.querySelector('#buttons input[value=\"dark\"]').parentElement)");
}); });
it('rounds a split button\'s trailing half while its menu is open', function () { it('rounds a split button\'s trailing half while its menu is open', function () {
@@ -227,7 +227,16 @@ it('rounds a split button\'s trailing half while its menu is open', function ()
showcase() showcase()
->click('[data-split="trailing"] >> nth=0') ->click('[data-split="trailing"] >> nth=0')
->assertScript("{$trailing}.getAttribute('aria-expanded') === 'true'") ->assertScript("{$trailing}.getAttribute('aria-expanded') === 'true'")
->assertScript("getComputedStyle({$trailing}).borderTopLeftRadius === '9999px'"); ->assertScript("getComputedStyle({$trailing}).borderTopLeftRadius === ({$trailing}.offsetHeight / 2) + 'px'");
});
it('keeps a connected segment\'s small inner corners, which a 9999px outer corner would scale away', function () {
// When a box's radii add up to more than a side, CSS shrinks every radius by the same
// factor: a full corner written as 9999px drew the 8px inner corners square. Every radius
// in a connected group or a split button stays within half its height, so none is scaled.
showcase()
->assertScript("[...document.querySelectorAll('[data-button-group=\"connected\"] > *, [data-split]')].every((el) => { const cs = getComputedStyle(el); const half = el.offsetHeight / 2 + 0.5; return el.offsetHeight === 0 || ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomLeftRadius', 'borderBottomRightRadius'].every((corner) => parseFloat(cs[corner]) <= half); })")
->assertScript("(el => getComputedStyle(el).borderTopRightRadius === '8px')(document.querySelector('#buttons input[name=\"showcase-theme\"][value=\"light\"]').parentElement)");
}); });
it('turns the FAB into a close button while its menu is open', function () { it('turns the FAB into a close button while its menu is open', function () {