@rcarls/rc-dialog 0.0.0-next-20260921045401
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +139 -0
- package/README.md +348 -0
- package/dist/custom-elements.json +907 -0
- package/dist/rc-dialog-BwAPVpoU.js +426 -0
- package/dist/rc-dialog-BwAPVpoU.js.map +1 -0
- package/dist/rc-dialog-define.js +9 -0
- package/dist/rc-dialog-define.js.map +1 -0
- package/dist/rc-dialog.js +7 -0
- package/dist/rc-dialog.js.map +1 -0
- package/dist/types/packages/rc-dialog/src/define.d.ts +1 -0
- package/dist/types/packages/rc-dialog/src/dialogBaseStyles.d.ts +3 -0
- package/dist/types/packages/rc-dialog/src/index.d.ts +2 -0
- package/dist/types/packages/rc-dialog/src/rc-dialog.d.ts +232 -0
- package/package.json +65 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# @rcarls/rc-dialog
|
|
2
|
+
|
|
3
|
+
## 0.0.0-next-20260921045401
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 4341311: Add reusable fullscreen dialogs and declarative dialog popup mode to `rc-select` and
|
|
8
|
+
`rc-combobox`. Dialog selection and creation are staged until Done, while close and Escape
|
|
9
|
+
discard the draft. Fullscreen surfaces follow the visual viewport under browser zoom and
|
|
10
|
+
software keyboards, and anchored popups now clamp to the visual viewport.
|
|
11
|
+
- 2c7c80a: Give `rc-dialog` a surface styling contract.
|
|
12
|
+
|
|
13
|
+
It exposed only `--rc-dialog-scrim`, so both themes drew the surface by selecting the native
|
|
14
|
+
`<dialog>` directly. It now installs a light-DOM base stylesheet, the same pattern as `rc-button`
|
|
15
|
+
and `rc-disclosure`, and exposes minimum and maximum inline and block bounds, padding, border, radius, background,
|
|
16
|
+
foreground, and shadow, plus fullscreen-specific padding and background for the two values that
|
|
17
|
+
variant genuinely needs of its own.
|
|
18
|
+
|
|
19
|
+
Fullscreen geometry, meaning position, size, margin, radius, and shadow, stays structural rather
|
|
20
|
+
than something a theme sets: filling the visual viewport is what keeps the surface correct under browser zoom
|
|
21
|
+
and a software keyboard.
|
|
22
|
+
|
|
23
|
+
Material's explicit block bound with `overflow: hidden` moved into the base layer rather than
|
|
24
|
+
staying a theme value. It exists because Chromium does not reliably clip a scrolling element's
|
|
25
|
+
own border radius at the scroll boundary, which is a correctness fix every theme needs.
|
|
26
|
+
|
|
27
|
+
`--rc-dialog-visual-viewport-left`, `-top`, `-width`, and `-height` are no longer documented as
|
|
28
|
+
theme properties. They are geometry the component writes while a fullscreen dialog is open:
|
|
29
|
+
readable if something inside the dialog needs the same measurements, but setting them does not
|
|
30
|
+
move the dialog. They are candidates for private `--_rc-dialog-*` names in a future revision.
|
|
31
|
+
|
|
32
|
+
- 57338f5: `rc-dialog`, `rc-bottom-sheet`, and `rc-snackbar` now fade in and out through
|
|
33
|
+
CSS alone, using `@starting-style` and a discrete `display`/`overlay`
|
|
34
|
+
transition, driven by the shared `--rc-motion-effects-*` tokens (and
|
|
35
|
+
`--rc-motion-spatial-*` for `rc-snackbar`'s slide). `showModal()`/`show()`/
|
|
36
|
+
`close()` all stay synchronous, and nothing in any of the three components
|
|
37
|
+
waits on the transition finishing, so a zero-duration theme or a browser
|
|
38
|
+
without support for the CSS involved both degrade to an instant, fully
|
|
39
|
+
correct open and close.
|
|
40
|
+
|
|
41
|
+
`rc-dialog` and `rc-bottom-sheet` animate opacity only, not a scale or slide
|
|
42
|
+
transform: both support pointer resize and read `getBoundingClientRect()` to
|
|
43
|
+
compute drag deltas, and a transform is a visual effect that
|
|
44
|
+
`getBoundingClientRect()` reports for as long as it's still animating,
|
|
45
|
+
corrupting that measurement if a resize or drag starts during the entrance.
|
|
46
|
+
`rc-snackbar` has no such feature, so it fades and slides up from the
|
|
47
|
+
block-end edge it's anchored to.
|
|
48
|
+
|
|
49
|
+
### Patch Changes
|
|
50
|
+
|
|
51
|
+
- Updated dependencies [50d5610]
|
|
52
|
+
- Updated dependencies [4341311]
|
|
53
|
+
- @rcarls/rc-common@0.0.0-next-20260921045401
|
|
54
|
+
|
|
55
|
+
## 0.6.0
|
|
56
|
+
|
|
57
|
+
### Patch Changes
|
|
58
|
+
|
|
59
|
+
- 57370e4: Align component metadata, aggregate framework typings, development-time native-child
|
|
60
|
+
validation, controlled menu state, package declarations, and public documentation before the
|
|
61
|
+
next release.
|
|
62
|
+
- Updated dependencies [6683eb9]
|
|
63
|
+
- Updated dependencies [ee7ba6c]
|
|
64
|
+
- Updated dependencies [30eb232]
|
|
65
|
+
- @rcarls/rc-common@0.6.0
|
|
66
|
+
|
|
67
|
+
## 0.5.0
|
|
68
|
+
|
|
69
|
+
### Patch Changes
|
|
70
|
+
|
|
71
|
+
- Updated dependencies [4ae2ef0]
|
|
72
|
+
- Updated dependencies [689340c]
|
|
73
|
+
- @rcarls/rc-common@0.5.0
|
|
74
|
+
|
|
75
|
+
## 0.4.2
|
|
76
|
+
|
|
77
|
+
### Patch Changes
|
|
78
|
+
|
|
79
|
+
- @rcarls/rc-common@0.4.2
|
|
80
|
+
|
|
81
|
+
## 0.4.1
|
|
82
|
+
|
|
83
|
+
### Patch Changes
|
|
84
|
+
|
|
85
|
+
- @rcarls/rc-common@0.4.1
|
|
86
|
+
|
|
87
|
+
## 0.4.0
|
|
88
|
+
|
|
89
|
+
### Minor Changes
|
|
90
|
+
|
|
91
|
+
- e57277f: Add Material 3 search-view, fixed-pane splitter, dialog scrim, and resize enhancements.
|
|
92
|
+
|
|
93
|
+
`rc-search-bar` now supports `variant="view"`, controlled/default open state, search-view methods, a rich `suggestions` slot, and datalist-derived text suggestions. `rc-splitter` adds `mode="fixed"` for clampable pixel primary panes. `rc-common` now honors CSS max-size constraints during resize gestures, and `rc-dialog` exposes a scrim token plus resize handle/origin hooks used by themed surfaces.
|
|
94
|
+
|
|
95
|
+
### Patch Changes
|
|
96
|
+
|
|
97
|
+
- Updated dependencies [e57277f]
|
|
98
|
+
- Updated dependencies [ccca8e2]
|
|
99
|
+
- Updated dependencies [037b1b3]
|
|
100
|
+
- @rcarls/rc-common@0.4.0
|
|
101
|
+
|
|
102
|
+
## 0.3.2
|
|
103
|
+
|
|
104
|
+
### Patch Changes
|
|
105
|
+
|
|
106
|
+
- Updated dependencies [88b4086]
|
|
107
|
+
- @rcarls/rc-common@0.3.2
|
|
108
|
+
|
|
109
|
+
## 0.3.1
|
|
110
|
+
|
|
111
|
+
### Patch Changes
|
|
112
|
+
|
|
113
|
+
- @rcarls/rc-common@0.3.1
|
|
114
|
+
|
|
115
|
+
## 0.3.0
|
|
116
|
+
|
|
117
|
+
### Added
|
|
118
|
+
|
|
119
|
+
- Add non-modal `show()` support and the `modal` property for controlled open behavior.
|
|
120
|
+
- Add `rc-dialog-open` and `rc-dialog-toggle` events.
|
|
121
|
+
|
|
122
|
+
### Changed
|
|
123
|
+
|
|
124
|
+
- Warn in development when the inner `<dialog>` has no enabled button.
|
|
125
|
+
- Update package metadata, README intro, and docs links.
|
|
126
|
+
|
|
127
|
+
### Fixed
|
|
128
|
+
|
|
129
|
+
- Restore focus to `document.body` when the opener is removed while the dialog is open.
|
|
130
|
+
|
|
131
|
+
### Dependencies
|
|
132
|
+
|
|
133
|
+
- Sync internal dependencies to 0.3.0.
|
|
134
|
+
|
|
135
|
+
## 0.2.0
|
|
136
|
+
|
|
137
|
+
### Patch Changes
|
|
138
|
+
|
|
139
|
+
- @rcarls/rc-common@0.2.0
|
package/README.md
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
# `@rcarls/rc-dialog`
|
|
2
|
+
|
|
3
|
+
Draggable, resizable wrapper for a native `<dialog>`, following the [WAI-ARIA Dialog Modal pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/).
|
|
4
|
+
|
|
5
|
+
Docs: [https://richardcarls.github.io/rc-webcomponents/components/rc-dialog](https://richardcarls.github.io/rc-webcomponents/components/rc-dialog).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @rcarls/rc-dialog
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Import
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
import '@rcarls/rc-dialog'; // side-effect: registers <rc-dialog>
|
|
19
|
+
import { RCDialog } from '@rcarls/rc-dialog'; // named class export
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Basic usage
|
|
25
|
+
|
|
26
|
+
Place a `<dialog>` element directly inside `<rc-dialog>`. The inner `<dialog>` must have
|
|
27
|
+
`aria-labelledby` or `aria-label` to satisfy the
|
|
28
|
+
[WAI-ARIA Dialog pattern](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/).
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<rc-dialog id="my-dialog">
|
|
32
|
+
<dialog aria-labelledby="dlg-title">
|
|
33
|
+
<h2 id="dlg-title">Hello</h2>
|
|
34
|
+
<p>Dialog content.</p>
|
|
35
|
+
<button onclick="document.querySelector('#my-dialog').close()">Close</button>
|
|
36
|
+
</dialog>
|
|
37
|
+
</rc-dialog>
|
|
38
|
+
|
|
39
|
+
<button onclick="document.querySelector('#my-dialog').showModal()">Open</button>
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## API
|
|
45
|
+
|
|
46
|
+
### Properties / attributes
|
|
47
|
+
|
|
48
|
+
| Property | Attribute | Type | Default | Description |
|
|
49
|
+
| ----------------- | ------------------ | -------------------------------------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| `open` | `open` | `boolean` | None | Controlled open state. Setting to `true`/`false` opens/closes the dialog silently (no `rc-dialog-toggle` event). Reads the inner `<dialog>.open` value. |
|
|
51
|
+
| `defaultOpen` | `default-open` | `boolean` | `false` | Uncontrolled initial open state. The component takes ownership after initialization. |
|
|
52
|
+
| `variant` | `variant` | `'standard' \| 'fullscreen'` | `'standard'` | Surface presentation. Fullscreen themes the direct child dialog to the current visual viewport and disables move/resize controllers. |
|
|
53
|
+
| `modal` | None | `boolean` | `true` | Whether controlled `open` / `defaultOpen` opens as modal (`showModal`) or non-modal (`show`). No effect on direct `showModal()` / `show()` calls. JS property only; no attribute. |
|
|
54
|
+
| `movable` | `movable` | `boolean` | `false` | Enable drag-to-move. Named `movable` (not `draggable`) to avoid colliding with the HTML `draggable` attribute. |
|
|
55
|
+
| `moveHandle` | `move-handle` | `string` | `''` | CSS selector for the drag handle within the inner `<dialog>` (for example, `'.titlebar'`). Defaults to the whole dialog. |
|
|
56
|
+
| `moveBounds` | `move-bounds` | `'viewport' \| 'parent'` | `'viewport'` | Constrains drag within the viewport or the nearest positioned ancestor. |
|
|
57
|
+
| `moveStep` | `move-step` | `number` | `4` | Keyboard arrow-key step in px when moving. Shift multiplies by 10×. |
|
|
58
|
+
| `resize` | `resize` | `'none' \| 'both' \| 'horizontal' \| 'vertical'` | `'none'` | Enables edge/corner resizing, mirroring CSS `resize` semantics. |
|
|
59
|
+
| `resizeOrigin` | `resize-origin` | `'' \| 'top' \| 'right' \| 'bottom' \| 'left' \| 'top-left' \| 'top-right' \| 'bottom-left' \| 'bottom-right'` | `''` | Constrains fallback edge detection or supplies the default origin for explicit handles. Empty keeps free edge detection. |
|
|
60
|
+
| `resizeHandle` | `resize-handle` | `string` | `''` | CSS selector for explicit resize handles within the inner `<dialog>`. Matching handles receive pointer and keyboard resize behavior. |
|
|
61
|
+
| `resizeThreshold` | `resize-threshold` | `number` | `8` | Edge hit-test band in px (straddles the border, half inside and half outside). |
|
|
62
|
+
| `resizeStep` | `resize-step` | `number` | `4` | Keyboard arrow-key step in px when resizing. Shift multiplies by 10×. |
|
|
63
|
+
| `closedBy` | `closed-by` | `'any' \| 'closerequest' \| 'none' \| ''` | `''` | Proxied to the inner `<dialog closedby="...">` attribute (Chrome 134+, Safari 18.4+, Firefox 139+). `'any'` = Escape or backdrop click; `'closerequest'` = Escape only; `'none'` = programmatic only. |
|
|
64
|
+
| `lightDismiss` | `light-dismiss` | `boolean` | `false` | JS fallback for backdrop-click dismissal. Detects clicks whose target is the `<dialog>` element itself and calls `requestClose()`. Works in all browsers alongside or instead of `closed-by`. |
|
|
65
|
+
|
|
66
|
+
### Methods
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
showModal(): void // Opens as modal (traps focus, shows backdrop).
|
|
70
|
+
show(): void // Opens as non-modal.
|
|
71
|
+
close(returnValue?): void // Closes immediately; sets returnValue.
|
|
72
|
+
requestClose(returnValue?): void
|
|
73
|
+
// Requests close: fires rc-dialog-request-close first. If not prevented,
|
|
74
|
+
// proceeds to close. Falls back to a synthesized cancel event on older
|
|
75
|
+
// browsers that lack native HTMLDialogElement.requestClose().
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Read-only getters
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
open: boolean; // Whether the inner <dialog> is currently open.
|
|
82
|
+
returnValue: string; // The return value set when the dialog last closed.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Events
|
|
86
|
+
|
|
87
|
+
| Event | Cancelable | Detail | Description |
|
|
88
|
+
| ------------------------- | ---------- | ---------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
89
|
+
| `rc-dialog-open` | No | None | Fired when the dialog opens via `showModal()` or `show()`. |
|
|
90
|
+
| `rc-dialog-toggle` | No | `{ open: boolean, returnValue: string }` | Fired when user or native interaction changes the open state. Not fired on silent host writes (`open` property). |
|
|
91
|
+
| `rc-dialog-request-close` | **Yes** | `{ returnValue: string }` | Fired before close (Escape, backdrop click, or `requestClose()`). Call `preventDefault()` to block. |
|
|
92
|
+
| `rc-dialog-cancel` | No | None | Fired after `rc-dialog-request-close` when the close was not prevented. Backward-compatible alias. |
|
|
93
|
+
| `rc-dialog-close` | No | `{ returnValue: string }` | Fired after the dialog has closed. |
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
### Theming
|
|
98
|
+
|
|
99
|
+
Set the `--rc-dialog-min-inline-size`, `--rc-dialog-max-inline-size`, and
|
|
100
|
+
`--rc-dialog-max-block-size` surface bounds on `<rc-dialog>` or a theme scope.
|
|
101
|
+
The component also exposes surface padding, border, radius, background,
|
|
102
|
+
foreground, shadow, and `--rc-dialog-scrim` properties. Theme packages map
|
|
103
|
+
their design-system values onto those public properties.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Examples
|
|
108
|
+
|
|
109
|
+
### Movable dialog with a drag handle
|
|
110
|
+
|
|
111
|
+
```html
|
|
112
|
+
<rc-dialog id="dlg" movable move-handle=".titlebar">
|
|
113
|
+
<dialog aria-labelledby="dlg-title">
|
|
114
|
+
<div class="titlebar">
|
|
115
|
+
<span id="dlg-title">Settings</span>
|
|
116
|
+
<button onclick="document.querySelector('#dlg').close()">✕</button>
|
|
117
|
+
</div>
|
|
118
|
+
<div class="body">…</div>
|
|
119
|
+
</dialog>
|
|
120
|
+
</rc-dialog>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Drag the `.titlebar` to reposition. Focus the titlebar and use **Arrow keys** to move it
|
|
124
|
+
(Shift = 10× step).
|
|
125
|
+
|
|
126
|
+
### Fullscreen dialog
|
|
127
|
+
|
|
128
|
+
`variant="fullscreen"` is a separately usable surface variant. It tracks the visual viewport,
|
|
129
|
+
including zoom offsets and software-keyboard resizing, while the dialog is open. The consumer
|
|
130
|
+
still owns the native dialog content and accessible header/actions.
|
|
131
|
+
|
|
132
|
+
```html
|
|
133
|
+
<rc-dialog id="picker" variant="fullscreen">
|
|
134
|
+
<dialog aria-labelledby="picker-title">
|
|
135
|
+
<header>
|
|
136
|
+
<button aria-label="Close" onclick="picker.close('cancel')">✕</button>
|
|
137
|
+
<h2 id="picker-title">Categories</h2>
|
|
138
|
+
<button onclick="picker.close('done')">Done</button>
|
|
139
|
+
</header>
|
|
140
|
+
</dialog>
|
|
141
|
+
</rc-dialog>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Movable + resizable
|
|
145
|
+
|
|
146
|
+
```html
|
|
147
|
+
<rc-dialog id="dlg" movable move-handle=".titlebar" resize="both">
|
|
148
|
+
<dialog aria-labelledby="dlg-title" style="min-width: 20rem; min-height: 10rem;">…</dialog>
|
|
149
|
+
</rc-dialog>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
All eight resize handles are active (top, right, bottom, left, and the four corners).
|
|
153
|
+
Opposite-edge handles anchor the far side and move `left` or `top`, mirroring OS-window resize
|
|
154
|
+
semantics. A small keyboard-accessible resize button is injected at the bottom-right corner.
|
|
155
|
+
|
|
156
|
+
### Explicit resize handle
|
|
157
|
+
|
|
158
|
+
```html
|
|
159
|
+
<rc-dialog id="sheet-like" resize="vertical" resize-origin="top" resize-handle=".resize-handle">
|
|
160
|
+
<dialog aria-labelledby="sheet-title">
|
|
161
|
+
<button
|
|
162
|
+
type="button"
|
|
163
|
+
class="resize-handle"
|
|
164
|
+
data-rc-dialog-resize-axis="y"
|
|
165
|
+
data-rc-dialog-resize-origin="top"
|
|
166
|
+
aria-label="Resize"
|
|
167
|
+
></button>
|
|
168
|
+
<h2 id="sheet-title">Filters</h2>
|
|
169
|
+
</dialog>
|
|
170
|
+
</rc-dialog>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Use `data-rc-dialog-resize-axis="x|y|both"` and
|
|
174
|
+
`data-rc-dialog-resize-origin="top|right|bottom|left|top-left|..."`
|
|
175
|
+
on individual handles to override the host resize direction or origin.
|
|
176
|
+
|
|
177
|
+
### Minimal / no-header dialog
|
|
178
|
+
|
|
179
|
+
No titlebar or footer structure is required. Any layout is valid.
|
|
180
|
+
|
|
181
|
+
```html
|
|
182
|
+
<rc-dialog id="dlg">
|
|
183
|
+
<dialog
|
|
184
|
+
aria-labelledby="dlg-title"
|
|
185
|
+
aria-describedby="dlg-desc"
|
|
186
|
+
style="border-radius: 12px; padding: 2rem; position: relative;"
|
|
187
|
+
>
|
|
188
|
+
<button
|
|
189
|
+
style="position:absolute;top:.5rem;right:.5rem;"
|
|
190
|
+
onclick="document.querySelector('#dlg').close()"
|
|
191
|
+
aria-label="Close"
|
|
192
|
+
>
|
|
193
|
+
✕
|
|
194
|
+
</button>
|
|
195
|
+
<h3 id="dlg-title">Quick note</h3>
|
|
196
|
+
<p id="dlg-desc">Content here.</p>
|
|
197
|
+
</dialog>
|
|
198
|
+
</rc-dialog>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Alert / confirm dialog
|
|
202
|
+
|
|
203
|
+
Use `role="alertdialog"` with `aria-describedby` pointing to the message text. Assistive
|
|
204
|
+
technology treats this with higher urgency.
|
|
205
|
+
|
|
206
|
+
```html
|
|
207
|
+
<rc-dialog id="confirm">
|
|
208
|
+
<dialog role="alertdialog" aria-labelledby="confirm-title" aria-describedby="confirm-msg">
|
|
209
|
+
<div class="titlebar"><span id="confirm-title">Delete item?</span></div>
|
|
210
|
+
<p id="confirm-msg">This action cannot be undone.</p>
|
|
211
|
+
<div class="footer">
|
|
212
|
+
<button onclick="document.querySelector('#confirm').close('cancel')">Cancel</button>
|
|
213
|
+
<button onclick="document.querySelector('#confirm').close('delete')">Delete</button>
|
|
214
|
+
</div>
|
|
215
|
+
</dialog>
|
|
216
|
+
</rc-dialog>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Native form integration
|
|
220
|
+
|
|
221
|
+
`<form method="dialog">` submits to the dialog: the submit button's `value` becomes
|
|
222
|
+
`returnValue`, and the dialog closes without JavaScript.
|
|
223
|
+
|
|
224
|
+
```html
|
|
225
|
+
<rc-dialog id="dlg">
|
|
226
|
+
<dialog aria-labelledby="dlg-title">
|
|
227
|
+
<div class="titlebar">
|
|
228
|
+
<span id="dlg-title">New contact</span>
|
|
229
|
+
<button formmethod="dialog" form="contact-form" value="cancel">✕</button>
|
|
230
|
+
</div>
|
|
231
|
+
<form id="contact-form" method="dialog">
|
|
232
|
+
<label>Name <input name="name" type="text" /></label>
|
|
233
|
+
<label>Email <input name="email" type="email" /></label>
|
|
234
|
+
</form>
|
|
235
|
+
<div class="footer">
|
|
236
|
+
<button formmethod="dialog" form="contact-form" value="cancel">Cancel</button>
|
|
237
|
+
<button type="submit" form="contact-form" value="save">Save</button>
|
|
238
|
+
</div>
|
|
239
|
+
</dialog>
|
|
240
|
+
</rc-dialog>
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Cancelable close guard (unsaved changes)
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<rc-dialog id="dlg" movable move-handle=".titlebar">
|
|
247
|
+
<dialog aria-labelledby="dlg-title">
|
|
248
|
+
<div class="titlebar">
|
|
249
|
+
<span id="dlg-title">Edit note</span>
|
|
250
|
+
<!-- requestClose() fires rc-dialog-request-close; close() bypasses it -->
|
|
251
|
+
<button onclick="document.querySelector('#dlg').requestClose('cancel')">✕</button>
|
|
252
|
+
</div>
|
|
253
|
+
<textarea id="note" rows="5"></textarea>
|
|
254
|
+
<p id="warning" hidden role="alert">Unsaved changes; save or discard first.</p>
|
|
255
|
+
<div class="footer">
|
|
256
|
+
<button onclick="note.value=''; dlg.close('discard')">Discard</button>
|
|
257
|
+
<button onclick="dlg.close('save')">Save</button>
|
|
258
|
+
</div>
|
|
259
|
+
</dialog>
|
|
260
|
+
</rc-dialog>
|
|
261
|
+
|
|
262
|
+
<script>
|
|
263
|
+
document.querySelector('#dlg').addEventListener('rc-dialog-request-close', (e) => {
|
|
264
|
+
if (document.querySelector('#note').value.trim()) {
|
|
265
|
+
e.preventDefault();
|
|
266
|
+
document.querySelector('#warning').hidden = false;
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
</script>
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Light-dismiss (click backdrop to close)
|
|
273
|
+
|
|
274
|
+
```html
|
|
275
|
+
<!-- Native (Chrome 134+ / Safari 18.4+ / Firefox 139+) + JS fallback -->
|
|
276
|
+
<rc-dialog id="dlg" closed-by="any" light-dismiss>
|
|
277
|
+
<dialog aria-labelledby="dlg-title" aria-describedby="dlg-body">
|
|
278
|
+
<div class="header"><span id="dlg-title">Info</span></div>
|
|
279
|
+
<div id="dlg-body">Click the backdrop or press Escape to dismiss.</div>
|
|
280
|
+
</dialog>
|
|
281
|
+
</rc-dialog>
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
`closed-by="any"` delegates to the browser's native light-dismiss on supporting browsers.
|
|
285
|
+
`light-dismiss` adds a JS click-on-backdrop fallback that works everywhere and routes through
|
|
286
|
+
`requestClose()`, so close guards still apply.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
290
|
+
## Accessibility
|
|
291
|
+
|
|
292
|
+
`<rc-dialog>` delegates entirely to the native `<dialog>` element:
|
|
293
|
+
|
|
294
|
+
- **Focus trapping:** Built into `showModal()` (Tab/Shift+Tab stays inside the dialog).
|
|
295
|
+
- **Focus restoration:** `rc-dialog` captures the focused element before opening and restores
|
|
296
|
+
focus to it on close. If the opener was removed from the DOM while the dialog was open, focus
|
|
297
|
+
falls back to `document.body`.
|
|
298
|
+
- **Escape to close:** Native behavior routes through `rc-dialog-request-close` so guards still
|
|
299
|
+
apply.
|
|
300
|
+
- **`aria-modal`:** `showModal()` implies `aria-modal="true"` without an explicit attribute.
|
|
301
|
+
|
|
302
|
+
**Required:** The inner `<dialog>` must have `aria-labelledby` or `aria-label`. In development
|
|
303
|
+
mode the component logs a console warning if either is absent.
|
|
304
|
+
|
|
305
|
+
**Alert dialogs:** When `role="alertdialog"` is set, add `aria-describedby` pointing to the
|
|
306
|
+
message text. A dev-mode warning fires if it is missing.
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## CSS layout for scrollable dialogs
|
|
311
|
+
|
|
312
|
+
When the dialog needs scrollable body content (especially when resizable), use flex layout so
|
|
313
|
+
the body grows and the header/footer stay fixed:
|
|
314
|
+
|
|
315
|
+
```css
|
|
316
|
+
dialog[open] {
|
|
317
|
+
display: flex;
|
|
318
|
+
flex-direction: column;
|
|
319
|
+
overflow: hidden; /* dialog box doesn't scroll; body does */
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
.dlg-header,
|
|
323
|
+
.dlg-footer {
|
|
324
|
+
flex-shrink: 0;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.dlg-body {
|
|
328
|
+
flex: 1;
|
|
329
|
+
overflow: auto;
|
|
330
|
+
min-height: 0; /* lets flex child shrink below content height */
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
> **Note:** `display: flex` must be scoped to `dialog[open]`, not `dialog`. Author styles take
|
|
335
|
+
> precedence over the UA `dialog:not([open]) { display: none }` rule. Scoping to `[open]`
|
|
336
|
+
> prevents all dialogs from being visible on page load.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
## Browser support
|
|
341
|
+
|
|
342
|
+
| Feature | Requirement |
|
|
343
|
+
| ------------------------------------ | --------------------------------------------------------------------------- |
|
|
344
|
+
| Core (`<dialog>` delegation, events) | Chrome 37+, Firefox 98+, Safari 15.4+ |
|
|
345
|
+
| Drag / resize | Any browser supporting Pointer Events |
|
|
346
|
+
| `closed-by` attribute proxy | Chrome 134+, Safari 18.4+, Firefox 139+ |
|
|
347
|
+
| `requestClose()` native method | Chrome 134+, Safari 18.4+, Firefox 139+ (fallback active on older browsers) |
|
|
348
|
+
| `light-dismiss` JS fallback | All browsers |
|