# Modal

Level: organism.

## Purpose and use

Host a focused task in a modal dialog over the current screen. Use Popup for a
nonmodal content surface. The parent controls whether this dialog remains open.

## Anatomy, behavior, and accessibility

- Optional header/title/description and close button surround a scrollable default
  content slot; `[wblModalFooter]` supplies footer actions. XL alone uses customWidth.
- Close-button, Escape and eligible backdrop activation emit closeRequest and
  openedChange(false); the parent must update opened. closable only hides the
  header close button and does not disable Escape/backdrop closure.
- Backdrop closure is blocked if descendant input/textarea controls contain values
  (or checked checkbox/radio inputs). This is a DOM-value heuristic, not dirty-form
  detection; Escape and the close button bypass it.
- The dialog sets aria-modal, traps/autocaptures focus, locks page scrolling and
  restores previous focus when closed. Overflow changes divider presentation.
- Use a meaningful title or ariaLabel; ariaLabel overrides title naming. With no
  header, supply ariaLabel. Footer buttons and their actions belong to the caller.

## Open Questions

Concurrent/nested modals share body overflow without a stack manager. The backdrop
heuristic does not inspect select controls, custom controls or saved-versus-dirty
values; do not treat it as a complete unsaved-change safeguard.

## Local component contract

This is the active owned contract for `component.modal`. The API below
is verified against the current local implementation, focused test and story.
The Source commit `0112cba0779a2d2a4b6c7a17243201694344d70a` remains
migration provenance, not the runtime owner.

### Classes and selectors

- `WblModalComponent` — `wbl-modal` (src/app/design-system/patterns/modal/modal.component.ts).

### Inputs

- `WblModalComponent.opened = input(false, { transform: booleanAttribute })`
- `WblModalComponent.size = input<WblModalSize>('S')`
- `WblModalComponent.customWidth = input('82.5rem')`
- `WblModalComponent.header = input(true, { transform: booleanAttribute })`
- `WblModalComponent.description = input(false, { transform: booleanAttribute })`
- `WblModalComponent.closable = input(true, { transform: booleanAttribute })`
- `WblModalComponent.footer = input(true, { transform: booleanAttribute })`
- `WblModalComponent.title = input('Title')`
- `WblModalComponent.descriptionText = input('Subtitle')`
- `WblModalComponent.ariaLabel = input<string | null>(null)`

### Public types and allowed values

```ts
type WblModalSize = 'S' | 'M' | 'L' | 'XL' | 'Full';
```

### Models

- None.

### Outputs

- `WblModalComponent.openedChange = output<boolean>()`
- `WblModalComponent.closeRequest = output<void>()`

### Slots and projection markers

- `WblModalComponent: <ng-content>`
- `WblModalComponent: <ng-content select="[wblModalFooter]">`

### Local evidence

- Code: `src/app/design-system/patterns/modal/modal.component.ts`.
- Focused test: `src/app/design-system/patterns/modal/modal.component.spec.ts`.
- Public export: `export * from './patterns/modal/modal.component';` in `src/app/design-system/index.ts`.
- Storybook: `storybook/stories/modal.stories.ts` (`Design System/Patterns/wbl-modal`).

## Figma status

This runtime contract does not infer a Figma owner. Assembly evidence, when
verified, is maintained separately through the inventory-linked sidecar.

## Provenance

The archived source-document copy is historical evidence only. The active
resolver is local code/test/export and the exact Storybook story above.

## M-002 refinement

The Transport Requests delete confirmation keeps `title` as the dialog name and
moves its explanatory copy into a normal `<p>` projected inside
`.wbl-modal__content`. The integration does not use `description` or
`descriptionText`; the footer remains projected through `[wblModalFooter]`.
This current screen composition adds no Modal input, output, or slot and is
verified against Figma nodes `17034:12382` and `17034:12383` in file
`uGBDiLA947AJwkuvpohikQ`.

## M-002 delete body typography

The projected explanatory `<p>` in the Transport Requests delete dialog uses
the regular `--typography-body-horse` role and does not inherit bold or accent
weight from the title or footer. This current screen composition adds no Modal
API and is covered by the feature's focused test.
