{{-- An M3 dialog, on the native `` opened with `showModal()`. Native because it gets the hard parts right on its own: the top layer above everything, the rest of the page inert, focus moved in and handed back, Escape. The open state is the Livewire property in `wire:model` (entangled live) — a flag (`$confirmingDelete`) or an id (`$deletingShareId`) — and closing writes back whichever "closed" means for it, `false` or `null`. Without `wire:model` it reads and writes `open` in the Alpine scope around it:
`wire:ignore.self`, because `showModal()` sets the `open` attribute, which the server's HTML does not have: without it the next Livewire render morphs the attribute away and the dialog shuts under the person using it. The contents still morph. The body is `wire:ignore.self` for the same reason: `materialShowModal()` (resources/js/dialog.js) gives a text-only body the `tabindex` that lets it hold the first focus, and a render that took it away would drop that focus out of the dialog in WebKit. M3's basic dialog (DialogTokens, androidx Compose Material 3, Apache-2.0): surface-container- high, extra-large corner, elevation 3, a headline-small `title`, body-medium `subtitle` in on-surface-variant, and the `actions` slot at the end. `icon` puts a secondary-coloured icon above a centred title, as M3 draws a dialog with a hero icon. `fullscreen` makes a dialog that holds a form take the whole screen on a compact window (below `medium`, 600px — M3 uses full-screen dialogs "only in compact breakpoints", FullScreenDialogTokens), with a 56px close-and-title bar clear of the notch (docs/audits/m3-alignment/containment.md, C-17). `persistent` ignores Escape and the scrim, for a dialog that must be answered. It opens on the fast spatial spring and closes at once, as M3's do. "Dialog content generally shouldn't scroll; if it must, the title stays pinned at the top and the buttons at the bottom" (docs/reference/m3/components-actions-communication-containment.md § Dialogs → Behaviour, C-06): the header and the action row are their own rows of the flex column and only the body between them scrolls, each with the 24dp padding the box used to carry. Between that body and the rows pinned around it go M3's optional dividers (the 1dp "Divider" in both dialogs' anatomy, § Dialogs → Anatomy, Specs), each only while content is hidden on its far side: the rule under the header once the body is scrolled away from its top, the rule over the actions while more of the body is below, so a body that fits draws neither. On a phone a full-screen dialog's top rule lies under its close-and-title bar, unless a subtitle or an icon keeps the header there, and its action bar's rule follows the scroll like any other. `separator` draws both rules whatever the scroll. The gaps M3 gives are split around the rules — 8px above and 8px below the top one (16dp title to body), 8px above and 16px below the bottom one (24dp body to actions) — so the scrolling body keeps room inside its edges for a floating label or a focus ring, and a rule that appears moves nothing. Without a body there is nothing to divide, and no rule. resources/css/components/modal.css draws the rules, keyed on `data-md-modal-divider` (only where a body exists to divide, the bar's own flag computed in PHP because it alone can sit beside a header that is *also* on screen) and `data-md-separator`; `x-dialog-dividers` (resources/js/dialog.js), on the body, watches it and the element wrapped around the slot, so content a Livewire render adds counts, and marks the `` with `data-md-overflow-top`/`data-md-overflow-bottom`. `alert` is M3's "on web, basic dialogs should have the alert dialog role" (C-20): it sets `role="alertdialog"` and points `aria-describedby` at the body, for the dialog that interrupts to say something important. It is opt-in, because ARIA-APG keeps `alertdialog` for exactly that and a form dialog would over-announce with it. A `subtitle` is always the dialog's description. The root renders `data-md-modal` and `data-md-fullscreen`; its parts are `data-md-modal-*`, drawn by resources/css/components/modal.css: `-box`, `-bar` (the phone-only header bar), `-head`, `-title`, `-subtitle`, `-body`, `-content` (the resize-observed wrapper around the slot), `-actions`. `box-class` lands on the box. --}} @props([ 'title' => null, 'subtitle' => null, 'icon' => null, 'separator' => false, 'persistent' => false, 'fullscreen' => false, 'alert' => false, 'boxClass' => null, ]) @php $model = $attributes->wire('model')->value() ?: null; $id = $attributes->get('id') ?? 'material-dialog-'.substr(md5($model.'|'.$title), 0, 10); $header = filled($title) || filled($subtitle) || $icon; $body = $slot->isNotEmpty(); // On a phone a full-screen dialog's bar names it, and the header hides unless an icon or a subtitle keeps it. $barAboveBody = $fullscreen && (! $header || (! $icon && blank($subtitle))); $describedBy = implode(' ', array_filter([ filled($subtitle) ? $id.'-subtitle' : null, $alert && $body ? $id.'-body' : null, ])); @endphp whereDoesntStartWith('wire:model')->except(['id'])->merge(array_filter([ 'id' => $id, 'data-md-modal' => true, 'data-md-fullscreen' => $fullscreen ? true : null, ], fn ($value): bool => $value !== null)) }} >
@if ($fullscreen)
@if (filled($title)) {{ $title }} @endif
@endif @if ($header) {{-- A full-screen dialog's bar names it on a phone, so only the subtitle stays there. --}}
@if ($icon) @endif @if (filled($title))

{{ $title }}

@endif @if (filled($subtitle))

{{ $subtitle }}

@endif
@endif @if ($body)
{{ $slot }}
@endif @isset($actions)
{{ $actions }}
@endisset