Bind the collapse's open state through wire:model and x-model
An application keeps a disclosure's state in a Livewire property (`<x-collapse wire:model="fineTuning">`) or an Alpine one. Either binding now works both ways: toggling the details writes the property, and the property changing, in an action or in Alpine, opens or closes it. `wire:model` is entangled as the dialog and sheets entangle theirs, and taken off the element. The server renders `open` from the property, so the first paint matches it. `x-model` binds through `x-modelable`, with `open` as the first paint until Alpine starts. The state is named `collapseOpen` so it never hides an `open` that a dialog in the slot reads from a scope around it. Without a binding the element has no Alpine and renders as before. 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
973cc0b68c
commit
e63c0f684c
@@ -1,5 +1,8 @@
|
||||
<?php
|
||||
|
||||
use Livewire\Component;
|
||||
use Livewire\Livewire;
|
||||
|
||||
it('discloses on the native details element, kept open through a morph', function () {
|
||||
$html = (string) $this->blade('<x-collapse title="How long do links last?" icon="schedule" open variant="filled">Until the expiry.</x-collapse>');
|
||||
|
||||
@@ -12,3 +15,50 @@ it('discloses on the native details element, kept open through a morph', functio
|
||||
->toContain('Until the expiry.')
|
||||
->toContain('group-open/collapse:rotate-180');
|
||||
});
|
||||
|
||||
it('has no Alpine on it without a binding', function () {
|
||||
$html = (string) $this->blade('<x-collapse title="Advanced" open>Body</x-collapse>');
|
||||
|
||||
expect($html)
|
||||
->toMatch('/<details\s+wire:ignore.self\s+open\s+class="group\/collapse/')
|
||||
->not->toContain('x-data')
|
||||
->not->toContain('x-modelable')
|
||||
->not->toContain('x-on:toggle')
|
||||
->and((string) $this->blade('<x-collapse title="Advanced">Body</x-collapse>'))
|
||||
->toMatch('/<details\s+wire:ignore.self\s+class="group\/collapse/');
|
||||
});
|
||||
|
||||
it('binds its open state through x-model, drawing the open prop until Alpine starts', function () {
|
||||
$html = (string) $this->blade('<x-collapse title="Advanced" x-model="advanced" open>Body</x-collapse>');
|
||||
|
||||
expect($html)
|
||||
->toMatch('/x-data="{ collapseOpen:\s*true\s*}"/')
|
||||
->toContain('x-modelable="collapseOpen"')
|
||||
->toContain('x-model="advanced"')
|
||||
->toContain('x-effect="$el.open = collapseOpen"')
|
||||
->toContain('x-on:toggle="collapseOpen = $el.open"')
|
||||
->toMatch('/\sopen\s/');
|
||||
});
|
||||
|
||||
it('entangles its open state with a Livewire property and renders open as the property is', function (bool $fineTuning) {
|
||||
Livewire::component('collapse-probe', new class extends Component
|
||||
{
|
||||
public bool $fineTuning = false;
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
return '<div><x-collapse title="Fine-tuning" wire:model.live="fineTuning" :open="! $fineTuning">Body</x-collapse></div>';
|
||||
}
|
||||
});
|
||||
|
||||
$html = Livewire::test('collapse-probe', ['fineTuning' => $fineTuning])->html();
|
||||
|
||||
expect($html)
|
||||
->toContain(".entangle('fineTuning').live")
|
||||
->not->toContain('x-modelable')
|
||||
->not->toContain('wire:model');
|
||||
|
||||
preg_match('/<details[^>]*>/', $html, $tag);
|
||||
|
||||
expect(preg_match('/\sopen\s/', $tag[0]) === 1)->toBe($fineTuning);
|
||||
})->with(['open' => true, 'closed' => false]);
|
||||
|
||||
Reference in New Issue
Block a user