Add sl-initial-focus to dialog + drawer

pull/481/head
Cory LaViska 2021-01-07 10:17:08 -05:00
rodzic abf03a8f9e
commit fc8e48cb6a
7 zmienionych plików z 116 dodań i 20 usunięć

Wyświetl plik

@ -108,4 +108,34 @@ By default, dialogs are closed when the user clicks or taps on the overlay. To p
</script>
```
### Customizing Initial Focus
By default, the dialog's panel will gain focus when opened. To set focus on a different element, listen for the `sl-initial-focus` event.
```html preview
<sl-dialog label="Dialog" class="dialog-focus">
<sl-input placeholder="I will have focus when the dialog is opened"></sl-input>
<sl-button slot="footer" type="primary">Close</sl-button>
</sl-dialog>
<sl-button>Open Dialog</sl-button>
<script>
(() => {
const dialog = document.querySelector('.dialog-focus');
const input = dialog.querySelector('sl-input');
const openButton = dialog.nextElementSibling;
const closeButton = dialog.querySelector('sl-button[slot="footer"]');
openButton.addEventListener('click', () => dialog.show());
closeButton.addEventListener('click', () => dialog.hide());
dialog.addEventListener('sl-initial-focus', event => {
event.preventDefault();
input.setFocus()
});
})();
</script>
```
[component-metadata:sl-dialog]

Wyświetl plik

@ -204,4 +204,36 @@ By default, drawers are closed when the user clicks or taps on the overlay. To p
</script>
```
### Customizing Initial Focus
By default, the drawer's panel will gain focus when opened. To set focus on a different element, listen for the `sl-initial-focus` event.
```html preview
<sl-drawer label="Drawer" class="drawer-focus">
<sl-input placeholder="I will have focus when the drawer is opened"></sl-input>
<sl-button slot="footer" type="primary">Close</sl-button>
</sl-drawer>
<sl-button>Open Drawer</sl-button>
<script>
(() => {
const drawer = document.querySelector('.drawer-focus');
const input = drawer.querySelector('sl-input');
const openButton = drawer.nextElementSibling;
const closeButton = drawer.querySelector('sl-button[type="primary"]');
openButton.addEventListener('click', () => drawer.show());
closeButton.addEventListener('click', () => drawer.hide());
drawer.addEventListener('sl-initial-focus', event => {
// preventScroll is necessary for the transition to work properly,
// otherwise the drawer will appear immediately
event.preventDefault();
input.setFocus({ preventScroll: true })
});
})();
</script>
```
[component-metadata:sl-drawer]

Wyświetl plik

