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
@@ -24,7 +24,7 @@ it('gives the collapsible, modal and adaptive rails a menu button and the store'
|
||||
->toContain('data-md-navigation-rail-menu')
|
||||
->toContain('aria-label="Collapse navigation"')
|
||||
->toContain('aria-expanded="true"')
|
||||
// The 40px menu button reaches M3's 48px target through the shared classes (N-01).
|
||||
// The 40px menu button reaches M3's 48px target through the shared classes.
|
||||
->toContain('class="md-state-layer md-focus-ring md-touch-target"')
|
||||
->not->toContain('data-md-navigation-rail-scrim')
|
||||
->not->toContain('x-trap')
|
||||
@@ -63,13 +63,13 @@ it('takes M3\'s optional divider and turns the container fill off', function ()
|
||||
|
||||
$css = ComponentStylesheet::read('navigation-rail');
|
||||
|
||||
// Neither reaches a rail open over a scrim, which is a surface over the page (N-22), nor one
|
||||
// Neither reaches a rail open over a scrim, which is a surface over the page, nor one
|
||||
// sliding off the window as it closes.
|
||||
expect($css->declarations('[data-md-navigation-rail][data-md-divider]:not([data-md-open], [data-md-closing=\'sheet\']) > [data-md-navigation-rail-panel]'))
|
||||
->toBe(['border-inline-end' => '1px solid var(--md-sys-color-outline-variant)'])
|
||||
->and($css->declarations('[data-md-navigation-rail][data-md-fill=\'false\']:not([data-md-open], [data-md-closing=\'sheet\']) > [data-md-navigation-rail-panel]'))
|
||||
->toBe(['background-color' => 'transparent'])
|
||||
// A collapsible rail is held to its collapsed width where M3 asks for a bar instead (N-24).
|
||||
// A collapsible rail is held to its collapsed width where M3 asks for a bar instead.
|
||||
->and($css->declarations('[data-md-navigation-rail=\'collapsible\']', ['@media (width < 600px)']))
|
||||
->toBe(['width' => 'var(--navigation-rail-collapsed-width)']);
|
||||
});
|
||||
@@ -97,7 +97,7 @@ it('takes M3\'s narrow collapsed width, icons alone but still named', function (
|
||||
->and($css->declarations('[data-md-navigation-rail]'))
|
||||
->toHaveKey('--navigation-rail-collapsed-width', '96px')
|
||||
// Out of the drawing, not out of the page: clipped only while collapsed.
|
||||
->and($css->declarations("[data-md-navigation-rail][data-md-width='narrow'] [data-md-navigation-rail-item] [data-md-navigation-label]:where( [data-md-navigation-rail='collapsed'], [data-md-navigation-rail='collapsed'] *, [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]), [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *, [data-md-navigation-rail='modal']:not([data-md-open]), [data-md-navigation-rail='modal']:not([data-md-open]) * )"))
|
||||
->and($css->declarations("[data-md-navigation-rail][data-md-width='narrow'] [data-md-navigation-rail-item] [data-md-navigation-label]", ['@container style(--md-navigation-rail-value: collapsed)']))
|
||||
->toHaveKey('clip-path', 'inset(50%)');
|
||||
});
|
||||
|
||||
@@ -206,18 +206,18 @@ it('reproduces every branch of the old rail-collapsed variant for the rail\'s ow
|
||||
it('flattens a FAB nested in the rail header and morphs its label', function () {
|
||||
$css = ComponentStylesheet::read('navigation-rail');
|
||||
|
||||
// A nested FAB rests at elevation 0, not the 3 a standalone one has (N-03). Now layered, like
|
||||
// A nested FAB rests at elevation 0, not the 3 a standalone one has. Now layered, like
|
||||
// toolbar.css's own docked-FAB override: fab.css's `[data-md-fab]` is one attribute, so the
|
||||
// doubled selector here (two) always wins without needing to sit outside the layer.
|
||||
expect($css->declarations('[data-md-navigation-rail-header] [data-md-fab][data-md-fab], [data-md-navigation-rail-header] [data-md-fab][data-md-fab]:hover'))
|
||||
->toBe(['box-shadow' => 'none'])
|
||||
// One FAB whose label springs shut, not two swapped by display (N-23).
|
||||
// One FAB whose label springs shut, not two swapped by display.
|
||||
->and($css->declarations('[data-md-navigation-rail-header] [data-md-fab] > span'))
|
||||
->toHaveKey('max-width', '256px');
|
||||
|
||||
expect($css->declarations("[data-md-navigation-rail-header] [data-md-fab] > span:where( [data-md-navigation-rail='collapsed'], [data-md-navigation-rail='collapsed'] *, [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]), [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *, [data-md-navigation-rail='modal']:not([data-md-open]), [data-md-navigation-rail='modal']:not([data-md-open]) * )"))
|
||||
expect($css->declarations('[data-md-navigation-rail-header] [data-md-fab] > span', ['@container style(--md-navigation-rail-value: collapsed)']))
|
||||
->toBe(['max-width' => '0', 'opacity' => '0'])
|
||||
->and($css->declarations("[data-md-navigation-rail-header] [data-md-fab][data-md-extended][data-md-extended]:where( [data-md-navigation-rail='collapsed'], [data-md-navigation-rail='collapsed'] *, [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]), [data-rail='collapsed'] [data-md-navigation-rail='collapsible']:not([data-md-open]) *, [data-md-navigation-rail='modal']:not([data-md-open]), [data-md-navigation-rail='modal']:not([data-md-open]) * )"))
|
||||
->and($css->declarations('[data-md-navigation-rail-header] [data-md-fab][data-md-extended][data-md-extended]', ['@container style(--md-navigation-rail-value: collapsed)']))
|
||||
->toBe(['min-inline-size' => '0', 'aspect-ratio' => '1', 'gap' => '0']);
|
||||
});
|
||||
|
||||
@@ -260,21 +260,13 @@ it('draws the item\'s own state layer split between the item and its indicator',
|
||||
});
|
||||
|
||||
/**
|
||||
* Plain CSS has no variant to name "drawn collapsed" once, so every rule with a collapsed shape
|
||||
* writes the conditions out: navigation-rail.css, navigation-rail-item.css,
|
||||
* navigation-rail-section.css, and layout/scaffold.css for the actions row inside the rail's footer.
|
||||
* This walks every copy in those files — not one pinned rule — and fails if any drifts from the
|
||||
* seven conditions the Tailwind-era `rail-collapsed:` variant had, or if a rule has its collapsed
|
||||
* shape in some window bands but not in all five.
|
||||
* navigation-rail.css alone still writes "drawn collapsed" branch for branch: the two rules that
|
||||
* set the rail's own width and publish `--md-navigation-rail-value`, which the rail cannot query
|
||||
* on itself (a `@container style()` reads an ancestor, never the element that publishes the
|
||||
* property). This walks every copy of that seven-condition set and fails if any drifts from it, or
|
||||
* has its collapsed shape in some window bands but not in all five.
|
||||
*/
|
||||
dataset('stylesheets that draw a collapsed rail', [
|
||||
'navigation-rail',
|
||||
'navigation-rail-item',
|
||||
'navigation-rail-section',
|
||||
'../layout/scaffold',
|
||||
]);
|
||||
|
||||
it('writes "collapsed" with the same seven conditions in every copy, in every band', function (string $name) {
|
||||
it('writes the rail\'s own "collapsed" condition with the same seven conditions, in every band', function () {
|
||||
$closed = ':not([data-md-open])';
|
||||
$bands = [
|
||||
'always' => ["[data-md-navigation-rail='collapsed']", "[data-rail='collapsed'] [data-md-navigation-rail='collapsible']{$closed}", "[data-md-navigation-rail='modal']{$closed}"],
|
||||
@@ -284,14 +276,12 @@ it('writes "collapsed" with the same seven conditions in every copy, in every ba
|
||||
'@media (width >= 1200px)' => ["[data-rail='collapsed'] [data-md-navigation-rail='adaptive']{$closed}"],
|
||||
];
|
||||
|
||||
// A condition matches the rail itself and everything in it; the scaffold's actions row can only
|
||||
// ever be inside a rail, so its copy keeps the descendant half alone.
|
||||
// A condition matches the rail itself and everything in it.
|
||||
$both = fn (array $conditions): array => array_merge(...array_map(fn (string $condition): array => [$condition, "{$condition} *"], $conditions));
|
||||
$inside = fn (array $conditions): array => array_map(fn (string $condition): string => "{$condition} *", $conditions);
|
||||
|
||||
$groups = [];
|
||||
|
||||
foreach (ComponentStylesheet::read($name)->rules() as $rule) {
|
||||
foreach (ComponentStylesheet::read('navigation-rail')->rules() as $rule) {
|
||||
preg_match_all('/:where\(((?:[^()]++|\((?1)\))*)\)/', $rule['selector'], $wheres, PREG_SET_ORDER);
|
||||
$wheres = array_filter($wheres, fn (array $where): bool => str_contains($where[1], 'data-md-navigation-rail='));
|
||||
|
||||
@@ -302,17 +292,15 @@ it('writes "collapsed" with the same seven conditions in every copy, in every ba
|
||||
$media = array_values(array_filter($rule['at'], fn (string $at): bool => str_starts_with($at, '@media')));
|
||||
$band = match ($media) {
|
||||
[] => 'always',
|
||||
['@media (width >= 840px)', '@media (width < 1200px)'] => '@media (840px <= width < 1200px)',
|
||||
default => implode(' › ', $media),
|
||||
};
|
||||
|
||||
expect(array_key_exists($band, $bands))->toBeTrue("{$name}.css: `{$rule['selector']}` sits in {$band}, which is no band of the rail's");
|
||||
expect(array_key_exists($band, $bands))->toBeTrue("navigation-rail.css: `{$rule['selector']}` sits in {$band}, which is no band of the rail's");
|
||||
|
||||
foreach ($wheres as [$where, $conditions]) {
|
||||
$written = array_map(trim(...), preg_split('/,(?![^()]*\))/', $conditions));
|
||||
|
||||
expect(in_array($written, [$both($bands[$band]), $inside($bands[$band])], true))
|
||||
->toBeTrue("{$name}.css: `{$where}` in {$band} is not the rail's collapsed conditions for that band");
|
||||
expect($written)->toBe($both($bands[$band]), "navigation-rail.css: `{$where}` in {$band} is not the rail's collapsed conditions for that band");
|
||||
}
|
||||
|
||||
$key = str_replace(array_column($wheres, 0), '', $rule['selector']).' '.json_encode($rule['declarations']);
|
||||
@@ -322,6 +310,51 @@ it('writes "collapsed" with the same seven conditions in every copy, in every ba
|
||||
expect($groups)->not->toBeEmpty();
|
||||
|
||||
foreach ($groups as $key => $found) {
|
||||
expect(array_values(array_unique($found)))->toEqualCanonicalizing(array_keys($bands), "{$name}.css: {$key} is drawn collapsed in some bands only");
|
||||
expect(array_values(array_unique($found)))->toEqualCanonicalizing(array_keys($bands), "navigation-rail.css: {$key} is drawn collapsed in some bands only");
|
||||
}
|
||||
})->with('stylesheets that draw a collapsed rail');
|
||||
});
|
||||
|
||||
/**
|
||||
* Everything that takes a collapsed shape and is not the rail element itself reads the value the
|
||||
* rules above publish, one `@container style()` per rule, instead of a copy of the rail's own
|
||||
* seven conditions — the reason `--md-navigation-rail-value` is published at all (UPGRADE.md,
|
||||
* "From 2.0.0 to 2.1.0"). This walks every copy across navigation-rail.css,
|
||||
* navigation-rail-item.css, navigation-rail-section.css and layout/scaffold.css, fails if any of
|
||||
* them spells the query differently or wraps no declarations, and — outside navigation-rail.css,
|
||||
* where the two rules above are the only rules allowed to — fails if the old `:where()` copy of
|
||||
* the rail's conditions comes back.
|
||||
*/
|
||||
dataset('stylesheets with a rail descendant that draws a collapsed shape', [
|
||||
'navigation-rail',
|
||||
'navigation-rail-item',
|
||||
'navigation-rail-section',
|
||||
'../layout/scaffold',
|
||||
]);
|
||||
|
||||
it('reads the rail\'s published value with the same style container query in every copy', function (string $name) {
|
||||
$css = ComponentStylesheet::read($name);
|
||||
$rules = $css->rules();
|
||||
|
||||
$matches = array_values(array_filter(
|
||||
$rules,
|
||||
fn (array $rule): bool => array_filter($rule['at'], fn (string $at): bool => str_starts_with($at, '@container')) !== [],
|
||||
));
|
||||
|
||||
expect($matches)->not->toBeEmpty();
|
||||
|
||||
foreach ($matches as $rule) {
|
||||
$containers = array_values(array_filter($rule['at'], fn (string $at): bool => str_starts_with($at, '@container')));
|
||||
|
||||
expect($containers)->toBe(['@container style(--md-navigation-rail-value: collapsed)'], "{$name}.css: `{$rule['selector']}` wraps a different container query")
|
||||
->and($rule['declarations'])->not->toBeEmpty("{$name}.css: `{$rule['selector']}` wraps an empty container query");
|
||||
}
|
||||
|
||||
if ($name === 'navigation-rail') {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($rules as $rule) {
|
||||
expect(str_contains($rule['selector'], "data-md-navigation-rail='collapsed'"))
|
||||
->toBeFalse("{$name}.css: `{$rule['selector']}` copies the rail's conditions instead of querying its value");
|
||||
}
|
||||
})->with('stylesheets with a rail descendant that draws a collapsed shape');
|
||||
|
||||
Reference in New Issue
Block a user