Draw the button's state layer, focus ring and target from the classes
Plan step 36 ("Interaction is the shared classes", the user, 2026-09-14).
button.css hand-rolled the same declarations as foundation/interaction.css's
md-state-layer, md-focus-ring and md-touch-target; it now renders those
classes (the touch target only at xs and sm, where it draws under 48px) and
keeps only its own geometry, colour and motion. The stylesheet test's "writes
no class list" check now allows the three interaction classes, in any subset,
and $attributes->class() as the way a root merges them with a caller's own
class; split-button and button-group, which nest a button, are updated to match.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
This commit is contained in:
co-authored by
Claude Opus 5
parent
aa3e789081
commit
bfd368e463
@@ -61,11 +61,56 @@ it('imports the stylesheet of every component its view renders', function (strin
|
||||
expect(array_values(array_diff($rendered, ComponentStylesheet::read($name)->imports())))->toBe([]);
|
||||
})->with('action components');
|
||||
|
||||
it('writes no class list into the view', function (string $name) {
|
||||
it('writes no class list into the view but the interaction classes', function (string $name) {
|
||||
$view = File::get(__DIR__."/../../../resources/views/components/{$name}.blade.php");
|
||||
$allowed = ['md-state-layer', 'md-focus-ring', 'md-touch-target'];
|
||||
|
||||
// A class the caller hands in (`hint-class`, `icon-class`, a slot's attributes) passes through.
|
||||
expect($view)->not->toMatch('/(?<![\w-])class="(?!\{\{ \$)|@class\(|->class\(|toCssClasses|(?:x-bind)?:class=/');
|
||||
// `@class()`, `Arr::toCssClasses` maps and Alpine's `:class` binding are never how a class
|
||||
// reaches the page here; the interaction classes and a caller's own class are the only source.
|
||||
expect($view)->not->toMatch('/@class\(|toCssClasses|(?:x-bind)?:class=/');
|
||||
|
||||
// A literal `class="…"` may hold only the interaction classes — any subset, any order, with a
|
||||
// simple `@if (…) … @endif` around one token — or a caller's value forwarded whole (`hint-class`,
|
||||
// `icon-class`: `class="{{ $…}}"`).
|
||||
preg_match_all('/(?<![\w:-])class=(["\'])(.*?)\1/s', $view, $classAttributes, PREG_SET_ORDER);
|
||||
|
||||
foreach ($classAttributes as [, , $content]) {
|
||||
if (preg_match('/^\{\{\s*\$/', trim($content)) === 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$stripped = (string) preg_replace('/\{\{.*?\}\}|@if\s*\([^()]*\)|@unless\s*\([^()]*\)|@else|@endif|@endunless/s', ' ', $content);
|
||||
$tokens = array_values(array_filter(preg_split('/\s+/', trim($stripped))));
|
||||
|
||||
expect(array_diff($tokens, $allowed))->toBe([], "{$name}.blade.php writes class=\"{$content}\", not only the interaction classes");
|
||||
}
|
||||
|
||||
// `$attributes->class([…])` merges the interaction classes with the caller's own, per the
|
||||
// brief: "merges through $attributes->class() only where the root takes the caller's class".
|
||||
// Only the array's own items are checked — a condition may nest its own brackets
|
||||
// (`in_array($size, ['xs', 'sm'], true)`), which are no class list of the view's writing.
|
||||
preg_match_all('/->class\(\[(.*?)\]\)/s', $view, $classCalls, PREG_SET_ORDER);
|
||||
|
||||
foreach ($classCalls as [, $args]) {
|
||||
$depth = 0;
|
||||
|
||||
for ($i = 0, $length = strlen($args); $i < $length; $i++) {
|
||||
$char = $args[$i];
|
||||
|
||||
if ($char === '[' || $char === '(') {
|
||||
$depth++;
|
||||
} elseif ($char === ']' || $char === ')') {
|
||||
$depth--;
|
||||
} elseif ($depth === 0 && $char === "'") {
|
||||
$end = strpos($args, "'", $i + 1);
|
||||
$item = substr($args, $i + 1, $end - $i - 1);
|
||||
|
||||
expect(in_array($item, $allowed, true))->toBeTrue("{$name}.blade.php: ->class() writes [{$item}], not an interaction class");
|
||||
|
||||
$i = $end;
|
||||
}
|
||||
}
|
||||
}
|
||||
})->with('action components');
|
||||
|
||||
it('takes its values from the tokens and its breakpoints in px', function (string $name) {
|
||||
|
||||
Reference in New Issue
Block a user