@ -13,6 +13,8 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
- Reworked animations into a separate module ([`@shoelace-style/animations`](https://github.com/shoelace-style/animations)) so it's more maintainable and animations are sync with the latest version of animate.css
- Animation and easing names are now camelcase (e.g. `easeInOut` instead of `ease-in-out`)
- Added initial E2E tests [#169](https://github.com/shoelace-style/shoelace/pull/169)
- Added the `FocusOptions` argument to all components that have a `setFocus()` method
- Added `sl-initial-focus` event to `sl-dialog` and `sl-drawer` so focus can be customized to a specific element
- Fixed a bug where `sl-hide` would be emitted twice when closing an alert with `hide()`
- Fixed a bug in `sl-color-picker` where the toggle button was smaller than the preview button in Safari
- Fixed a bug in `sl-tab-group` where activating a nested tab group didn't work properly [#299](https://github.com/shoelace-style/shoelace/issues/299)

Wyświetl plik

@ -41,7 +41,7 @@
"serve": "node dev-server.js",
"start": "concurrently --kill-others \"npm run dev\" \"npm run serve\"",
"test.watch": "stencil test --spec --e2e --watchAll",
"test": "stencil test --spec --e2e",
"test": "stencil test --spec --e2e --coverage",
"version": "npm run build"
},
"devDependencies": {

28
src/components.d.ts vendored
Wyświetl plik

@ -183,7 +183,7 @@ export namespace Components {
/**
* Sets focus on the button.
*/
"setFocus": () => Promise<void>;
"setFocus": (options?: FocusOptions) => Promise<void>;
/**
* The button's size.
*/
@ -253,7 +253,7 @@ export namespace Components {
/**
* Sets focus on the checkbox.
*/
"setFocus": () => Promise<void>;
"setFocus": (options?: FocusOptions) => Promise<void>;
/**
* The checkbox's value attribute.
*/
@ -751,7 +751,7 @@ export namespace Components {
/**
* Sets focus on the input.
*/
"setFocus": () => Promise<void>;
"setFocus": (options?: FocusOptions) => Promise<void>;
/**
* Replaces a range of text with a new string.
*/
@ -809,7 +809,7 @@ export namespace Components {
/**
* Sets focus on the button.
*/
"setFocus": () => Promise<void>;
"setFocus": (options?: FocusOptions) => Promise<void>;
/**
* A unique value to store in the menu item. This can be used as a way to identify menu items when selected.
*/
@ -873,7 +873,7 @@ export namespace Components {
/**
* Sets focus on the radio.
*/
"setFocus": () => Promise<void>;
"setFocus": (options?: FocusOptions) => Promise<void>;
/**
* The radio's value attribute.
*/
@ -911,7 +911,7 @@ export namespace Components {
/**
* Sets focus on the input.
*/
"setFocus": () => Promise<void>;
"setFocus": (options?: FocusOptions) => Promise<void>;
/**
* The input's step attribute.
*/
@ -957,7 +957,7 @@ export namespace Components {
/**
* Sets focus on the rating.
*/
"setFocus": () => Promise<void>;
"setFocus": (options?: FocusOptions) => Promise<void>;
/**
* The current rating.
*/
@ -1103,7 +1103,7 @@ export namespace Components {
/**
* Sets focus on the switch.
*/
"setFocus": () => Promise<void>;
"setFocus": (options?: FocusOptions) => Promise<void>;
/**
* The switch's value attribute.
*/
@ -1133,7 +1133,7 @@ export namespace Components {
/**
* Sets focus to the tab.
*/
"setFocus": () => Promise<void>;
"setFocus": (options?: FocusOptions) => Promise<void>;
}
interface SlTabGroup {
/**
@ -1265,7 +1265,7 @@ export namespace Components {
/**
* Sets focus on the textarea.
*/
"setFocus": () => Promise<void>;
"setFocus": (options?: FocusOptions) => Promise<void>;
/**
* Replaces a range of text with a new string.
*/
@ -2031,6 +2031,10 @@ declare namespace LocalJSX {
* Emitted when the dialog closes. Calling `event.preventDefault()` will prevent it from being closed.
*/
"onSl-hide"?: (event: CustomEvent<any>) => void;
/**
* 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.
*/
"onSl-initial-focus"?: (event: CustomEvent<any>) => void;
/**
* Emitted when the overlay is clicked. Calling `event.preventDefault()` will prevent the dialog from closing.
*/
@ -2069,6 +2073,10 @@ declare namespace LocalJSX {
* Emitted when the drawer closes. Calling `event.preventDefault()` will prevent it from being closed.
*/
"onSl-hide"?: (event: CustomEvent<any>) => void;
/**
* 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.
*/
"onSl-initial-focus"?: (event: CustomEvent<any>) => void;
/**
* Emitted when the overlay is clicked. Calling `event.preventDefault()` will prevent the drawer from closing.
*/

Wyświetl plik

@ -73,6 +73,12 @@ export class Dialog {
/** Emitted after the dialog closes and all transitions are complete. */
@Event({ eventName: 'sl-after-hide' }) slAfterHide: EventEmitter;
/**
* 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({ eventName: 'sl-initial-focus' }) slInitialFocus: EventEmitter;
/** Emitted when the overlay is clicked. Calling `event.preventDefault()` will prevent the dialog from closing. */
@Event({ eventName: 'sl-overlay-dismiss' }) slOverlayDismiss: EventEmitter;
@ -120,6 +126,16 @@ export class Dialog {
this.modal.activate();
lockBodyScrolling(this.host);
if (this.open) {
// Wait for the next frame before setting initial focus so the dialog is technically visible
requestAnimationFrame(() => {
const slInitialFocus = this.slInitialFocus.emit();
if (!slInitialFocus.defaultPrevented) {
this.panel.focus({ preventScroll: true });
}
});
}
}
/** Hides the dialog */
@ -173,10 +189,6 @@ export class Dialog {
this.willShow = false;
this.willHide = false;
this.open ? this.slAfterShow.emit() : this.slAfterHide.emit();
if (this.open) {
this.panel.focus();
}
}
}

Wyświetl plik

@ -10,7 +10,7 @@ let id = 0;
* @status stable
*
* @slot - The drawer's content.
* @slot label - The dialog's label. Alternatively, you can use the label prop.
* @slot label - The drawer's label. Alternatively, you can use the label prop.
* @slot footer - The drawer's footer, usually one or more buttons representing various options.
*
* @part base - The component's base wrapper.
@ -81,6 +81,12 @@ export class Drawer {
/** Emitted after the drawer closes and all transitions are complete. */
@Event({ eventName: 'sl-after-hide' }) slAfterHide: EventEmitter;
/**
* 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({ eventName: 'sl-initial-focus' }) slInitialFocus: EventEmitter;
/** Emitted when the overlay is clicked. Calling `event.preventDefault()` will prevent the drawer from closing. */
@Event({ eventName: 'sl-overlay-dismiss' }) slOverlayDismiss: EventEmitter;
@ -131,6 +137,16 @@ export class Drawer {
this.modal.activate();
lockBodyScrolling(this.host);
}
if (this.open) {
// Wait for the next frame before setting initial focus so the drawer is technically visible
requestAnimationFrame(() => {
const slInitialFocus = this.slInitialFocus.emit();
if (!slInitialFocus.defaultPrevented) {
this.panel.focus({ preventScroll: true });
}
});
}
}
/** Hides the drawer */
@ -184,10 +200,6 @@ export class Drawer {
this.willShow = false;
this.willHide = false;
this.open ? this.slAfterShow.emit() : this.slAfterHide.emit();
if (this.open) {
this.panel.focus();
}
}
}