Keep the theme-color meta in step with the theme
An app installed as a PWA, and a mobile browser, colour their bar from <meta name="theme-color">, which the package left alone: the bar kept the server's one colour whatever theme the visitor chose. With the new `theme.meta` option (off by default), <x-theme-script> sets the content of every theme-color meta without a `media` attribute to the resolved theme's surface, from the scheme data and for the profile in <html data-scheme>, adding one to <head> when the page has none. A MutationObserver on <html> follows every later change of data-theme or data-scheme: $store.theme's set() and toggle(), an OS change while `system`, a profile preview, an application's own script. wire:navigate's head merge puts the next page's server-rendered meta in place, so it is painted again as the page is swapped in and on livewire:navigated. Turned off, the script is byte-for-byte what it was. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
This commit is contained in:
co-authored by
Claude Opus 5
parent
3a7aa96ec7
commit
bcf0d4f4e3
@@ -1,5 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Blade;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use NoNameWeb\LivewireMaterial\Support\Scheme;
|
||||
|
||||
use function Orchestra\Testbench\workbench_path;
|
||||
|
||||
/**
|
||||
* The theme <html> shows. A bare `html` selector would be read as text to search for.
|
||||
*/
|
||||
@@ -56,3 +62,94 @@ it('repaints a section that sets its own theme', function () {
|
||||
->assertScript(theme('data-theme', 'light'))
|
||||
->assertScript("{$swatch(0)} !== {$swatch(1)}");
|
||||
});
|
||||
|
||||
/**
|
||||
* The page has exactly one theme-color meta without a media query, and it shows the given colour.
|
||||
*/
|
||||
function themeColorIs(string $hex): string
|
||||
{
|
||||
return "(() => { const metas = document.head.querySelectorAll('meta[name=theme-color]:not([media])'); return metas.length === 1 && metas[0].getAttribute('content') === '{$hex}'; })()";
|
||||
}
|
||||
|
||||
/**
|
||||
* The surface colour role, as <html> resolves it.
|
||||
*/
|
||||
function pageSurfaceIs(string $hex): string
|
||||
{
|
||||
return "getComputedStyle(document.documentElement).getPropertyValue('--md-sys-color-surface').trim() === '{$hex}'";
|
||||
}
|
||||
|
||||
function themeReady(mixed $page): mixed
|
||||
{
|
||||
return $page->waitForEvent('networkidle')
|
||||
->assertScript("document.readyState === 'complete' && typeof window.Alpine !== 'undefined' && typeof window.Livewire !== 'undefined'");
|
||||
}
|
||||
|
||||
it('adds a theme-color meta in the painted surface, and follows the theme and the colour profile', function () {
|
||||
config([
|
||||
'livewire-material.theme.meta' => true,
|
||||
'livewire-material.scheme' => workbench_path('resources/css/material-scheme.json'),
|
||||
]);
|
||||
|
||||
$profiles = Scheme::profiles();
|
||||
|
||||
$page = themeReady(visit('/material/colour')->inLightMode())
|
||||
->assertScript(theme('data-scheme', 'baseline'))
|
||||
->assertScript(themeColorIs($profiles['baseline']['light']['surface']))
|
||||
->assertScript(pageSurfaceIs($profiles['baseline']['light']['surface']));
|
||||
|
||||
$page->click('[data-theme-option="dark"]')
|
||||
->assertScript(theme('data-theme', 'dark'))
|
||||
->assertScript(themeColorIs($profiles['baseline']['dark']['surface']))
|
||||
->assertScript(pageSurfaceIs($profiles['baseline']['dark']['surface']));
|
||||
|
||||
$page->click('#colour [data-scheme-option="rose"]')
|
||||
->assertScript(theme('data-scheme', 'rose'))
|
||||
->assertScript(themeColorIs($profiles['rose']['dark']['surface']))
|
||||
->assertScript(pageSurfaceIs($profiles['rose']['dark']['surface']));
|
||||
|
||||
$page->click('[data-theme-option="light"]')
|
||||
->assertScript(themeColorIs($profiles['rose']['light']['surface']))
|
||||
->assertScript(pageSurfaceIs($profiles['rose']['light']['surface']))
|
||||
->assertNoJavaScriptErrors();
|
||||
});
|
||||
|
||||
it('repaints the page\'s own theme-color meta, and the next page\'s after wire:navigate', function () {
|
||||
config(['livewire-material.theme.meta' => true]);
|
||||
|
||||
Route::middleware('web')->get('/theme-color-probe/{page}', fn (string $page) => Blade::render(<<<'BLADE'
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="theme-color" media="print" content="#ffffff" />
|
||||
<x-theme-script />
|
||||
@vite(config('livewire-material.showcase.vite'))
|
||||
@livewireStyles
|
||||
</head>
|
||||
<body class="bg-surface">
|
||||
<p id="page">This is page {{ $page }}.</p>
|
||||
<a id="next" href="/theme-color-probe/two" wire:navigate>Next</a>
|
||||
@livewireScripts
|
||||
</body>
|
||||
</html>
|
||||
BLADE, ['page' => $page]));
|
||||
|
||||
$scheme = Scheme::load();
|
||||
|
||||
$page = themeReady(visit('/theme-color-probe/one')->inLightMode())
|
||||
->assertScript(themeColorIs($scheme['light']['surface']))
|
||||
->assertScript("document.head.querySelector('meta[media]').getAttribute('content') === '#ffffff'");
|
||||
|
||||
$page->script("window.eval(\"Alpine.store('theme').set('dark'); window.samePage = true\")");
|
||||
|
||||
$page->assertScript(themeColorIs($scheme['dark']['surface']));
|
||||
|
||||
$page->click('#next')
|
||||
->assertSeeIn('#page', 'This is page two.')
|
||||
->assertScript("window.eval('window.samePage') === true")
|
||||
->assertScript(theme('data-theme', 'dark'))
|
||||
->assertScript(themeColorIs($scheme['dark']['surface']))
|
||||
->assertScript("document.head.querySelector('meta[media]').getAttribute('content') === '#ffffff'")
|
||||
->assertNoJavaScriptErrors();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user