` or on a * nested panel), the medium and high contrast levels under `[data-contrast]` beside it, and — when * a caller gives one — every colour profile again under its own prefix (`[data-scheme='name']`). * * `SchemeCommand::handle()` writes this to the application's `material-scheme.css`; plan step 40 * moved the shape itself here so `ErrorPage::fallbackStyles()` can draw the same scheme, from * `Scheme::forStylesheet()` rather than a generated file, without a second copy of the selectors to * keep in step with the command's. */ final class SchemeStylesheet { /** * M3's two contrast levels beyond standard, keyed on ``; standard is the * scheme's own level and has no attribute of its own. * * @var list */ public const array LEVELS = ['medium', 'high']; /** * One scheme, whole: the standard level first, under `$prefix` (or `:root`/`[data-theme]` * without one), then M3's medium and high levels under `[data-contrast]` beside it. A level's * blocks are the standard ones with one more attribute, so they outrank them wherever both * match, and the light blocks of a level come before its dark ones as in every other pair here. * * @param array{contrast: array, dark: array}>, light: array, dark: array} $scheme */ public static function levels(array $scheme, string $prefix = ''): string { [$light, $dark] = self::selectors($prefix); $css = self::blocks($light, $dark, $scheme); foreach (self::LEVELS as $level) { [$light, $dark] = self::selectors($prefix."[data-contrast='{$level}']"); /** @var array{light: array, dark: array} $roles */ $roles = $scheme['contrast'][$level]; $css .= "\n".self::blocks($light, $dark, $roles); } return $css; } /** * The light and the dark selectors of one block, under the given prefix. Without a prefix the * plain blocks stand on `:root` and on the attribute alone; with one, the prefix is on `` * and the theme may be on `` or on a panel inside it, so both forms are written. * * @return array{list, list} */ public static function selectors(string $prefix): array { if ($prefix === '') { return [[':root', "[data-theme='light']"], ["[data-theme='dark']"]]; } return [ [$prefix, "{$prefix}[data-theme='light']", "{$prefix} [data-theme='light']"], ["{$prefix}[data-theme='dark']", "{$prefix} [data-theme='dark']"], ]; } /** * A light and a dark block of roles under the given selectors. * * @param list $light * @param list $dark * @param array{light: array, dark: array} $scheme */ public static function blocks(array $light, array $dark, array $scheme): string { $roles = fn (array $roles): string => collect($roles) ->map(fn (string $hex, string $role): string => " --md-sys-color-{$role}: {$hex};") ->implode("\n"); return implode(",\n", $light)." {\n color-scheme: light;\n\n".$roles($scheme['light'])."\n}\n\n" .implode(",\n", $dark)." {\n color-scheme: dark;\n\n".$roles($scheme['dark'])."\n}\n"; } }