Give a closing standard side sheet's room back as it leaves
From `expanded` a standard side sheet sits in the layout beside the content, and the view took it out of the layout the moment it closed: `data-md-drawer-collapsed` (`display: none` on the root) followed `! open` at once, so its fade was never seen and the content beside it took the sheet's width, and the gap, in one jump — in every engine. M3's side sheets: opening a standard sheet shrinks the body beside it, and closing gives the room back. drawer.css: the standard root is a clipping flex box whose `inline-size` springs between none and the sheet's width, with a negative margin as wide as its flex parent's gap (`--md-drawer-gap`), on the sheet's own exit and entry timings; the sheet keeps its width at the root's far edge, so it is uncovered from its inner edge while it fades. drawer.blade.php: `settle()`, from `x-effect`, measures the gap on each change and holds the collapse (`closing`) for the root's and the sheet's closing durations, read a frame on; reopening lets a pending end go by. The binding reads `collapsed`, which only `settle()` and the window's width write: a binding reading `open` ran before `settle()` in the same flush and, already queued, did not run again once `closing` changed. ContainmentTest closes the showcase's sheet at 1000px and samples, in the page, for the root part-way to none while the column beside it has grown part of the way, then checks the collapse, the column at the row's full width, and a reopen part-way through the exit ending fully open; it fails on the previous code in Chrome, Firefox and Safari. OverlayTest pins the root's declarations and the view's bindings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
db6023bf80
commit
99c18e01d8
@@ -509,6 +509,62 @@ it('is a standard side sheet from 840px, without a scrim or a focus trap, and th
|
||||
->assertScript("document.querySelector('header').closest('[aria-hidden=\"true\"]') !== null");
|
||||
});
|
||||
|
||||
it('gives a closing standard side sheet\'s room back to the content beside it as it leaves, from 840px', function () {
|
||||
$sheet = "document.querySelector('#containment aside[data-md-side]')";
|
||||
$root = "{$sheet}.parentElement";
|
||||
$column = "{$root}.previousElementSibling";
|
||||
$toggle = "Array.from(document.querySelectorAll('#containment button')).find((button) => button.textContent.trim() === 'Show or hide the filters')";
|
||||
|
||||
// The example starts open, and grows in once Alpine opens it: its entry has run when the sheet
|
||||
// stands at its full 400px and fully shown, with nothing on it still animating.
|
||||
$page = containment()->resize(1000, 800)
|
||||
->assertScript("{$root}.hasAttribute('data-md-open') && Math.round({$root}.getBoundingClientRect().width) === 400 && getComputedStyle({$sheet}).opacity === '1' && {$root}.getAnimations({ subtree: true }).length === 0");
|
||||
|
||||
// Closed and sampled in one round trip: the sheet's root caught part-way between its width
|
||||
// and none, still in the layout, while the column beside it has grown part of the way — not
|
||||
// the whole sheet and its gap handed back in one jump at the end.
|
||||
$midExit = $page->script(<<<JS
|
||||
(async () => {
|
||||
const root = {$root}
|
||||
const column = {$column}
|
||||
const open = { root: root.getBoundingClientRect().width, column: column.getBoundingClientRect().width, row: root.parentElement.getBoundingClientRect().width }
|
||||
{$toggle}.click()
|
||||
|
||||
for (let i = 0; i < 80; i++) {
|
||||
const width = root.getBoundingClientRect().width
|
||||
const columnWidth = column.getBoundingClientRect().width
|
||||
|
||||
if (! root.hasAttribute('data-md-drawer-collapsed') && getComputedStyle(root).display !== 'none' && width > 1 && width < open.root - 1 && columnWidth > open.column + 1 && columnWidth < open.row - 1) return true
|
||||
await new Promise((resolve) => setTimeout(resolve, 5))
|
||||
}
|
||||
return false
|
||||
})()
|
||||
JS);
|
||||
|
||||
expect($midExit)->toBeTrue();
|
||||
|
||||
// Settled: out of the layout, and the column is the whole row, gap included.
|
||||
$page->assertScript("{$root}.hasAttribute('data-md-drawer-collapsed') && getComputedStyle({$root}).display === 'none'")
|
||||
->assertScript("Math.abs({$column}.getBoundingClientRect().width - {$root}.parentElement.getBoundingClientRect().width) < 1");
|
||||
|
||||
// Reopened part-way through its exit, it ends open, not collapsed. Each script stays well under
|
||||
// the browser plugin's one-second call timeout, past which it runs the script a second time.
|
||||
$page->script("{$toggle}.click()");
|
||||
$page->wait(0.6)
|
||||
->assertScript("{$root}.hasAttribute('data-md-open') && {$root}.getAnimations({ subtree: true }).length === 0");
|
||||
|
||||
$page->script(<<<JS
|
||||
(async () => {
|
||||
{$toggle}.click()
|
||||
await new Promise((resolve) => setTimeout(resolve, 60))
|
||||
{$toggle}.click()
|
||||
})()
|
||||
JS);
|
||||
|
||||
$page->wait(0.9)
|
||||
->assertScript("{$root}.hasAttribute('data-md-open') && ! {$root}.hasAttribute('data-md-drawer-collapsed') && Math.abs({$root}.getBoundingClientRect().width - 400) < 1 && getComputedStyle({$sheet}).opacity === '1'");
|
||||
});
|
||||
|
||||
it('opens a row\'s opener from a press anywhere on the row, but not from its own buttons', function () {
|
||||
$page = containment();
|
||||
|
||||
|
||||
@@ -194,6 +194,8 @@ it('puts a side sheet\'s actions on the left, in M3\'s 72dp row', function () {
|
||||
it('slides a side sheet in from either edge', function () {
|
||||
expect((string) $this->blade('<x-drawer title="Details" with-close-button>Body</x-drawer>'))
|
||||
->toContain('x-trap.inert.noscroll="open && ! wide"')
|
||||
->toContain('x-bind:data-md-drawer-collapsed="collapsed ? \'\' : null"')
|
||||
->toContain('x-effect="settle(open)"')
|
||||
->toContain('data-md-side="end"')
|
||||
->toContain('data-md-drawer-sheet')
|
||||
->toContain('role="dialog"')
|
||||
@@ -229,6 +231,8 @@ it('is M3\'s standard side sheet from expanded, and the modal one below', functi
|
||||
->toContain('--sheet-width: min(400px, 400px)')
|
||||
->toContain("matchMedia('(width >= 840px)')")
|
||||
->toContain('x-trap.inert.noscroll="open && ! wide"')
|
||||
->toContain('x-bind:data-md-drawer-collapsed="collapsed ? \'\' : null"')
|
||||
->toContain('x-effect="settle(open)"')
|
||||
->toContain('aria-label="Close"')
|
||||
->and((string) $this->blade('<x-drawer standard side="start" width="30rem">Body</x-drawer>'))
|
||||
->toContain('data-md-side="start"')
|
||||
@@ -241,6 +245,18 @@ it('is M3\'s standard side sheet from expanded, and the modal one below', functi
|
||||
|
||||
expect($css->has('[data-md-drawer][data-md-standard]', $expanded))->toBeTrue()
|
||||
->and($css->declarations('[data-md-drawer][data-md-standard][data-md-drawer-collapsed]', $expanded))->toBe(['display' => 'none'])
|
||||
// Closing gives the room back as the sheet leaves (M3's side sheets, "Adaptive"): the root's
|
||||
// inline size and the parent's gap spring to none, and the collapse waits for that exit.
|
||||
->and($css->declarations('[data-md-drawer][data-md-standard]', $expanded))->toMatchArray([
|
||||
'overflow' => 'clip',
|
||||
'inline-size' => '0',
|
||||
'margin-inline-start' => 'calc(-1 * var(--md-drawer-gap, 0px))',
|
||||
'transition-property' => 'inline-size, margin-inline',
|
||||
])
|
||||
->and($css->declarations('[data-md-drawer][data-md-standard][data-md-open]', $expanded))->toMatchArray([
|
||||
'inline-size' => 'min(var(--sheet-width), calc(100vw - 64px))',
|
||||
'margin-inline' => '0',
|
||||
])
|
||||
// The old view's `expanded:hidden` on a standard sheet's scrim.
|
||||
->and($css->declarations('[data-md-drawer][data-md-standard] > [data-md-drawer-scrim]', $expanded))->toBe(['display' => 'none'])
|
||||
->and($css->declarations('[data-md-drawer][data-md-standard] > [data-md-drawer-sheet]', $expanded))->toHaveKey('box-shadow', 'none')
|
||||
|
||||
Reference in New Issue
Block a user