Cut duplicated and speculative code across the package
An over-engineering audit of the whole tree, applied in five reviewed batches. Behaviour stays the same except where UPGRADE.md says otherwise. PHP: the showcase and error-page stylesheets are prebuilt into resources/dist by bin/stylesheets.mjs, through Vite's own postcss-import (first occurrence kept, the order an application's build gives), instead of Stylesheets::bundle() inlining imports on every request; only the import walk DesignGuard needs stays. SchemeStylesheet::withProfiles() replaces three copies of the scheme-plus-profiles loop, material:scheme leaves spec and contrast checks to the node script that already made them, and the error page's scheme cache, the hashed view namespace, the translations path with no lang/ folder and DesignGuard's 1.x-name hints are gone. JS: the androidx shape port progress.js and both bin scripts each carried lives once in resources/js/shapes.js (the generated SVGs are unchanged); util.js holds ringIndex(), ms(), reopenGuard() and remember(), which were written out several times; listeners are released through AbortController; tooltip.js's hoverPopover() serves the rich tooltip too. CSS: every rule for an element inside the navigation rail queries `--md-navigation-rail-value` instead of repeating the seven collapsed conditions under five media branches; badge, alert, progress, slider and button read one non-inheriting colour-role table (components/color.css); the dialog chrome, the submenu's popover chrome, the chip's state layer and touch target, and the visually-hidden inputs use the shared rules they copied; foundation/tokens.css is folded into foundation.css. Views: Support\Field and Support\Link replace the error-key, bound-value and link-attribute blocks copied into the fields and link components; the timepicker period group, the menu filter and the showcase head are partials; the datepicker's steppers and entry fields are loops; component docblocks no longer restate SKILL.md. Tests and tooling: one dataset-driven ComponentStylesheetsTest replaces four per-group files, DesignGuardTest and the layout-component tests use datasets, browser tests share one ready() helper, CSS parsing lives in ComponentStylesheet alone. docs/audits and the finding IDs citing it are removed, as are pestphp/pest-plugin-laravel, the unused composer scripts and check:font; the lint job runs in the feature job, which now installs node packages so the prebuilt-stylesheet staleness test runs in CI. Feature suite 1177 passed, Chrome browser suite 299 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
471d927e64
commit
247c596c3a
@@ -104,8 +104,7 @@ function collapseProbe()
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
return visit('/collapse-probe')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
return ready(visit('/collapse-probe'));
|
||||
}
|
||||
|
||||
function overlayProbe()
|
||||
@@ -127,14 +126,12 @@ function overlayProbe()
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
return visit('/overlay-probe')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
return ready(visit('/overlay-probe'));
|
||||
}
|
||||
|
||||
function containment()
|
||||
{
|
||||
return visit('/material/containment')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
return ready(visit('/material/containment'));
|
||||
}
|
||||
|
||||
it('writes back what closed means: false for a flag, null for an id', function () {
|
||||
@@ -265,8 +262,7 @@ function nestedDialogProbe()
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
return visit('/nested-dialog-probe')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
return ready(visit('/nested-dialog-probe'));
|
||||
}
|
||||
|
||||
it('marks a scrolling dialog\'s divider at the edge with more content, and a nested dialog keeps its own marks', function () {
|
||||
@@ -356,8 +352,7 @@ it('opens a dialog with no fade under reduced motion, but a real one without it'
|
||||
// With reduced motion (the plugin's own emulation, `visit(..., ['reducedMotion' => 'reduce'])`):
|
||||
// tokens/motion.css takes the same spring's duration to zero, so there is no transition to run,
|
||||
// and the dialog is at its final opacity at once rather than mid-fade.
|
||||
$reduced = visit('/material/containment', ['reducedMotion' => 'reduce'])->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'")
|
||||
$reduced = ready(visit('/material/containment', ['reducedMotion' => 'reduce']))
|
||||
->assertScript("window.matchMedia('(prefers-reduced-motion: reduce)').matches");
|
||||
|
||||
$reduced->click('#containment button:has-text("Basic dialog")');
|
||||
@@ -403,8 +398,7 @@ function textOnlyDialogProbe()
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
return visit('/text-only-dialog-probe')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
return ready(visit('/text-only-dialog-probe'));
|
||||
}
|
||||
|
||||
it('gives the scrolling body an inset focus ring when a text-only dialog opens from the keyboard', function () {
|
||||
@@ -634,8 +628,7 @@ function layersProbe(): mixed
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
return visit('/layers-probe')->resize(1000, 800)->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined'");
|
||||
return ready(visit('/layers-probe')->resize(1000, 800), livewire: false);
|
||||
}
|
||||
|
||||
function sheetOpen(string $id): string
|
||||
@@ -963,12 +956,39 @@ it('binds a collapse to an Alpine property both ways', function () {
|
||||
->assertScript("{$collapse}.open === false");
|
||||
});
|
||||
|
||||
it('fades the side sheet\'s scrim out on close, rather than making it vanish', function () {
|
||||
$sheet = "document.querySelector('#containment aside[role=\"dialog\"]')";
|
||||
$scrim = "{$sheet}.parentElement.querySelector(':scope > [data-md-drawer-scrim]')";
|
||||
/**
|
||||
* The side sheet and the bottom sheet close the same way — a fade on the scrim, a slide on the
|
||||
* sheet itself — differing only in the selector each renders under, the click that opens it, the
|
||||
* scrim's own hook attribute, the dimension it slides across, the `translate` component that
|
||||
* carries the offset (a single value for the side sheet's horizontal slide, the second of a
|
||||
* `translate: x y` pair for the bottom sheet's vertical one) and whether that offset is read
|
||||
* signed: the side sheet's can slide either way (a right-to-left page), so its mid-slide sample
|
||||
* takes `Math.abs()`; the bottom sheet only ever slides down, so its own stays signed.
|
||||
*/
|
||||
dataset('containment sheets', [
|
||||
'side sheet' => [
|
||||
"document.querySelector('#containment aside[role=\"dialog\"]')",
|
||||
'#containment button:has-text("Side sheet")',
|
||||
'data-md-drawer-scrim',
|
||||
'width',
|
||||
'',
|
||||
true,
|
||||
],
|
||||
'bottom sheet' => [
|
||||
"document.querySelector('#containment section[role=\"dialog\"]')",
|
||||
'#containment button:has(> span:text-is("Bottom sheet"))',
|
||||
'data-md-bottom-sheet-scrim',
|
||||
'height',
|
||||
".split(' ')[1]",
|
||||
false,
|
||||
],
|
||||
]);
|
||||
|
||||
it('fades the sheet\'s scrim out on close, rather than making it vanish', function (string $sheet, string $trigger, string $scrimAttr) {
|
||||
$scrim = "{$sheet}.parentElement.querySelector(':scope > [{$scrimAttr}]')";
|
||||
|
||||
$page = containment()
|
||||
->click('#containment button:has-text("Side sheet")')
|
||||
->click($trigger)
|
||||
->assertScript("getComputedStyle({$sheet}).display !== 'none'")
|
||||
->assertScript("getComputedStyle({$scrim}).opacity === '1'");
|
||||
|
||||
@@ -994,57 +1014,31 @@ it('fades the side sheet\'s scrim out on close, rather than making it vanish', f
|
||||
expect($midFade)->toBeTrue();
|
||||
|
||||
$page->assertScript("getComputedStyle({$scrim}).display === 'none'");
|
||||
});
|
||||
})->with('containment sheets');
|
||||
|
||||
it('fades the bottom sheet\'s scrim out on close, rather than making it vanish', function () {
|
||||
$sheet = "document.querySelector('#containment section[role=\"dialog\"]')";
|
||||
$scrim = "{$sheet}.parentElement.querySelector(':scope > [data-md-bottom-sheet-scrim]')";
|
||||
it('slides the sheet out on close, rather than making it vanish', function (string $sheet, string $trigger, string $scrimAttr, string $dimension, string $axis, bool $absolute) {
|
||||
$scrim = "{$sheet}.parentElement.querySelector(':scope > [{$scrimAttr}]')";
|
||||
$offset = "(parseFloat(getComputedStyle({$sheet}).translate{$axis}) || 0)";
|
||||
$sample = $absolute ? "Math.abs(parseFloat(style.translate{$axis}) || 0)" : "(parseFloat(style.translate{$axis}) || 0)";
|
||||
|
||||
$page = containment()
|
||||
->click('#containment button:has(> span:text-is("Bottom sheet"))')
|
||||
->click($trigger)
|
||||
->assertScript("getComputedStyle({$sheet}).display !== 'none'")
|
||||
->assertScript("getComputedStyle({$scrim}).opacity === '1'");
|
||||
|
||||
$midFade = $page->script(<<<JS
|
||||
(async () => {
|
||||
{$scrim}.click()
|
||||
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const opacity = parseFloat(getComputedStyle({$scrim}).opacity)
|
||||
if (opacity > 0.02 && opacity < 0.98) return true
|
||||
await new Promise((resolve) => setTimeout(resolve, 5))
|
||||
}
|
||||
return false
|
||||
})()
|
||||
JS);
|
||||
|
||||
expect($midFade)->toBeTrue();
|
||||
|
||||
$page->assertScript("getComputedStyle({$scrim}).display === 'none'");
|
||||
});
|
||||
|
||||
it('slides the side sheet out on close, rather than making it vanish', function () {
|
||||
$sheet = "document.querySelector('#containment aside[role=\"dialog\"]')";
|
||||
$scrim = "{$sheet}.parentElement.querySelector(':scope > [data-md-drawer-scrim]')";
|
||||
|
||||
$page = containment()
|
||||
->click('#containment button:has-text("Side sheet")')
|
||||
->assertScript("getComputedStyle({$sheet}).display !== 'none'")
|
||||
->assertScript("Math.abs(parseFloat(getComputedStyle({$sheet}).translate)) < 0.5");
|
||||
->assertScript("Math.abs({$offset}) < 0.5");
|
||||
|
||||
// Sampled in the page, in the same round trip as the close, as the scrim's fade is above: the
|
||||
// sheet must be caught still displayed and part of the way to its closed offset (one sheet
|
||||
// width past its edge), not gone at once — which is what Firefox showed while the exit leaned
|
||||
// on `allow-discrete` holding `display`.
|
||||
// sheet must be caught still displayed and part of the way to its closed offset (one sheet's
|
||||
// own size past its edge), not gone at once — which is what Firefox showed while the exit
|
||||
// leaned on `allow-discrete` holding `display`.
|
||||
$midSlide = $page->script(<<<JS
|
||||
(async () => {
|
||||
const width = {$sheet}.getBoundingClientRect().width
|
||||
const size = {$sheet}.getBoundingClientRect().{$dimension}
|
||||
{$scrim}.click()
|
||||
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const style = getComputedStyle({$sheet})
|
||||
const offset = Math.abs(parseFloat(style.translate) || 0)
|
||||
if (style.display !== 'none' && offset > 1 && offset < width - 1) return true
|
||||
const offset = {$sample}
|
||||
if (style.display !== 'none' && offset > 1 && offset < size - 1) return true
|
||||
await new Promise((resolve) => setTimeout(resolve, 5))
|
||||
}
|
||||
return false
|
||||
@@ -1054,36 +1048,7 @@ it('slides the side sheet out on close, rather than making it vanish', function
|
||||
expect($midSlide)->toBeTrue();
|
||||
|
||||
$page->assertScript("getComputedStyle({$sheet}).display === 'none'");
|
||||
});
|
||||
|
||||
it('slides the bottom sheet down on close, rather than making it vanish', function () {
|
||||
$sheet = "document.querySelector('#containment section[role=\"dialog\"]')";
|
||||
$scrim = "{$sheet}.parentElement.querySelector(':scope > [data-md-bottom-sheet-scrim]')";
|
||||
$offset = "(parseFloat(getComputedStyle({$sheet}).translate.split(' ')[1]) || 0)";
|
||||
|
||||
$page = containment()
|
||||
->click('#containment button:has(> span:text-is("Bottom sheet"))')
|
||||
->assertScript("getComputedStyle({$sheet}).display !== 'none'")
|
||||
->assertScript("Math.abs({$offset}) < 0.5");
|
||||
|
||||
$midSlide = $page->script(<<<JS
|
||||
(async () => {
|
||||
const height = {$sheet}.getBoundingClientRect().height
|
||||
{$scrim}.click()
|
||||
|
||||
for (let i = 0; i < 60; i++) {
|
||||
const offset = {$offset}
|
||||
if (getComputedStyle({$sheet}).display !== 'none' && offset > 1 && offset < height - 1) return true
|
||||
await new Promise((resolve) => setTimeout(resolve, 5))
|
||||
}
|
||||
return false
|
||||
})()
|
||||
JS);
|
||||
|
||||
expect($midSlide)->toBeTrue();
|
||||
|
||||
$page->assertScript("getComputedStyle({$sheet}).display === 'none'");
|
||||
});
|
||||
})->with('containment sheets');
|
||||
|
||||
it('slides the side sheet in from its own edge in a right-to-left page', function () {
|
||||
Route::middleware('web')->get('/drawer-rtl-probe', fn () => Blade::render(<<<'BLADE'
|
||||
@@ -1104,8 +1069,7 @@ it('slides the side sheet in from its own edge in a right-to-left page', functio
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
$page = visit('/drawer-rtl-probe')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined'");
|
||||
$page = ready(visit('/drawer-rtl-probe'), livewire: false);
|
||||
|
||||
$sheet = "document.querySelector('aside[data-md-side]')";
|
||||
|
||||
@@ -1171,8 +1135,7 @@ it('keeps a basic dialog\'s own title and padding when it is nested inside a ful
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
$page = visit('/nested-fullscreen-dialog-probe')->resize(400, 800)->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
$page = ready(visit('/nested-fullscreen-dialog-probe')->resize(400, 800));
|
||||
|
||||
$page->click('button:has-text("Open settings")');
|
||||
|
||||
@@ -1234,8 +1197,7 @@ it('keeps a selected segmented row\'s fill under the hover and focus tint', func
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
$page = visit('/segmented-selected-row-probe')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined'");
|
||||
$page = ready(visit('/segmented-selected-row-probe'), livewire: false);
|
||||
|
||||
$selected = "document.querySelector('[data-md-selected]')";
|
||||
$plain = "document.querySelector('[data-md-list-item]:not([data-md-selected])')";
|
||||
@@ -1301,8 +1263,7 @@ it('fills a selected row written by hand in secondary-container in both themes,
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
$page = visit('/hand-made-selected-row-probe')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined'");
|
||||
$page = ready(visit('/hand-made-selected-row-probe'), livewire: false);
|
||||
|
||||
$swatch = settledPaint("document.getElementById('swatch')");
|
||||
$row = settledPaint("document.getElementById('selected-row')");
|
||||
@@ -1376,8 +1337,7 @@ function collapseMotionProbe(string $durations = ''): mixed
|
||||
</html>
|
||||
BLADE));
|
||||
|
||||
$page = visit('/collapse-motion-probe')->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined'");
|
||||
$page = ready(visit('/collapse-motion-probe'), livewire: false);
|
||||
|
||||
if ($durations !== '') {
|
||||
$page->script("document.head.insertAdjacentHTML('beforeend', '<style>:root { --md-sys-motion-spatial-fast-duration: {$durations}; }</style>')");
|
||||
@@ -1658,8 +1618,7 @@ it('shows no tooltip on the control a sheet or a dialog focuses as it opens, but
|
||||
|
||||
// A phone, where the sheet is modal and traps the focus, which it moves to its ✕ on open: here
|
||||
// on a page loaded with the sheet open, as a link to a session opens it.
|
||||
$page = visit('/opened-layers-probe?workout=7', ['viewport' => ['width' => 393, 'height' => 800]])->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined'")
|
||||
$page = ready(visit('/opened-layers-probe?workout=7', ['viewport' => ['width' => 393, 'height' => 800]]), livewire: false)
|
||||
->assertScript("document.activeElement.matches('{$sheet} button[aria-label=\"Close\"]')")
|
||||
->wait(0.3)
|
||||
->assertScript('! '.closeTooltipOpen($sheet));
|
||||
@@ -1670,8 +1629,7 @@ it('shows no tooltip on the control a sheet or a dialog focuses as it opens, but
|
||||
->assertScript(closeTooltipOpen($sheet));
|
||||
|
||||
// A full-screen dialog opened from the keyboard moves the focus to its bar's ✕ in showModal().
|
||||
$page = visit('/opened-layers-probe', ['viewport' => ['width' => 393, 'height' => 800]])->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Livewire !== 'undefined'");
|
||||
$page = ready(visit('/opened-layers-probe', ['viewport' => ['width' => 393, 'height' => 800]]), alpine: false);
|
||||
|
||||
$page->keys('#open-setback', 'Enter')
|
||||
->assertScript("document.querySelector('{$dialog}').open && document.activeElement.matches('{$dialog} button[aria-label=\"Close\"]')")
|
||||
@@ -1683,8 +1641,7 @@ it('shows no tooltip on the control a sheet or a dialog focuses as it opens, but
|
||||
->assertScript(closeTooltipOpen($dialog));
|
||||
|
||||
// A rich tooltip's trigger as the first control of a dialog opened from the keyboard.
|
||||
$page = visit('/opened-layers-probe', ['viewport' => ['width' => 393, 'height' => 800]])->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Livewire !== 'undefined'");
|
||||
$page = ready(visit('/opened-layers-probe', ['viewport' => ['width' => 393, 'height' => 800]]), alpine: false);
|
||||
|
||||
$page->keys('#open-about', 'Enter')
|
||||
->assertScript("document.activeElement.id === 'about-help'")
|
||||
|
||||
Reference in New Issue
Block a user