tests / feature (8.4) (push) Successful in 2m0s
tests / feature (8.5) (push) Successful in 2m0s
tests / browser (chrome, chromium) (push) Failing after 8m3s
tests / browser (firefox, firefox) (push) Failing after 12m58s
tests / browser (safari, webkit) (push) Failing after 13m8s
Tailwind left the stack in 2.0.0, but the package still carried about 330 mentions of it. What the guard's Tailwind detection protected — a class that compiles to nothing — is now protected by a check that does not care where a dead class came from. DesignGuard: about 500 lines of Tailwind tables, scales, palettes and "2.0.0 replacement" hints give way to one check — a class a view or PHP file writes that neither the application's stylesheets nor the package's own declare. It catches a utility of any framework, a typo and a class whose rules were deleted alike, so it also found two classes ReStride draws nothing with. A stylesheet has to be in reach for it: the `.css` files among the scanned paths, or what the `missingStylesheets()` entry imports. The class reader no longer mistakes an array index for a class list (`$block['base']`), and it reads the array a class helper is given, where it read nothing before. The package's own three Tailwind self-guards go with it. Only their one unique check stays, as a test of its own: every `matchMedia` width in resources/js is an M3 breakpoint. The pagination views are `material.blade.php` and `simple-material.blade.php`; only Laravel's and Livewire's default theme names ever made them `tailwind`. The provider sets `Paginator`'s default views and switches `livewire.pagination_theme` to `material` when it is still Livewire's own default, so no application can forget the config; a theme an application chose, and a component's own `$paginationTheme` or `paginationView()`, still win. The rest is prose: the layer-order guidance for an application that still builds Tailwind, the Tailwind wording in the README, the Boost guidelines and the development skill, and about 25 "this used to be a Tailwind utility" comments, along with every "plan step NN" pointer into a gitignored folder. The reset keeps its credit, and NOTICE now carries it too. Feature suite 1159 passed, Chrome browser suite 299 passed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
216 lines
8.9 KiB
PHP
216 lines
8.9 KiB
PHP
<?php
|
|
|
|
namespace NoNameWeb\LivewireMaterial;
|
|
|
|
use Illuminate\Contracts\View\Factory;
|
|
use Illuminate\Pagination\Paginator;
|
|
use Illuminate\Support\Facades\Blade;
|
|
use Illuminate\Support\Facades\Route;
|
|
use Illuminate\Support\ServiceProvider;
|
|
|
|
class LivewireMaterialServiceProvider extends ServiceProvider
|
|
{
|
|
public function register(): void
|
|
{
|
|
$this->mergeConfigFrom(__DIR__.'/../config/livewire-material.php', 'livewire-material');
|
|
|
|
$this->disableBladeIconsComponent();
|
|
$this->registerErrorViews();
|
|
$this->registerMailComponents();
|
|
}
|
|
|
|
public function boot(): void
|
|
{
|
|
$this->loadViewsFrom(__DIR__.'/../resources/views', 'livewire-material');
|
|
|
|
$this->registerComponents();
|
|
$this->registerPagination();
|
|
$this->registerShowcase();
|
|
|
|
if ($this->app->runningInConsole()) {
|
|
$this->commands([
|
|
Console\SchemeCommand::class,
|
|
]);
|
|
|
|
$this->publishes([
|
|
__DIR__.'/../config/livewire-material.php' => config_path('livewire-material.php'),
|
|
], 'livewire-material-config');
|
|
|
|
$this->publishes([
|
|
static::errorViewPath().'/errors' => resource_path('views/errors'),
|
|
], 'livewire-material-errors');
|
|
|
|
$this->publishes([
|
|
static::mailComponentPath().'/html' => resource_path('views/vendor/mail/html'),
|
|
static::mailComponentPath().'/text' => resource_path('views/vendor/mail/text'),
|
|
], 'livewire-material-mail');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The anonymous components.
|
|
*/
|
|
public static function componentPath(): string
|
|
{
|
|
return __DIR__.'/../resources/views/components';
|
|
}
|
|
|
|
/**
|
|
* The error-view root: a folder holding only `errors/`.
|
|
*/
|
|
public static function errorViewPath(): string
|
|
{
|
|
return dirname(__DIR__).'/resources/views/error-pages';
|
|
}
|
|
|
|
/**
|
|
* The mail component root, laid out like the framework's: `html/` and `text/`.
|
|
*/
|
|
public static function mailComponentPath(): string
|
|
{
|
|
return dirname(__DIR__).'/resources/views/mail';
|
|
}
|
|
|
|
/**
|
|
* Append the error-view root to `view.paths`, after the application's own.
|
|
*
|
|
* The exception handler replaces the `errors` namespace each time it renders an HTTP
|
|
* exception, with every entry of `view.paths` plus `/errors` and the framework's views last
|
|
* (RegisterErrorViewPaths), so a namespace added with addNamespace() is gone by then; only a
|
|
* view path survives. That config is read at render time, but also once when the view
|
|
* finder is built: appended here, in register(), before any boot() resolves the view
|
|
* factory, the finder and `view:cache` see the same paths the handler does. Appending keeps
|
|
* `view.paths[0]` — Laravel's viewPath() — the application's, and puts the application's
|
|
* own `resources/views/errors` first. Idempotent, since a cached config already holds it.
|
|
*/
|
|
protected function registerErrorViews(): void
|
|
{
|
|
$paths = (array) config('view.paths', []);
|
|
|
|
if (! in_array(static::errorViewPath(), $paths, true)) {
|
|
config(['view.paths' => [...$paths, static::errorViewPath()]]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Put the package's mail header and message after the application's own mail components,
|
|
* when `livewire-material.mail.components` asks for it. Off by default: it changes every
|
|
* Markdown mail the application sends, theme or not, and the framework reads
|
|
* `mail.markdown.paths` once, when the Markdown renderer is first made — after register().
|
|
*/
|
|
protected function registerMailComponents(): void
|
|
{
|
|
if (! config('livewire-material.mail.components')) {
|
|
return;
|
|
}
|
|
|
|
$paths = (array) config('mail.markdown.paths', []);
|
|
|
|
if (! in_array(static::mailComponentPath(), $paths, true)) {
|
|
config(['mail.markdown.paths' => [...$paths, static::mailComponentPath()]]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Register the components as anonymous Blade components under the configured prefix, or
|
|
* under `livewire-material` without one.
|
|
*
|
|
* A compiled `<x-button>` names the view namespace Blade derives from the prefix, or from the
|
|
* component folder's absolute path when there is none. That path differs between a laptop and
|
|
* a container sharing `storage/framework/views`, or between release directories, while the
|
|
* compiled file's name does not — so a view compiled on one showed `a1b2…::button` as text on
|
|
* the other. A prefix does not stop an unprefixed tag resolving.
|
|
*
|
|
* The package's own views never go through this registration: they write
|
|
* `<x-livewire-material::button>`, which resolves through the view namespace whatever the
|
|
* prefix, and which an application's own `<x-button>` cannot shadow.
|
|
*/
|
|
protected function registerComponents(): void
|
|
{
|
|
Blade::anonymousComponentPath(
|
|
static::componentPath(),
|
|
filled(config('livewire-material.prefix')) ? config('livewire-material.prefix') : 'livewire-material',
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Put the M3 paginators in front of Laravel's and Livewire's own, and take over both
|
|
* frameworks' default theme so no application forgets the config and silently renders
|
|
* Laravel's or Livewire's own Tailwind view instead.
|
|
*
|
|
* The namespaces still go through callAfterResolving('view', …): the Factory instance only
|
|
* exists once the `view` service resolves, which may happen before or after this method runs.
|
|
* `Paginator::defaultView()`/`defaultSimpleView()` and `livewire.pagination_theme`, in
|
|
* contrast, are a plain static and a config value — nothing needs resolving first — so they
|
|
* are set here directly, during this provider's boot(). Package providers boot before the
|
|
* application's own (its `bootstrap/providers.php` list is appended after the discovered
|
|
* package providers), so an application that calls `Paginator::defaultView()` again, or sets
|
|
* `livewire.pagination_theme` itself, in its own boot() still wins. Deferring them into the
|
|
* callback instead would risk the opposite: `view` can resolve arbitrarily late — the first
|
|
* render, possibly after the application's own boot() already set its override — and this
|
|
* call would silently overwrite it.
|
|
*
|
|
* `livewire.pagination_theme` is only taken over while it still reads as Livewire's own
|
|
* default, `tailwind` — including when the key is missing entirely, since
|
|
* `SupportPagination::paginationView()` reads it with that same fallback. An application that
|
|
* chose another theme, or already set `material`, keeps it. Either way, a component's own
|
|
* `$paginationTheme` property or `paginationView()` method still wins over the config, because
|
|
* Livewire checks those first.
|
|
*/
|
|
protected function registerPagination(): void
|
|
{
|
|
if (! config('livewire-material.pagination')) {
|
|
return;
|
|
}
|
|
|
|
$this->callAfterResolving('view', function (Factory $view): void {
|
|
$view->prependNamespace('pagination', __DIR__.'/../resources/views/pagination/laravel');
|
|
$view->prependNamespace('livewire', __DIR__.'/../resources/views/pagination/livewire');
|
|
});
|
|
|
|
Paginator::defaultView('pagination::material');
|
|
Paginator::defaultSimpleView('pagination::simple-material');
|
|
|
|
if (config('livewire.pagination_theme', 'tailwind') === 'tailwind') {
|
|
config(['livewire.pagination_theme' => 'material']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* blade-icons registers a class-based <x-icon> of its own, and Blade resolves a
|
|
* registered class alias before any anonymous component path, so ours would never
|
|
* render. It reads the key in its boot(); a booting callback runs after every
|
|
* register() — so after blade-icons merges its defaults over the key — and before
|
|
* any provider boots.
|
|
*/
|
|
protected function disableBladeIconsComponent(): void
|
|
{
|
|
$this->app->booting(function (): void {
|
|
config(['blade-icons.components.default' => null]);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Mount the showcase when it is enabled.
|
|
*/
|
|
protected function registerShowcase(): void
|
|
{
|
|
// Registered even with the showcase off: `php artisan view:cache` compiles every view in the
|
|
// package's namespace, the showcase's pages included, and <x-showcase::…> must resolve there.
|
|
Blade::anonymousComponentPath(__DIR__.'/../resources/views/showcase/components', 'showcase');
|
|
|
|
if (! config('livewire-material.showcase.enabled')) {
|
|
return;
|
|
}
|
|
|
|
if ($this->app->routesAreCached()) {
|
|
return;
|
|
}
|
|
|
|
Route::middleware(config('livewire-material.showcase.middleware'))
|
|
->prefix(config('livewire-material.showcase.path'))
|
|
->name('livewire-material.')
|
|
->group(__DIR__.'/../routes/showcase.php');
|
|
}
|
|
}
|