Skip to content

Commit

Permalink
fix(focus)!: corrected outward/inward animations
Browse files Browse the repository at this point in the history
BREAKING CHANGE: inward focus rings must be specified with `inward` rather than a negative offset.

PiperOrigin-RevId: 534552625
  • Loading branch information
asyncLiz authored and copybara-github committed May 23, 2023
1 parent 146aa17 commit 26d69c2
Show file tree
Hide file tree
Showing 16 changed files with 118 additions and 50 deletions.
2 changes: 1 addition & 1 deletion checkbox/lib/_checkbox.scss
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ $_checkmark-bottom-left: 7px, -14px;

@include focus-ring.theme(
(
'offset': -2px,
'outward-offset': -2px,
)
);
}
Expand Down
2 changes: 1 addition & 1 deletion chips/lib/_trailing-icon.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
'shape-end-start': var(--_container-shape-end-end),
'shape-end-end': var(--_container-shape-end-end),
'shape-start-end': var(--_container-shape-start-end),
'offset': -2px,
'outward-offset': -2px,
)
);
}
Expand Down
2 changes: 1 addition & 1 deletion fab/lib/forced-colors-styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// Adjust the focus ring padding to account for the 1px border in HCM.
@include focus-ring.theme(
(
'offset': 3px,
'outward-offset': 3px,
)
);
border: 1px solid ButtonText;
Expand Down
12 changes: 8 additions & 4 deletions focus/demo/demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,25 @@ import './index.js';
import './material-collection.js';

import {KnobTypesToKnobs, MaterialCollection, materialInitsToStoryInits, setUpDemo} from './material-collection.js';
import {cssCustomProperty, Knob, textInput} from './index.js';
import {boolInput, cssCustomProperty, Knob, textInput} from './index.js';

import {stories, StoryKnobs} from './stories.js';

const collection =
new MaterialCollection<KnobTypesToKnobs<StoryKnobs>>('Focus', [
new Knob('inward', {ui: boolInput(), defaultValue: false}),
new Knob(
'--md-focus-ring-width',
{ui: textInput(), wiring: cssCustomProperty, defaultValue: '3px'}),
new Knob(
'--md-focus-ring-offset',
{ui: textInput(), wiring: cssCustomProperty, defaultValue: '2px'}),
new Knob(
'--md-focus-ring-active-width',
{ui: textInput(), wiring: cssCustomProperty, defaultValue: '8px'}),
new Knob(
'--md-focus-ring-outward-offset',
{ui: textInput(), wiring: cssCustomProperty, defaultValue: '2px'}),
new Knob(
'--md-focus-ring-inward-offset',
{ui: textInput(), wiring: cssCustomProperty, defaultValue: '0px'}),
]);

collection.addStories(...materialInitsToStoryInits(stories));
Expand Down
39 changes: 28 additions & 11 deletions focus/demo/stories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import {css, html} from 'lit';

/** Knob types for focus ring stories. */
export interface StoryKnobs {
inward: boolean;
'--md-focus-ring-width': string;
'--md-focus-ring-offset': string;
'--md-focus-ring-active-width': string;
'--md-focus-ring-outward-offset': string;
'--md-focus-ring-inward-offset': string;
}

