You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Jan 13, 2025. It is now read-only.
- Remove all focus trapping logic within the dialog foundation,
replacing it with adapter methods set up to handle focus trapping.
- Add [focus-trap](https://npmjs.com/focus-trap) as a dependency and use
it for implementing the adapter methods at the component level
- Add a util method for easily creating properly configured focus-trap
instances that framework components can use when implementing their
adapters.
- Remove use of aria-hidden and replace with visibility styles so that
tab-focusing works correctly when dialog is hidden.
- [techdebt] Remove all unnecessary adapter API methods and dead code
- [techdebt] Make grammatical corrections in README
- [techdebt] Remove unneeded passive event listener logic for dialog handlers
- [demos] Fix ripples incorrectly appearing in buttons within the demos.
Resolves#424Resolves#409Resolves#426
NOTE: The additional issues resolved are by-products from switching to
focus-trap.
BREAKING CHANGE: There are a few changes that need to be taken into
account for this commit:
- Dialogs no longer require an `aria-hidden="true"` attribute.
- Dialogs _do_ require a `style="visibility:hidden"` attribute for
correct first render.
- `trapFocusOnSurface` and `untrapFocusOnSurface` methods must be
implemented for the adapter
- `hasClass`, `setAttr`, `registerFocusTrappingHandler`,
`deregisterFocusTrappingHandler`, `numFocusableTargets`,
`setDialogFocusFirstTarget`, `setInitialFocus`,
`getFocusableElements`, `saveElementTabState`,
`restoreElementTabState`, `makeElementUntabbable`, `setBodyAttr`,
`rmBodyAttr`, `getFocusedTarget`, and `setFocusedTarget` have all been
removed from the adapter.
- `applyPassive`, `saveElementTabState`, and `restoreElementTabState`
have all been removed from `mdcDialog.util`.
Dialogs can easily be initialized using their default constructors as well, similar to `attachTo`.
142
+
Dialogs can easily be initialized using their default constructors as well, similar to `attachTo`.
143
143
144
144
```javascript
145
145
import {MDCDialog} from'mdc-dialog';
@@ -167,27 +167,13 @@ document.querySelector('#default-dialog-activation').addEventListener('click', f
167
167
168
168
### Dialog component API
169
169
170
-
#### MDCDialog.open
170
+
#### MDCDialog.open
171
171
172
172
Boolean. True when the dialog is shown, false otherwise.
173
173
174
-
#### MDCDialog.lastFocusedTarget
175
-
176
-
EventTarget, usually an HTMLElement. Represents the element that was focused on the page before the dialog is shown. If set,
177
-
the dialog will return focus to this element when closed. _This property should be set before calls to show()_.
178
-
179
-
180
-
#### MDCDialog.initialize() => void
181
-
182
-
Attaches ripples to the dialog footer buttons
183
-
184
-
#### MDCDialog.destroy() => void
185
-
186
-
Cleans up ripples when dialog is destroyed
187
-
188
174
#### MDCDialog.show() => void
189
175
190
-
Shows the dialog
176
+
Shows the dialog
191
177
192
178
#### MDCDialog.close() => void
193
179
@@ -208,17 +194,16 @@ Broadcast when a user actions on the `.mdc-dialog__footer__button--cancel` eleme
208
194
MDC Dialog ships with an `MDCDialogFoundation` class that external frameworks and libraries can
209
195
use to integrate the component. As with all foundation classes, an adapter object must be provided.
210
196
211
-
> **NOTE**: Components themselves must manage adding ripples to dialog buttons, should they choose to
197
+
> **NOTE**: Components themselves must manage adding ripples to dialog buttons, should they choose to
212
198
do so. We provide instructions on how to add ripples to buttons within the [mdc-button README](https://github.com/material-components/material-components-web/tree/master/packages/mdc-button#adding-ripples-to-buttons).
213
199
214
200
### Adapter API
215
201
216
202
| Method Signature | Description |
217
203
| --- | --- |
218
-
|`hasClass(className: string) => boolean`| Returns boolean indicating whether the root has a given class. |
219
204
|`addClass(className: string) => void`| Adds a class to the root element. |
220
205
|`removeClass(className: string) => void`| Removes a class from the root element. |
221
-
|`setAttr(attr: string, val: string) => void`| Sets the given attribute to the given value on the root element.|
206
+
|`setStyle(propertyName: string, value: string) => void`| Sets a style property `propertyName` on the root element to the `value` specified|
222
207
|`addBodyClass(className: string) => void`| Adds a class to the body. |
223
208
|`removeBodyClass(className: string) => void`| Removes a class from the body. |
224
209
|`eventTargetHasClass(target: EventTarget, className: string) => boolean`| Returns true if target has className, false otherwise. |
@@ -228,45 +213,77 @@ do so. We provide instructions on how to add ripples to buttons within the [mdc-
228
213
|`deregisterSurfaceInteractionHandler(evt: string, handler: EventListener) => void`| Deregisters an event handler from the dialog surface element. |
229
214
|`registerDocumentKeydownHandler(handler: EventListener) => void`| Registers an event handler on the `document` object for a `keydown` event. |
230
215
|`deregisterDocumentKeydownHandler(handler: EventListener) => void`| Deregisters an event handler on the `document` object for a `keydown` event. |
231
-
|`registerFocusTrappingHandler(handler: EventListener) => void`| Registers a focus event listener with a given handler at the capture phase on the document. |
232
-
|`deregisterFocusTrappingHandler(handler: EventListener) => void`| Deregisters a focus event listener from a given handler at the capture phase on the document. |
233
-
|`numFocusableTargets() => number`| Returns the number of focusable elements in the dialog |
234
-
|`setDialogFocusFirstTarget() => void`| Resets focus to the first focusable element in the dialog |
235
-
|`setInitialFocus() => void`| Sets focus on the `mdc-dialog__footer__button--accept` element. |
236
-
|`getFocusableElements() => Array<Element>`| Returns the list of focusable elements inside the dialog. |
237
-
|`saveElementTabState(el: Element) => void`| Saves the current tab index for the element in a way which it can be retrieved later on. |
238
-
|`restoreElementTabState(el: Element) => void`| Restores the saved tab index (if any) for an element. |
239
-
|`makeElementUntabbable(el: Element) => void`| Makes an element untabbable, e.g. by setting the `tabindex` to `-1`. |
240
-
|`setBodyAttr(attr: string, val: string) => void`| Sets the given attribute to the given value on the body element. |
241
-
|`rmBodyAttr(attr: string) => void`| Removes the given attribute from the body element. |
242
-
|`getFocusedTarget() => Element`| Returns the currently focused element, e.g. `document.activeElement`. |
243
-
|`setFocusedTarget(target: EventTarget) => void`| Sets focus on the given target, e.g. by calling `focus()`|
244
216
|`notifyAccept() => {}`| Broadcasts an event denoting that the user has accepted the dialog. |
245
217
|`notifyCancel() => {}`| Broadcasts an event denoting that the user has cancelled the dialog. |
218
+
|`trapFocusOnSurface() => {}`| Sets up the DOM which the dialog is contained in such that focusability is restricted to the elements on the dialog surface (see [Handling Focus Trapping](#handling-focus-trapping) below for more details). |
219
+
|`untrapFocusOnSurface() => {}`| Removes any affects of focus trapping on the dialog surface from the DOM (see [Handling Focus Trapping](#handling-focus-trapping) below for more details). |
220
+
221
+
#### Handling Focus Trapping
246
222
223
+
In order for dialogs to be fully accessible, they must conform to the guidelines outlined in
224
+
https://www.w3.org/TR/wai-aria-practices/#dialog_modal. The main implication of these guidelines is
225
+
that the only focusable elements are those contained within a dialog surface.
226
+
227
+
Trapping focus correctly for a modal dialog requires a complex set of events and interaction
228
+
patterns that we feel is best not duplicated within the logic of this component. Furthermore,
229
+
frameworks and libraries may have their own ways of trapping focus that framework authors may want
230
+
to make use of. For this reason, we have two methods on the adapter that should be used to handle
231
+
focus trapping:
232
+
233
+
-*trapFocusOnSurface()* is called when the dialog is open and should set up focus trapping adhering
234
+
to the ARIA practices in the link above.
235
+
-*untrapFocusOnSurface()* is called when the dialog is closed and should tear down any focus
236
+
trapping set up when the dialog was open.
237
+
238
+
In our `MDCDialog` component, we use the [focus-trap](https://github.com/davidtheclark/focus-trap)
239
+
package to handle this. **You can use [util.createFocusTrapInstance](#mdcdialog-util-api) to easily
240
+
create a focus trapping solution for your component code.**
Given a dialog surface element, an accept button element, and an optional focusTrap factory
270
+
function, creates a properly configured [focus-trap](https://github.com/davidtheclark/focus-trap)
271
+
instance such that:
272
+
273
+
- The focus is trapped within the `surfaceEl`
274
+
- The `acceptButtonEl` receives focus when the focus trap is activated
275
+
- Pressing the `escape` key deactivates focus
276
+
- Clicking outside the dialog deactivates focus
277
+
- Focus is returned to the previously focused element before the focus trap was activated
278
+
279
+
This focus trap instance can be used to implement the `trapFocusOnSurface` and
280
+
`untrapFocusOnSurface` adapter methods by calling `instance.activate()` and `instance.deactivate()`
281
+
respectively within those methods.
282
+
283
+
The `focusTrapFactory` can be used to override the `focus-trap` function used to create the focus
284
+
trap. It's API is the same as focus-trap's [createFocusTrap](https://github.com/davidtheclark/focus-trap#focustrap--createfocustrapelement-createoptions) (which is what it defaults to). You can pass in a custom function for mocking out the
285
+
actual function within tests, or to modify the arguments passed to the function before it's called.
286
+
270
287
## Theming - Dark Theme Considerations
271
288
272
289
When using `mdc-theme--dark` / `mdc-dialog--theme-dark`, the dialog by default sets its background color to `#303030`. You can override this by either overridding the
0 commit comments