Plan step 23 (containment), containment.md § Missing: "no divider pinned between a scrolling body and the header/actions". Every dialog now draws a 1px outline-variant rule under the header once the body is scrolled away from its top, and over the actions while more of it is below; a body that fits shows neither. The rules are pseudo-elements in the rows' own padding (dialog.css), so showing one moves nothing, and x-dialog-dividers (dialog.js) marks the wire:ignore.self <dialog>, which a morph leaves alone, watching scroll and a ResizeObserver on the body and a wrapper around the slot. The M3 gaps are split around the rules (8/8 under the header, 8/16 over the actions). A full-screen dialog's rule sits under its phone bar, and its action bar's always-on border follows the scroll too. `separator` now means "draw both rules always" instead of rendering two <x-divider> elements. The fade uses the effects-fast token, zero under reduced motion. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qwx5USif3wFFmxtHg5U1g9
59 lines
2.6 KiB
CSS
59 lines
2.6 KiB
CSS
/*
|
|
* `<x-modal>`'s dividers: the rule under the pinned header and the rule over the pinned actions.
|
|
*
|
|
* M3 lists a divider in the anatomy of both dialogs, 1dp high (docs/reference/m3/
|
|
* components-actions-communication-containment.md § Dialogs → Anatomy, Specs), in outline-variant
|
|
* (DividerTokens: Color = OutlineVariant, Thickness = 1dp, androidx Compose Material 3,
|
|
* Apache-2.0). A scrolling dialog keeps its title and buttons pinned and scrolls only what is
|
|
* between them (§ Dialogs → Behaviour), and a rule there only says something while content is
|
|
* hidden on its far side: the one under the header shows once the body is scrolled away from its
|
|
* top, the one over the actions while more of the body is below. resources/js/dialog.js marks the
|
|
* `<dialog>` with `data-overflow-top` and `data-overflow-bottom`; a row with `data-separator` (the
|
|
* `separator` prop) draws its rule whatever the scroll.
|
|
*
|
|
* Each rule is a pseudo-element laid over the edge of its own row's padding, as Material Web's
|
|
* dialog lays its dividers at the bottom of the headline and the top of the actions
|
|
* (material-components/material-web, dialog/internal/_dialog.scss): it takes no room, so showing
|
|
* it moves nothing, and it lies outside the body's scrollport, so no content scrolls over it. The
|
|
* rule is full-bleed, the width of the dialog. Only the head and actions of a dialog that has a
|
|
* body carry the hooks (resources/views/components/modal.blade.php).
|
|
*
|
|
* The child combinators tie a mark to its own dialog's rows, never to those of a dialog opened
|
|
* from inside its body. The rule fades on the effects-fast token, which reduced motion sets to
|
|
* zero (resources/css/tokens/motion.css), so there it simply appears.
|
|
*/
|
|
|
|
@layer components {
|
|
[data-dialog-head],
|
|
[data-dialog-actions] {
|
|
position: relative;
|
|
}
|
|
|
|
[data-dialog-head]::after,
|
|
[data-dialog-actions]::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset-inline: 0;
|
|
height: 1px;
|
|
background-color: var(--md-sys-color-outline-variant);
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity var(--md-sys-motion-effects-fast-duration) var(--md-sys-motion-effects-fast);
|
|
}
|
|
|
|
[data-dialog-head]::after {
|
|
bottom: 0;
|
|
}
|
|
|
|
[data-dialog-actions]::before {
|
|
top: 0;
|
|
}
|
|
|
|
dialog[data-overflow-top] > * > [data-dialog-head]::after,
|
|
dialog[data-overflow-bottom] > * > [data-dialog-actions]::before,
|
|
[data-dialog-head][data-separator]::after,
|
|
[data-dialog-actions][data-separator]::before {
|
|
opacity: 1;
|
|
}
|
|
}
|