const standard: MaterialStoryInit<StoryKnobs> = {
Expand All @@ -22,7 +24,7 @@ const standard: MaterialStoryInit<StoryKnobs> = {
button {
appearance: none;
background: var(--md-sys-color-surface);
border: 1px solid var(--md-sys-color-outline);
border: none;
border-radius: 16px;
--md-focus-ring-shape: 16px;
height: 64px;
Expand All @@ -33,20 +35,28 @@ const standard: MaterialStoryInit<StoryKnobs> = {
width: 64px;
}
button::before {
border: 1px solid var(--md-sys-color-outline);
border-radius: inherit;
content: '';
inset: 0;
position: absolute;
}
button:focus {
background: var(--md-sys-color-surface-variant);
}
`,
render() {
render({inward}) {
return html`
<button aria-label="A button with a focus ring">
<md-focus-ring></md-focus-ring>
<md-focus-ring ?inward=${inward}></md-focus-ring>
</button>
<button aria-label="A button with a focus ring">
<md-focus-ring></md-focus-ring>
<md-focus-ring ?inward=${inward}></md-focus-ring>
</button>
<button aria-label="A button with a focus ring">
<md-focus-ring></md-focus-ring>
<md-focus-ring ?inward=${inward}></md-focus-ring>
</button>
`;
}
Expand All @@ -59,7 +69,6 @@ const multiAction: MaterialStoryInit<StoryKnobs> = {
align-items: center;
appearance: none;
background: var(--md-sys-color-surface);
border: 1px solid var(--md-sys-color-outline);
border-radius: 16px;
--md-focus-ring-shape: 16px;
display: flex;
Expand Down Expand Up @@ -94,17 +103,25 @@ const multiAction: MaterialStoryInit<StoryKnobs> = {
}
#secondary {
border: 1px solid var(--md-sys-color-outline);
height: 32px;
width: 32px;
border-radius: 32px;
--md-focus-ring-shape: 32px;
}
[role="list"]::before,
#secondary::before {
border: 1px solid var(--md-sys-color-outline);
border-radius: inherit;
content: '';
inset: 0;
position: absolute;
}
`,
render() {
render({inward}) {
return html`
<div role="list">
<md-focus-ring for="primary"></md-focus-ring>
<md-focus-ring for="primary" ?inward=${inward}></md-focus-ring>
<div role="listitem">
<button id="primary" aria-label="The primary action for a multi-action component">
Expand All @@ -115,7 +132,7 @@ const multiAction: MaterialStoryInit<StoryKnobs> = {
<div role="listitem">
<button id="secondary" aria-label="The secondary action for a multi-action component">
X
<md-focus-ring></md-focus-ring>
<md-focus-ring ?inward=${inward}></md-focus-ring>
</button>
</div>
</div>
Expand Down
77 changes: 60 additions & 17 deletions focus/lib/_focus-ring.scss
Original file line number Diff line number Diff line change
Expand Up @@ -44,37 +44,80 @@ $_md-sys-motion: tokens.md-sys-motion-values();
--_shape-end-end: var(--md-focus-ring-shape-end-end, var(--_shape));
--_shape-end-start: var(--md-focus-ring-shape-end-start, var(--_shape));

animation-duration: var(--_duration);
animation-delay: 0s, calc(var(--_duration) * 0.25);
animation-duration: calc(var(--_duration) * 0.25),
calc(var(--_duration) * 0.75);
animation-timing-function: map.get($_md-sys-motion, 'easing-emphasized');
border-end-end-radius: calc(var(--_offset) + var(--_shape-end-end));
border-end-start-radius: calc(var(--_offset) + var(--_shape-end-start));
border-start-end-radius: calc(var(--_offset) + var(--_shape-start-end));
border-start-start-radius: calc(var(--_offset) + var(--_shape-start-start));
box-shadow: inset 0 0 0 0 currentColor;
box-sizing: border-box;
color: var(--_color);
display: none;
inset: calc(-1 * (var(--_offset) + 1px));
// NOTE: this 1px offset hides a transparent ring between the outline and
// offset when border-radius is large
outline-offset: -1px;
outline: var(--_width) solid currentColor;
pointer-events: none;
position: absolute;
}

:host([visible]) {
display: flex;
animation-name: focus-ring;
}

@keyframes focus-ring {
:host(:not([inward])) {
animation-name: outward-grow, outward-shrink;
border-end-end-radius: calc(var(--_shape-end-end) + var(--_outward-offset));
border-end-start-radius: calc(
var(--_shape-end-start) + var(--_outward-offset)
);
border-start-end-radius: calc(
var(--_shape-start-end) + var(--_outward-offset)
);
border-start-start-radius: calc(
var(--_shape-start-start) + var(--_outward-offset)
);
inset: calc(-1 * (var(--_outward-offset)));
outline: var(--_width) solid currentColor;
}

:host([inward]) {
animation-name: inward-grow, inward-shrink;
border-end-end-radius: calc(var(--_shape-end-end) - var(--_inward-offset));
border-end-start-radius: calc(
var(--_shape-end-start) - var(--_inward-offset)
);
border-start-end-radius: calc(
var(--_shape-start-end) - var(--_inward-offset)
);
border-start-start-radius: calc(
var(--_shape-start-start) - var(--_inward-offset)
);
border: var(--_width) solid currentColor;
inset: var(--_inward-offset);
}

@keyframes outward-grow {
from {
outline-width: 0px;
outline-width: 0;
}
to {
outline-width: var(--_active-width);
}
25% {
box-shadow: inset 0 0 0 calc(var(--_active-width) / 2) currentColor;
outline-width: calc(var(--_active-width) / 2);
}

@keyframes outward-shrink {
from {
outline-width: var(--_active-width);
}
}

@keyframes inward-grow {
from {
border-width: 0;
}
to {
border-width: var(--_active-width);
}
}

@keyframes inward-shrink {
from {
border-width: var(--_active-width);
}
}

Expand Down
1 change: 1 addition & 0 deletions focus/lib/focus-ring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class FocusRing extends LitElement {
* Makes the focus ring visible.
*/
@property({type: Boolean, reflect: true}) visible = false;
@property({type: Boolean, reflect: true}) inward = false;

/**
* Reflects the value of the `for` attribute, which is the ID of the focus
Expand Down
3 changes: 1 addition & 2 deletions list/lib/listitem/_list-item.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@

@include focus-ring.theme(
(
'offset': -3px,
'shape': map.get(tokens.md-sys-shape-values(), 'corner-extra-small'),
'shape': 8px,
)
);
@include ripple.theme(
Expand Down
2 changes: 1 addition & 1 deletion list/lib/listitem/list-item.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export class ListItemEl extends LitElement implements ListItem {
* Handles rendering of the focus ring.
*/
protected renderFocusRing() {
return html`<md-focus-ring class="focus-ring" for="item"></md-focus-ring>`;
return html`<md-focus-ring class="focus-ring" for="item" inward></md-focus-ring>`;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion navigationtab/lib/_navigation-tab.scss
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ $_custom-property-prefix: 'navigation-bar';

@include focus-ring.theme(
(
'offset': -2px,
'shape': map.get(tokens.md-sys-shape-values(), 'corner-small'),
'inward-offset': -1px,
)
);

Expand Down
5 changes: 3 additions & 2 deletions navigationtab/lib/navigation-tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,9 @@ export class NavigationTab extends LitElement implements NavigationTabState {
aria-label=${ariaLabel || nothing}
tabindex="${this.active ? 0 : -1}"
@click="${this.handleClick}"
${ripple(this.getRipple)}>
<md-focus-ring></md-focus-ring>
${ripple(this.getRipple)}
>
<md-focus-ring inward></md-focus-ring>
${when(this.showRipple, this.renderRipple)}
<span aria-hidden="true" class="md3-navigation-tab__icon-content"
><span class="md3-navigation-tab__active-indicator"
Expand Down
2 changes: 1 addition & 1 deletion radio/lib/_radio.scss
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ $_md-sys-motion: tokens.md-sys-motion-values();

@include focus-ring.theme(
(
'offset': -2px,
'outward-offset': -2px,
)
);

Expand Down
2 changes: 1 addition & 1 deletion slider/lib/_slider.scss
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ $_md-sys-shape: tokens.md-sys-shape-values();
md-focus-ring {
@include focus-ring.theme(
(
'offset': -2px,
'outward-offset': -2px,
)
);
}
Expand Down
9 changes: 5 additions & 4 deletions tabs/lib/_tab.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,15 @@

@include focus-ring.theme(
(
// desired border-radius is 8px and it's internally calc'd as sum
// of shape + offset (-7 + 15)
shape: 15px,
offset: -7px
'shape': 8px,
)
);
}

:host([selected]) md-focus-ring {
margin-bottom: calc(var(--_active-indicator-height) + 1px);
}

.button {
display: inline-flex;
align-items: center;
Expand Down
2 changes: 1 addition & 1 deletion tabs/lib/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export class Tab extends LitElement {
aria-label=${this.ariaLabel || nothing}
${ripple(this.getRipple)}
>
<md-focus-ring></md-focus-ring>
<md-focus-ring inward></md-focus-ring>
<md-elevation></md-elevation>
${when(this.showRipple, this.renderRipple)}
<span class="touch"></span>
Expand Down
6 changes: 4 additions & 2 deletions tokens/_md-comp-focus-ring.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ $supported-tokens: (
'active-width',
'color',
'duration',
'offset',
'inward-offset',
'outward-offset',
'shape',
'width',
// go/keep-sorted end
Expand All @@ -36,7 +37,8 @@ $_default: (
'active-width': if($exclude-hardcoded-values, null, 8px),
'color': map.get($deps, 'md-sys-color', 'secondary'),
'duration': map.get($deps, 'md-sys-motion', 'duration-long4'),
'offset': if($exclude-hardcoded-values, null, 2px),
'inward-offset': if($exclude-hardcoded-values, null, 0px),
'outward-offset': if($exclude-hardcoded-values, null, 2px),
'shape': map.get($deps, 'md-sys-shape', 'corner-full'),
'width': if($exclude-hardcoded-values, null, 3px),
);
Expand Down

0 comments on commit 26d69c2

Please sign in to comment.