pull/792/head
Cory LaViska 2022-06-09 18:14:38 -04:00
rodzic c8d92e41b2
commit 8a28d66393
10 zmienionych plików z 91 dodań i 33 usunięć

Wyświetl plik

@ -108,7 +108,7 @@ Not all components expose CSS custom properties. For those that do, they can be
Some components use animation, such as when a dialog is shown or hidden. Animations are performed using the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) rather than CSS. However, you can still customize them through Shoelace's animation registry. If a component has customizable animations, they'll be listed in the "Animation" section of its documentation.
To customize a default animation, use the `setDefaultAnimation()` method. The function accepts an animation name (found in the component's docs) and an object with `keyframes` and `options` or `null` to disable the animation.
To customize a default animation, use the `setDefaultAnimation()` method. The function accepts an animation name (found in the component's docs) and an object with `keyframes`, and `options` or `null` to disable the animation.
This example will make all dialogs use a custom show animation.
@ -127,6 +127,8 @@ setDefaultAnimation('dialog.show', {
});
```
?> To support RTL languages in your animation, you can pass an additional property called `rtlKeyframes`. This property shares the same type as `keyframes` and will be automatically used when the component's directionality is RTL. If `rtlKeyframes` is not provided, `keyframes` will be used as a fallback.
If you only want to target a single component, use the `setAnimation()` method instead. This function accepts an element, an animation name, and an object comprised of animation `keyframes` and `options`.
In this example, only the target dialog will use a custom show animation.

Wyświetl plik

@ -8,6 +8,8 @@ _During the beta period, these restrictions may be relaxed in the event of a mis
## Next
- Added support for RTL animations in the Animation Registry
- Improved RTL animations for `<sl-drawer>` [#784](https://github.com/shoelace-style/shoelace/issues/784)
- Improved RTL styles for `<sl-button-group>` [#783](https://github.com/shoelace-style/shoelace/issues/783)
- Improved RTL styles for the toast stack [#785](https://github.com/shoelace-style/shoelace/issues/785)

Wyświetl plik

@ -7,6 +7,7 @@ import { emit, waitForEvent } from '../../internal/event';
import { HasSlotController } from '../../internal/slot';
import { watch } from '../../internal/watch';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
import { LocalizeController } from '../../utilities/localize';
import styles from './alert.styles';
const toastStack = Object.assign(document.createElement('div'), { className: 'sl-toast-stack' });
@ -43,6 +44,7 @@ export default class SlAlert extends LitElement {
private autoHideTimeout: number;
private readonly hasSlotController = new HasSlotController(this, 'icon', 'suffix');
private readonly localize = new LocalizeController(this);
@query('[part="base"]') base: HTMLElement;
@ -148,7 +150,7 @@ export default class SlAlert extends LitElement {
await stopAnimations(this.base);
this.base.hidden = false;
const { keyframes, options } = getAnimation(this, 'alert.show');
const { keyframes, options } = getAnimation(this, 'alert.show', { dir: this.localize.dir() });
await animateTo(this.base, keyframes, options);
emit(this, 'sl-after-show');
@ -159,7 +161,7 @@ export default class SlAlert extends LitElement {
clearTimeout(this.autoHideTimeout);
await stopAnimations(this.base);
const { keyframes, options } = getAnimation(this, 'alert.hide');
const { keyframes, options } = getAnimation(this, 'alert.hide', { dir: this.localize.dir() });
await animateTo(this.base, keyframes, options);
this.base.hidden = true;

Wyświetl plik

@ -6,6 +6,7 @@ import { animateTo, shimKeyframesHeightAuto, stopAnimations } from '../../intern
import { emit, waitForEvent } from '../../internal/event';
import { watch } from '../../internal/watch';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
import { LocalizeController } from '../../utilities/localize';
import styles from './details.styles';
/**
@ -39,6 +40,8 @@ export default class SlDetails extends LitElement {
@query('.details__header') header: HTMLElement;
@query('.details__body') body: HTMLElement;
private readonly localize = new LocalizeController(this);
/** Indicates whether or not the details is open. You can use this in lieu of the show/hide methods. */
@property({ type: Boolean, reflect: true }) open = false;
@ -116,7 +119,7 @@ export default class SlDetails extends LitElement {
await stopAnimations(this.body);
this.body.hidden = false;
const { keyframes, options } = getAnimation(this, 'details.show');
const { keyframes, options } = getAnimation(this, 'details.show', { dir: this.localize.dir() });
await animateTo(this.body, shimKeyframesHeightAuto(keyframes, this.body.scrollHeight), options);
this.body.style.height = 'auto';
@ -127,7 +130,7 @@ export default class SlDetails extends LitElement {
await stopAnimations(this.body);
const { keyframes, options } = getAnimation(this, 'details.hide');
const { keyframes, options } = getAnimation(this, 'details.hide', { dir: this.localize.dir() });
await animateTo(this.body, shimKeyframesHeightAuto(keyframes, this.body.scrollHeight), options);
this.body.hidden = true;
this.body.style.height = 'auto';

Wyświetl plik

@ -129,7 +129,7 @@ export default class SlDialog extends LitElement {
});
if (slRequestClose.defaultPrevented) {
const animation = getAnimation(this, 'dialog.denyClose');
const animation = getAnimation(this, 'dialog.denyClose', { dir: this.localize.dir() });
animateTo(this.panel, animation.keyframes, animation.options);
return;
}
@ -187,8 +187,8 @@ export default class SlDialog extends LitElement {
}
});
const panelAnimation = getAnimation(this, 'dialog.show');
const overlayAnimation = getAnimation(this, 'dialog.overlay.show');
const panelAnimation = getAnimation(this, 'dialog.show', { dir: this.localize.dir() });
const overlayAnimation = getAnimation(this, 'dialog.overlay.show', { dir: this.localize.dir() });
await Promise.all([
animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options),
animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options)
@ -201,8 +201,8 @@ export default class SlDialog extends LitElement {
this.modal.deactivate();
await Promise.all([stopAnimations(this.dialog), stopAnimations(this.overlay)]);
const panelAnimation = getAnimation(this, 'dialog.hide');
const overlayAnimation = getAnimation(this, 'dialog.overlay.hide');
const panelAnimation = getAnimation(this, 'dialog.hide', { dir: this.localize.dir() });
const overlayAnimation = getAnimation(this, 'dialog.overlay.hide', { dir: this.localize.dir() });
await Promise.all([
animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options),
animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options)

Wyświetl plik

@ -15,7 +15,7 @@ export default css`
.drawer {
top: 0;
left: 0;
inset-inline-start: 0;
width: 100%;
height: 100%;
pointer-events: none;
@ -52,36 +52,36 @@ export default css`
.drawer--top .drawer__panel {
top: 0;
right: auto;
inset-inline-end: auto;
bottom: auto;
left: 0;
inset-inline-start: 0;
width: 100%;
height: var(--size);
}
.drawer--end .drawer__panel {
top: 0;
right: 0;
inset-inline-end: 0;
bottom: auto;
left: auto;
inset-inline-start: auto;
width: var(--size);
height: 100%;
}
.drawer--bottom .drawer__panel {
top: auto;
right: auto;
inset-inline-end: auto;
bottom: 0;
left: 0;
inset-inline-start: 0;
width: 100%;
height: var(--size);
}
.drawer--start .drawer__panel {
top: 0;
right: auto;
inset-inline-end: auto;
bottom: auto;
left: 0;
inset-inline-start: 0;
width: var(--size);
height: 100%;
}

Wyświetl plik

@ -146,7 +146,7 @@ export default class SlDrawer extends LitElement {
});
if (slRequestClose.defaultPrevented) {
const animation = getAnimation(this, 'drawer.denyClose');
const animation = getAnimation(this, 'drawer.denyClose', { dir: this.localize.dir() });
animateTo(this.panel, animation.keyframes, animation.options);
return;
}
@ -207,8 +207,10 @@ export default class SlDrawer extends LitElement {
}
});
const panelAnimation = getAnimation(this, `drawer.show${uppercaseFirstLetter(this.placement)}`);
const overlayAnimation = getAnimation(this, 'drawer.overlay.show');
const panelAnimation = getAnimation(this, `drawer.show${uppercaseFirstLetter(this.placement)}`, {
dir: this.localize.dir()
});
const overlayAnimation = getAnimation(this, 'drawer.overlay.show', { dir: this.localize.dir() });
await Promise.all([
animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options),
animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options)
@ -222,8 +224,10 @@ export default class SlDrawer extends LitElement {
unlockBodyScrolling(this);
await Promise.all([stopAnimations(this.drawer), stopAnimations(this.overlay)]);
const panelAnimation = getAnimation(this, `drawer.hide${uppercaseFirstLetter(this.placement)}`);
const overlayAnimation = getAnimation(this, 'drawer.overlay.hide');
const panelAnimation = getAnimation(this, `drawer.hide${uppercaseFirstLetter(this.placement)}`, {
dir: this.localize.dir()
});
const overlayAnimation = getAnimation(this, 'drawer.overlay.hide', { dir: this.localize.dir() });
await Promise.all([
animateTo(this.panel, panelAnimation.keyframes, panelAnimation.options),
animateTo(this.overlay, overlayAnimation.keyframes, overlayAnimation.options)
@ -255,6 +259,7 @@ export default class SlDrawer extends LitElement {
'drawer--start': this.placement === 'start',
'drawer--contained': this.contained,
'drawer--fixed': !this.contained,
'drawer--rtl': this.localize.dir() === 'rtl',
'drawer--has-footer': this.hasSlotController.test('footer')
})}
@keydown=${this.handleKeyDown}
@ -328,6 +333,10 @@ setDefaultAnimation('drawer.showEnd', {
{ opacity: 0, transform: 'translateX(100%)' },
{ opacity: 1, transform: 'translateX(0)' }
],
rtlKeyframes: [
{ opacity: 0, transform: 'translateX(-100%)' },
{ opacity: 1, transform: 'translateX(0)' }
],
options: { duration: 250, easing: 'ease' }
});
@ -336,6 +345,10 @@ setDefaultAnimation('drawer.hideEnd', {
{ opacity: 1, transform: 'translateX(0)' },
{ opacity: 0, transform: 'translateX(100%)' }
],
rtlKeyframes: [
{ opacity: 1, transform: 'translateX(0)' },
{ opacity: 0, transform: 'translateX(-100%)' }
],
options: { duration: 250, easing: 'ease' }
});
@ -362,6 +375,10 @@ setDefaultAnimation('drawer.showStart', {
{ opacity: 0, transform: 'translateX(-100%)' },
{ opacity: 1, transform: 'translateX(0)' }
],
rtlKeyframes: [
{ opacity: 0, transform: 'translateX(100%)' },
{ opacity: 1, transform: 'translateX(0)' }
],
options: { duration: 250, easing: 'ease' }
});
@ -370,6 +387,10 @@ setDefaultAnimation('drawer.hideStart', {
{ opacity: 1, transform: 'translateX(0)' },
{ opacity: 0, transform: 'translateX(-100%)' }
],
rtlKeyframes: [
{ opacity: 1, transform: 'translateX(0)' },
{ opacity: 0, transform: 'translateX(100%)' }
],
options: { duration: 250, easing: 'ease' }
});

Wyświetl plik

@ -8,6 +8,7 @@ import { scrollIntoView } from '../../internal/scroll';
import { getTabbableBoundary } from '../../internal/tabbable';
import { watch } from '../../internal/watch';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
import { LocalizeController } from '../../utilities/localize';
import styles from './dropdown.styles';
import type SlButton from '../../components/button/button';
import type SlIconButton from '../../components/icon-button/icon-button';
@ -41,6 +42,7 @@ export default class SlDropdown extends LitElement {
@query('.dropdown__panel') panel: HTMLElement;
@query('.dropdown__positioner') positioner: HTMLElement;
private readonly localize = new LocalizeController(this);
private positionerCleanup: ReturnType<typeof autoUpdate> | undefined;
/** Indicates whether or not the dropdown is open. You can use this in lieu of the show/hide methods. */
@ -371,7 +373,7 @@ export default class SlDropdown extends LitElement {
await stopAnimations(this);
this.startPositioner();
this.panel.hidden = false;
const { keyframes, options } = getAnimation(this, 'dropdown.show');
const { keyframes, options } = getAnimation(this, 'dropdown.show', { dir: this.localize.dir() });
await animateTo(this.panel, keyframes, options);
emit(this, 'sl-after-show');
@ -381,7 +383,7 @@ export default class SlDropdown extends LitElement {
this.removeOpenListeners();
await stopAnimations(this);
const { keyframes, options } = getAnimation(this, 'dropdown.hide');
const { keyframes, options } = getAnimation(this, 'dropdown.hide', { dir: this.localize.dir() });
await animateTo(this.panel, keyframes, options);
this.panel.hidden = true;
this.stopPositioner();

Wyświetl plik

@ -6,6 +6,7 @@ import { animateTo, parseDuration, stopAnimations } from '../../internal/animate
import { emit, waitForEvent } from '../../internal/event';
import { watch } from '../../internal/watch';
import { getAnimation, setDefaultAnimation } from '../../utilities/animation-registry';
import { LocalizeController } from '../../utilities/localize';
import styles from './tooltip.styles';
/**
@ -39,6 +40,7 @@ export default class SlTooltip extends LitElement {
private target: HTMLElement;
private hoverTimeout: number;
private readonly localize = new LocalizeController(this);
private positionerCleanup: ReturnType<typeof autoUpdate> | undefined;
/** The tooltip's content. Alternatively, you can use the content slot. */
@ -220,7 +222,7 @@ export default class SlTooltip extends LitElement {
await stopAnimations(this.tooltip);
this.startPositioner();
this.tooltip.hidden = false;
const { keyframes, options } = getAnimation(this, 'tooltip.show');
const { keyframes, options } = getAnimation(this, 'tooltip.show', { dir: this.localize.dir() });
await animateTo(this.tooltip, keyframes, options);
emit(this, 'sl-after-show');
@ -229,7 +231,7 @@ export default class SlTooltip extends LitElement {
emit(this, 'sl-hide');
await stopAnimations(this.tooltip);
const { keyframes, options } = getAnimation(this, 'tooltip.hide');
const { keyframes, options } = getAnimation(this, 'tooltip.hide', { dir: this.localize.dir() });
await animateTo(this.tooltip, keyframes, options);
this.tooltip.hidden = true;
this.stopPositioner();

Wyświetl plik

@ -1,12 +1,21 @@
interface ElementAnimation {
export interface ElementAnimation {
keyframes: Keyframe[];
rtlKeyframes?: Keyframe[];
options?: KeyframeAnimationOptions;
}
interface ElementAnimationMap {
export interface ElementAnimationMap {
[animationName: string]: ElementAnimation;
}
export interface GetAnimationOptions {
/**
* The component's directionality. When set to "rtl", `rtlKeyframes` will be preferred over `keyframes` where
* available using getAnimation().
*/
dir: string;
}
const defaultAnimationRegistry = new Map<string, ElementAnimation>();
const customAnimationRegistry = new WeakMap<Element, ElementAnimationMap>();
@ -14,6 +23,21 @@ function ensureAnimation(animation: ElementAnimation | null) {
return animation ?? { keyframes: [], options: { duration: 0 } };
}
//
// Given an ElementAnimation, this function returns a new ElementAnimation where the keyframes property reflects either
// keyframes or rtlKeyframes depending on the specified directionality.
//
function getLogicalAnimation(animation: ElementAnimation, dir: string) {
if (dir.toLowerCase() === 'rtl') {
return {
keyframes: animation.rtlKeyframes || animation.keyframes,
options: animation.options
};
}
return animation;
}
//
// Sets a default animation. Components should use the `name.animation` for primary animations and `name.part.animation`
// for secondary animations, e.g. `dialog.show` and `dialog.overlay.show`. For modifiers, use `drawer.showTop`.
@ -32,18 +56,18 @@ export function setAnimation(el: Element, animationName: string, animation: Elem
//
// Gets an element's animation. Falls back to the default if no animation is found.
//
export function getAnimation(el: Element, animationName: string) {
export function getAnimation(el: Element, animationName: string, options: GetAnimationOptions) {
const customAnimation = customAnimationRegistry.get(el);
// Check for a custom animation
if (customAnimation?.[animationName]) {
return customAnimation[animationName];
return getLogicalAnimation(customAnimation[animationName], options.dir);
}
// Check for a default animation
const defaultAnimation = defaultAnimationRegistry.get(animationName);
if (defaultAnimation) {
return defaultAnimation;
return getLogicalAnimation(defaultAnimation, options.dir);
}
// Fall back to an empty animation