pull/667/head
Cory LaViska 2022-02-27 11:46:55 -05:00
rodzic 3fa41ea8d9
commit eb18d759f1
6 zmienionych plików z 74 dodań i 120 usunięć

Wyświetl plik

@ -222,11 +222,11 @@ const App = () => {
### Customizing Initial Focus
By default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element within the dialog. To set focus on a different element, listen for and cancel the `sl-initial-focus` event.
By default, the dialog's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the dialog. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
```html preview
<sl-dialog label="Dialog" class="dialog-focus">
<sl-input placeholder="I will have focus when the dialog is opened"></sl-input>
<sl-input autofocus placeholder="I will have focus when the dialog is opened"></sl-input>
<sl-button slot="footer" variant="primary">Close</sl-button>
</sl-dialog>
@ -240,31 +240,20 @@ By default, the dialog's panel will gain focus when opened. This allows a subseq
openButton.addEventListener('click', () => dialog.show());
closeButton.addEventListener('click', () => dialog.hide());
dialog.addEventListener('sl-initial-focus', event => {
event.preventDefault();
input.focus({ preventScroll: true });
});
</script>
```
```jsx react
import { useRef, useState } from 'react';
import { useState } from 'react';
import { SlButton, SlDialog, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const input = useRef(null);
const [open, setOpen] = useState(false);
function handleInitialFocus(event) {
event.preventDefault();
input.current.focus();
}
return (
<>
<SlDialog label="Dialog" open={open} onSlInitialFocus={handleInitialFocus} onSlAfterHide={() => setOpen(false)}>
<SlInput ref={input} placeholder="I will have focus when the dialog is opened" />
<SlDialog label="Dialog" open={open} onSlAfterHide={() => setOpen(false)}>
<SlInput autofocus placeholder="I will have focus when the dialog is opened" />
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
Close
</SlButton>
@ -276,6 +265,6 @@ const App = () => {
};
```
?> Alternatively, you can add the `autofocus` attribute to any form control to customize initial focus without using JavaScript.
?> You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
[component-metadata:sl-dialog]

Wyświetl plik

@ -410,11 +410,11 @@ const App = () => {
### Customizing Initial Focus
By default, the drawer's panel will gain focus when opened. This allows the first tab press to focus on the first tabbable element within the drawer. To set focus on a different element, listen for and cancel the `sl-initial-focus` event.
By default, the drawer's panel will gain focus when opened. This allows a subsequent tab press to focus on the first tabbable element in the drawer. If you want a different element to have focus, add the `autofocus` attribute to it as shown below.
```html preview
<sl-drawer label="Drawer" class="drawer-focus">
<sl-input placeholder="I will have focus when the drawer is opened"></sl-input>
<sl-input autofocus placeholder="I will have focus when the drawer is opened"></sl-input>
<sl-button slot="footer" variant="primary">Close</sl-button>
</sl-drawer>
@ -428,31 +428,20 @@ By default, the drawer's panel will gain focus when opened. This allows the firs
openButton.addEventListener('click', () => drawer.show());
closeButton.addEventListener('click', () => drawer.hide());
drawer.addEventListener('sl-initial-focus', event => {
event.preventDefault();
input.focus({ preventScroll: true });
});
</script>
```
```jsx react
import { useRef, useState } from 'react';
import { useState } from 'react';
import { SlButton, SlDrawer, SlInput } from '@shoelace-style/shoelace/dist/react';
const App = () => {
const input = useRef(null);
const [open, setOpen] = useState(false);
function handleInitialFocus(event) {
event.preventDefault();
input.current.focus();
}
return (
<>
<SlDrawer label="Drawer" open={open} onSlInitialFocus={handleInitialFocus} onSlAfterHide={() => setOpen(false)}>
<SlInput ref={input} placeholder="I will have focus when the drawer is opened" />
<SlDrawer label="Drawer" open={open} onSlAfterHide={() => setOpen(false)}>
<SlInput autofocus placeholder="I will have focus when the drawer is opened" />
<SlButton slot="footer" variant="primary" onClick={() => setOpen(false)}>
Close
</SlButton>
@ -464,6 +453,5 @@ const App = () => {
};
```
?> Alternatively, you can add the `autofocus` attribute to any form control to customize initial focus without using JavaScript.
?> You can further customize initial focus behavior by canceling the `sl-initial-focus` event and setting focus yourself inside the event handler.
[component-metadata:sl-drawer]

Wyświetl plik

@ -8,6 +8,8 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
## Next
- Improved `autofocus` behavior in Safari for `<sl-dialog>` and `<sl-drawer>` [#693](https://github.com/shoelace-style/shoelace/issues/693)
- Removed feature detection for `focus({ preventScroll })` since it no longer works in Safari
- Removed path aliasing and third-party dependencies that it required
## 2.0.0-beta.70

Wyświetl plik

@ -7,15 +7,12 @@ import { emit, waitForEvent } from '../../internal/event';
import Modal from '../../internal/modal';
import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll';
import { HasSlotController } from '../../internal/slot';
import { isPreventScrollSupported } from '../../internal/support';
import { watch } from '../../internal/watch';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
import { LocalizeController } from '../../utilities/localize';
import '../icon-button/icon-button';
import styles from './dialog.styles';
const hasPreventScroll = isPreventScrollSupported();
/**
* @since 2.0
* @status stable
@ -30,8 +27,8 @@ const hasPreventScroll = isPreventScrollSupported();
* @event sl-after-show - Emitted after the dialog opens and all animations are complete.
* @event sl-hide - Emitted when the dialog closes.
* @event sl-after-hide - Emitted after the dialog closes and all animations are complete.
* @event sl-initial-focus - Emitted when the dialog opens and the panel gains focus. Calling `event.preventDefault()`
* will prevent focus and allow you to set it on a different element in the dialog, such as an input or button.
* @event sl-initial-focus - Emitted when the dialog opens and is ready to receive focus. Calling
* `event.preventDefault()` will prevent focusing and allow you to set it on a different element, such as an input.
* @event {{ source: 'close-button' | 'keyboard' | 'overlay' }} sl-request-close - Emitted when the user attempts to
* close the dialog by clicking the close button, clicking the overlay, or pressing escape. Calling
* `event.preventDefault()` will keep the dialog open. Avoid using this unless closing the dialog will result in
@ -139,17 +136,6 @@ export default class SlDialog extends LitElement {
this.hide();
}
// Sets focus on the first child element with autofocus, falling back to the panel if one isn't found
private setInitialFocus() {
const target = this.querySelector('[autofocus]');
if (target) {
(target as HTMLElement).focus({ preventScroll: true });
} else {
this.panel.focus({ preventScroll: true });
}
}
handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') {
event.stopPropagation();
@ -167,18 +153,38 @@ export default class SlDialog extends LitElement {
lockBodyScrolling(this);
// When the dialog is shown, Safari will attempt to set focus on whatever element has autofocus. This can cause
// the dialogs's animation to jitter (if it starts offscreen), so we'll temporarily remove the attribute, call
// `focus({ preventScroll: true })` ourselves, and add the attribute back afterwards.
//
// Related: https://github.com/shoelace-style/shoelace/issues/693
//
const autoFocusTarget = this.querySelector('[autofocus]');
if (autoFocusTarget) {
autoFocusTarget.removeAttribute('autofocus');
}
await Promise.all([stopAnimations(this.dialog), stopAnimations(this.overlay)]);
this.dialog.hidden = false;
// Browsers that support el.focus({ preventScroll }) can set initial focus immediately
if (hasPreventScroll) {
requestAnimationFrame(() => {
const slInitialFocus = emit(this, 'sl-initial-focus', { cancelable: true });
if (!slInitialFocus.defaultPrevented) {
this.setInitialFocus();
// Set initial focus
requestAnimationFrame(() => {
const slInitialFocus = emit(this, 'sl-initial-focus', { cancelable: true });
if (!slInitialFocus.defaultPrevented) {
// Set focus to the autofocus target and restore the attribute
if (autoFocusTarget) {
(autoFocusTarget as HTMLInputElement).focus({ preventScroll: true });
} else {
this.panel.focus({ preventScroll: true });
}
});
}
}
// Restore the autofocus attribute
if (autoFocusTarget) {
autoFocusTarget.setAttribute('autofocus', '');
}
});
const panelAnimation = getAnimation(this, 'dialog.show');
const overlayAnimation = getAnimation(this, 'dialog.overlay.show');
@ -187,17 +193,6 @@ export default class SlDialog extends LitElement {
animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options)
]);
// Browsers that don't support el.focus({ preventScroll }) have to wait for the animation to finish before initial
// focus to prevent scrolling issues. See: https://caniuse.com/mdn-api_htmlelement_focus_preventscroll_option
if (!hasPreventScroll) {
requestAnimationFrame(() => {
const slInitialFocus = emit(this, 'sl-initial-focus', { cancelable: true });
if (!slInitialFocus.defaultPrevented) {
this.setInitialFocus();
}
});
}
emit(this, 'sl-after-show');
} else {
// Hide

Wyświetl plik

@ -8,15 +8,12 @@ import Modal from '../../internal/modal';
import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll';
import { HasSlotController } from '../../internal/slot';
import { uppercaseFirstLetter } from '../../internal/string';
import { isPreventScrollSupported } from '../../internal/support';
import { watch } from '../../internal/watch';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
import { LocalizeController } from '../../utilities/localize';
import '../icon-button/icon-button';
import styles from './drawer.styles';
const hasPreventScroll = isPreventScrollSupported();
/**
* @since 2.0
* @status stable
@ -31,8 +28,8 @@ const hasPreventScroll = isPreventScrollSupported();
* @event sl-after-show - Emitted after the drawer opens and all animations are complete.
* @event sl-hide - Emitted when the drawer closes.
* @event sl-after-hide - Emitted after the drawer closes and all animations are complete.
* @event sl-initial-focus - Emitted when the drawer opens and the panel gains focus. Calling `event.preventDefault()` will
* prevent focus and allow you to set it on a different element in the drawer, such as an input or button.
* @event sl-initial-focus - Emitted when the drawer opens and is ready to receive focus. Calling
* `event.preventDefault()` will prevent focusing and allow you to set it on a different element, such as an input.
* @event {{ source: 'close-button' | 'keyboard' | 'overlay' }} sl-request-close - Emitted when the user attempts to
* close the drawer by clicking the close button, clicking the overlay, or pressing escape. Calling
* `event.preventDefault()` will keep the drawer open. Avoid using this unless closing the drawer will result in
@ -156,17 +153,6 @@ export default class SlDrawer extends LitElement {
this.hide();
}
// Sets focus on the first child element with autofocus, falling back to the panel if one isn't found
private setInitialFocus() {
const target = this.querySelector('[autofocus]');
if (target) {
(target as HTMLElement).focus({ preventScroll: true });
} else {
this.panel.focus({ preventScroll: true });
}
}
handleKeyDown(event: KeyboardEvent) {
if (event.key === 'Escape') {
event.stopPropagation();
@ -187,18 +173,38 @@ export default class SlDrawer extends LitElement {
lockBodyScrolling(this);
}
// When the drawer is shown, Safari will attempt to set focus on whatever element has autofocus. This causes the
// drawer's animation to jitter, so we'll temporarily remove the attribute, call `focus({ preventScroll: true })`
// ourselves, and add the attribute back afterwards.
//
// Related: https://github.com/shoelace-style/shoelace/issues/693
//
const autoFocusTarget = this.querySelector('[autofocus]');
if (autoFocusTarget) {
autoFocusTarget.removeAttribute('autofocus');
}
await Promise.all([stopAnimations(this.drawer), stopAnimations(this.overlay)]);
this.drawer.hidden = false;
// Browsers that support el.focus({ preventScroll }) can set initial focus immediately
if (hasPreventScroll) {
requestAnimationFrame(() => {
const slInitialFocus = emit(this, 'sl-initial-focus', { cancelable: true });
if (!slInitialFocus.defaultPrevented) {
this.setInitialFocus();
// Set initial focus
requestAnimationFrame(() => {
const slInitialFocus = emit(this, 'sl-initial-focus', { cancelable: true });
if (!slInitialFocus.defaultPrevented) {
// Set focus to the autofocus target and restore the attribute
if (autoFocusTarget) {
(autoFocusTarget as HTMLInputElement).focus({ preventScroll: true });
} else {
this.panel.focus({ preventScroll: true });
}
});
}
}
// Restore the autofocus attribute
if (autoFocusTarget) {
autoFocusTarget.setAttribute('autofocus', '');
}
});
const panelAnimation = getAnimation(this, `drawer.show${uppercaseFirstLetter(this.placement)}`);
const overlayAnimation = getAnimation(this, 'drawer.overlay.show');
@ -207,17 +213,6 @@ export default class SlDrawer extends LitElement {
animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options)
]);
// Browsers that don't support el.focus({ preventScroll }) have to wait for the animation to finish before initial
// focus to prevent scrolling issues. See: https://caniuse.com/mdn-api_htmlelement_focus_preventscroll_option
if (!hasPreventScroll) {
requestAnimationFrame(() => {
const slInitialFocus = emit(this, 'sl-initial-focus', { cancelable: true });
if (!slInitialFocus.defaultPrevented) {
this.setInitialFocus();
}
});
}
emit(this, 'sl-after-show');
} else {
// Hide

Wyświetl plik

@ -1,15 +0,0 @@
//
// Determines if the browser supports focus({ preventScroll })
//
export function isPreventScrollSupported() {
let supported = false;
document.createElement('div').focus({
get preventScroll() {
supported = true;
return false;
}
});
return supported;
}