tests / lint (push) Has been cancelled
tests / feature (8.4) (push) Has been cancelled
tests / feature (8.5) (push) Has been cancelled
tests / browser (chrome, chromium) (push) Has been cancelled
tests / browser (firefox, firefox) (push) Has been cancelled
tests / browser (safari, webkit) (push) Has been cancelled
The showcase is now an overview and one page per section behind a grouped navigation rail, moved between with wire:navigate, instead of one long page under a scrolling top bar. The switch exposed a WebKit bug in the menu: inside a focusable region, WebKit moves focus out of the closing popover before its toggle event, so Escape no longer returned focus to the trigger. The menu now reads it on beforetoggle. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
158 lines
6.3 KiB
PHP
158 lines
6.3 KiB
PHP
<?php
|
|
|
|
use Illuminate\Support\Facades\Blade;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Livewire\Component;
|
|
use Livewire\Livewire;
|
|
|
|
class OverlayProbe extends Component
|
|
{
|
|
public bool $confirming = false;
|
|
|
|
public ?int $deletingId = null;
|
|
|
|
public int $renders = 0;
|
|
|
|
public function touch(): void
|
|
{
|
|
$this->renders++;
|
|
}
|
|
|
|
public function render(): string
|
|
{
|
|
return <<<'BLADE'
|
|
<div class="space-y-4 p-4">
|
|
<p>confirming: <span id="confirming">{{ var_export($confirming, true) }}</span></p>
|
|
<p>deleting: <span id="deleting">{{ var_export($deletingId, true) }}</span></p>
|
|
<p>renders: <span id="renders">{{ $renders }}</span></p>
|
|
|
|
<x-button label="Confirm" wire:click="$set('confirming', true)" />
|
|
<x-button label="Delete 7" wire:click="$set('deletingId', 7)" />
|
|
|
|
<x-modal wire:model="confirming" title="Are you sure?">
|
|
<x-slot:actions>
|
|
<x-button label="Re-render" wire:click="touch" />
|
|
</x-slot:actions>
|
|
</x-modal>
|
|
|
|
<x-modal wire:model="deletingId" title="Delete share?" />
|
|
</div>
|
|
BLADE;
|
|
}
|
|
}
|
|
|
|
function overlayProbe()
|
|
{
|
|
Livewire::component('overlay-probe', OverlayProbe::class);
|
|
|
|
Route::middleware('web')->get('/overlay-probe', fn () => Blade::render(<<<'BLADE'
|
|
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<x-theme-script />
|
|
@vite(config('livewire-material.showcase.vite'))
|
|
@livewireStyles
|
|
</head>
|
|
<body class="bg-surface">
|
|
<livewire:overlay-probe />
|
|
@livewireScripts
|
|
</body>
|
|
</html>
|
|
BLADE));
|
|
|
|
return visit('/overlay-probe')->waitForEvent('networkidle')
|
|
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
|
}
|
|
|
|
function containment()
|
|
{
|
|
return visit('/material/containment')->waitForEvent('networkidle')
|
|
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
|
}
|
|
|
|
it('writes back what closed means: false for a flag, null for an id', function () {
|
|
$page = overlayProbe();
|
|
|
|
$page->click('button:has-text("Confirm")')
|
|
->assertScript("[...document.querySelectorAll('dialog')].some((d) => d.open && d.textContent.includes('Are you sure?'))");
|
|
|
|
$page->click('dialog[open] button:has-text("Re-render")')
|
|
->assertSeeIn('#renders', '1')
|
|
->assertScript("[...document.querySelectorAll('dialog')].some((d) => d.open && d.textContent.includes('Are you sure?'))");
|
|
|
|
$page->keys('dialog[open] button:has-text("Re-render")', 'Escape')
|
|
->assertScript("! [...document.querySelectorAll('dialog')].some((d) => d.open)")
|
|
->assertSeeIn('#confirming', 'false');
|
|
|
|
$page->click('button:has-text("Delete 7")')
|
|
->assertSeeIn('#deleting', '7')
|
|
->assertScript("[...document.querySelectorAll('dialog')].some((d) => d.open && d.textContent.includes('Delete share?'))");
|
|
|
|
$page->script("document.querySelector('dialog[open]').dispatchEvent(new Event('cancel', { cancelable: true }))");
|
|
|
|
$page->assertSeeIn('#deleting', 'NULL');
|
|
});
|
|
|
|
it('closes a showcase dialog on Escape and gives focus back to its trigger', function () {
|
|
// Opened from the keyboard: Safari does not focus a button on click, so after a click there
|
|
// is nothing for the dialog to hand focus back to.
|
|
$page = containment();
|
|
|
|
$page->script("[...document.querySelectorAll('#containment button')].find((b) => b.textContent.trim() === 'Basic dialog').focus()");
|
|
|
|
$page->keys(':focus', 'Enter')
|
|
->assertScript("document.querySelector('#containment dialog').open");
|
|
|
|
$page->keys('#containment dialog[open] button:has-text("Cancel")', 'Escape')
|
|
->assertScript("! document.querySelector('#containment dialog').open")
|
|
->assertScript("document.activeElement.textContent.trim() === 'Basic dialog'");
|
|
});
|
|
|
|
it('slides a side sheet in, traps the page, and closes on the scrim', function () {
|
|
$sheet = "document.querySelector('#containment aside[role=\"dialog\"]')";
|
|
|
|
$page = containment()
|
|
->click('#containment button:has-text("Side sheet")')
|
|
->assertScript("getComputedStyle({$sheet}).display !== 'none'")
|
|
->assertScript("document.querySelector('header').closest('[aria-hidden=\"true\"]') !== null");
|
|
|
|
$page->script("document.querySelector('[data-sheet] > [aria-hidden=\"true\"]').click()");
|
|
|
|
$page->assertScript("getComputedStyle({$sheet}).display === 'none'")
|
|
->assertScript("document.querySelector('header').closest('[aria-hidden=\"true\"]') === null");
|
|
});
|
|
|
|
it('dismisses a bottom sheet dragged down past a quarter of its height', function () {
|
|
$sheet = "document.querySelector('#containment section[role=\"dialog\"]')";
|
|
|
|
$page = containment()
|
|
->click('#containment button:has-text("Bottom sheet")')
|
|
->assertScript("getComputedStyle({$sheet}).display !== 'none'")
|
|
->wait(0.5);
|
|
|
|
$page->script(<<<JS
|
|
(() => {
|
|
const handle = {$sheet}.querySelector('[data-drag-handle]');
|
|
const box = handle.getBoundingClientRect();
|
|
const x = box.x + box.width / 2, y = box.y + box.height / 2;
|
|
handle.dispatchEvent(new PointerEvent('pointerdown', { bubbles: true, clientX: x, clientY: y, button: 0 }));
|
|
window.dispatchEvent(new PointerEvent('pointermove', { clientX: x, clientY: y + 400 }));
|
|
window.dispatchEvent(new PointerEvent('pointerup', { clientX: x, clientY: y + 400 }));
|
|
})()
|
|
JS);
|
|
|
|
$page->assertScript("getComputedStyle({$sheet}).display === 'none'");
|
|
});
|
|
|
|
it('opens a row\'s opener from a press anywhere on the row, but not from its own buttons', function () {
|
|
$page = containment();
|
|
|
|
$page->script("window.__opened = 0; document.querySelector('#containment [data-card][data-list-row] [data-list-open]').addEventListener('click', (event) => { event.preventDefault(); window.__opened++ })");
|
|
|
|
$page->click('#containment [data-card][data-list-row] p')
|
|
->assertScript('window.__opened === 1');
|
|
|
|
$page->click('#containment [data-card][data-list-row] button:has-text("Copy link")')
|
|
->assertScript('window.__opened === 1');
|
|
});
|