mirror of
https://github.com/material-components/material-web.git
synced 2024-09-11 13:46:10 +03:00
feat(iconbutton)!: combine button, link, and toggle variants into single components
PiperOrigin-RevId: 521789432
This commit is contained in:
parent
2ca34df894
commit
0aa39e8153
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
import {ClassInfo} from 'lit/directives/class-map.js';
|
||||
|
||||
import {styles} from './lib/filled-styles.css.js';
|
||||
import {IconButtonToggle} from './lib/icon-button-toggle.js';
|
||||
import {styles as sharedStyles} from './lib/shared-styles.css.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-filled-icon-button-toggle': MdFilledIconButtonToggle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Icon buttons help people take supplementary actions with a single
|
||||
* tap. This variant can toggle between icons.
|
||||
*
|
||||
* @description
|
||||
* __Emphasis:__ Low emphasis – For optional or supplementary actions with the
|
||||
* least amount of prominence.
|
||||
*
|
||||
* __Rationale:__ The most compact and unobtrusive type of button, icon buttons
|
||||
* are used for optional supplementary actions such as "Bookmark" or "Star."
|
||||
*
|
||||
* __Example usages:__
|
||||
* - Add to Favorites
|
||||
* - Print
|
||||
*/
|
||||
@customElement('md-filled-icon-button-toggle')
|
||||
export class MdFilledIconButtonToggle extends IconButtonToggle {
|
||||
static override styles = [sharedStyles, styles];
|
||||
|
||||
protected override getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--filled': true,
|
||||
'md3-icon-button--toggle-filled': true,
|
||||
};
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ export class MdFilledIconButton extends IconButton {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--filled': true,
|
||||
'md3-icon-button--toggle-filled': this.toggle,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
import {ClassInfo} from 'lit/directives/class-map.js';
|
||||
|
||||
import {styles} from './lib/filled-styles.css.js';
|
||||
import {LinkIconButton} from './lib/link-icon-button.js';
|
||||
import {styles as sharedStyles} from './lib/shared-styles.css.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-filled-link-icon-button': MdFilledLinkIconButton;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Icon buttons help people take supplementary actions with a single
|
||||
* tap. This is a linkable variant.
|
||||
*
|
||||
* @description
|
||||
* __Emphasis:__ Low emphasis – For optional or supplementary actions with the
|
||||
* least amount of prominence.
|
||||
*
|
||||
* __Rationale:__ The most compact and unobtrusive type of button, icon buttons
|
||||
* are used for optional supplementary actions such as "Bookmark" or "Star."
|
||||
*
|
||||
* __Example usages:__
|
||||
* - Add to Favorites
|
||||
* - Print
|
||||
*/
|
||||
@customElement('md-filled-link-icon-button')
|
||||
export class MdFilledLinkIconButton extends LinkIconButton {
|
||||
static override styles = [sharedStyles, styles];
|
||||
|
||||
protected override getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--filled': true,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
import {ClassInfo} from 'lit/directives/class-map.js';
|
||||
|
||||
import {styles} from './lib/filled-tonal-styles.css.js';
|
||||
import {IconButtonToggle} from './lib/icon-button-toggle.js';
|
||||
import {styles as sharedStyles} from './lib/shared-styles.css.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-filled-tonal-icon-button-toggle': MdFilledTonalIconButtonToggle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Icon buttons help people take supplementary actions with a single
|
||||
* tap. This variant can toggle between icons.
|
||||
*
|
||||
* @description
|
||||
* __Emphasis:__ Low emphasis – For optional or supplementary actions with the
|
||||
* least amount of prominence.
|
||||
*
|
||||
* __Rationale:__ The most compact and unobtrusive type of button, icon buttons
|
||||
* are used for optional supplementary actions such as "Bookmark" or "Star."
|
||||
*
|
||||
* __Example usages:__
|
||||
* - Add to Favorites
|
||||
* - Print
|
||||
*/
|
||||
@customElement('md-filled-tonal-icon-button-toggle')
|
||||
export class MdFilledTonalIconButtonToggle extends IconButtonToggle {
|
||||
static override styles = [sharedStyles, styles];
|
||||
|
||||
protected override getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--filled-tonal': true,
|
||||
'md3-icon-button--toggle-filled-tonal': true,
|
||||
};
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ export class MdFilledTonalIconButton extends IconButton {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--filled-tonal': true,
|
||||
'md3-icon-button--toggle-filled-tonal': this.toggle,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
import {ClassInfo} from 'lit/directives/class-map.js';
|
||||
|
||||
import {styles} from './lib/filled-tonal-styles.css.js';
|
||||
import {LinkIconButton} from './lib/link-icon-button.js';
|
||||
import {styles as sharedStyles} from './lib/shared-styles.css.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-filled-tonal-link-icon-button': MdFilledTonalLinkIconButton;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Icon buttons help people take supplementary actions with a single
|
||||
* tap. This is a linkable variant.
|
||||
*
|
||||
* @description
|
||||
* __Emphasis:__ Low emphasis – For optional or supplementary actions with the
|
||||
* least amount of prominence.
|
||||
*
|
||||
* __Rationale:__ The most compact and unobtrusive type of button, icon buttons
|
||||
* are used for optional supplementary actions such as "Bookmark" or "Star."
|
||||
*
|
||||
* __Example usages:__
|
||||
* - Add to Favorites
|
||||
* - Print
|
||||
*/
|
||||
@customElement('md-filled-tonal-link-icon-button')
|
||||
export class MdFilledTonalLinkIconButton extends LinkIconButton {
|
||||
static override styles = [sharedStyles, styles];
|
||||
|
||||
protected override getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--filled-tonal': true,
|
||||
};
|
||||
}
|
||||
}
|
@ -7,12 +7,11 @@
|
||||
import {Harness} from '../testing/harness.js';
|
||||
|
||||
import {IconButton} from './lib/icon-button.js';
|
||||
import {IconButtonToggle} from './lib/icon-button-toggle.js';
|
||||
|
||||
/**
|
||||
* Test harness for icon buttons.
|
||||
*/
|
||||
export class IconButtonHarness extends Harness<IconButton|IconButtonToggle> {
|
||||
export class IconButtonHarness extends Harness<IconButton> {
|
||||
protected override async getInteractiveElement() {
|
||||
await this.element.updateComplete;
|
||||
return this.element.renderRoot.querySelector('.md3-icon-button') as
|
||||
|
@ -4,8 +4,6 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import './standard-icon-button-toggle.js';
|
||||
import './standard-link-icon-button.js';
|
||||
import './standard-icon-button.js';
|
||||
|
||||
import {html} from 'lit';
|
||||
@ -13,7 +11,6 @@ import {html} from 'lit';
|
||||
import {Environment} from '../testing/environment.js';
|
||||
|
||||
import {IconButtonHarness} from './harness.js';
|
||||
import {MdStandardIconButtonToggle} from './standard-icon-button-toggle.js';
|
||||
|
||||
const ICON_BUTTON_TEMPLATE = html`
|
||||
<md-standard-icon-button aria-label="Star">
|
||||
@ -21,15 +18,15 @@ const ICON_BUTTON_TEMPLATE = html`
|
||||
</md-standard-icon-button>
|
||||
`;
|
||||
const LINK_ICON_BUTTON_TEMPLATE = html`
|
||||
<md-standard-link-icon-button aria-label="Star">
|
||||
<md-standard-icon-button aria-label="Star" href="https://google.com">
|
||||
star
|
||||
</md-standard-link-icon-button>
|
||||
</md-standard-icon-button>
|
||||
`;
|
||||
const ICON_BUTTON_TOGGLE_TEMPLATE = html`
|
||||
<md-standard-icon-button-toggle aria-label="Star">
|
||||
<md-standard-icon-button toggle aria-label="Star">
|
||||
<md-icon slot="onIcon">star</md-icon>
|
||||
<md-icon slot="offIcon">star_border</md-icon>
|
||||
</md-standard-icon-button-toggle>
|
||||
</md-standard-icon-button>
|
||||
`;
|
||||
|
||||
interface IconButtonInternals {
|
||||
@ -43,7 +40,7 @@ describe('icon button tests', () => {
|
||||
it('setting `disabled` updates the disabled attribute on the native ' +
|
||||
'button element',
|
||||
async () => {
|
||||
const {element} = await setUpTest('md-standard-icon-button');
|
||||
const {element} = await setUpTest('button');
|
||||
const button = element.shadowRoot!.querySelector('button')!;
|
||||
|
||||
element.disabled = true;
|
||||
@ -58,7 +55,7 @@ describe('icon button tests', () => {
|
||||
it('setting `ariaLabel` updates the aria-label attribute on the native ' +
|
||||
'button element',
|
||||
async () => {
|
||||
const {element} = await setUpTest('md-standard-icon-button');
|
||||
const {element} = await setUpTest('button');
|
||||
const button = element.shadowRoot!.querySelector('button')!;
|
||||
|
||||
element.ariaLabel = 'test';
|
||||
@ -67,12 +64,13 @@ describe('icon button tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('md-standard-link-icon-button', () => {
|
||||
describe('md-standard-icon-button link', () => {
|
||||
it('setting `ariaLabel` updates the aria-label attribute on the anchor' +
|
||||
'tag',
|
||||
async () => {
|
||||
const {element} = await setUpTest('md-standard-link-icon-button');
|
||||
const {element} = await setUpTest('link');
|
||||
const anchor = element.shadowRoot!.querySelector('a')!;
|
||||
expect(anchor).not.toBeNull();
|
||||
|
||||
element.ariaLabel = 'test';
|
||||
await element.updateComplete;
|
||||
@ -80,11 +78,11 @@ describe('icon button tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('md-standard-icon-button-toggle', () => {
|
||||
describe('md-standard-icon-button toggle', () => {
|
||||
it('setting `disabled` updates the disabled attribute on the native ' +
|
||||
'button element',
|
||||
async () => {
|
||||
const {element} = await setUpTest('md-standard-icon-button-toggle');
|
||||
const {element} = await setUpTest('toggle');
|
||||
const button = element.shadowRoot!.querySelector('button')!;
|
||||
|
||||
element.disabled = true;
|
||||
@ -99,7 +97,7 @@ describe('icon button tests', () => {
|
||||
it('setting `ariaLabel` updates the aria-label attribute on the native ' +
|
||||
'button element',
|
||||
async () => {
|
||||
const {element} = await setUpTest('md-standard-icon-button-toggle');
|
||||
const {element} = await setUpTest('toggle');
|
||||
const button = element.shadowRoot!.querySelector('button')!;
|
||||
|
||||
element.ariaLabel = 'test';
|
||||
@ -108,12 +106,7 @@ describe('icon button tests', () => {
|
||||
});
|
||||
|
||||
it('toggles the `selected` state when button is clicked', async () => {
|
||||
const {element, harness} =
|
||||
await setUpTest('md-standard-icon-button-toggle');
|
||||
if (!(element instanceof MdStandardIconButtonToggle)) {
|
||||
throw new Error(
|
||||
'Icon button is not instance of MdStandardIconButtonToggle.');
|
||||
}
|
||||
const {element, harness} = await setUpTest('toggle');
|
||||
|
||||
expect(element.selected).toBeFalse();
|
||||
await harness.clickWithMouse();
|
||||
@ -123,12 +116,7 @@ describe('icon button tests', () => {
|
||||
});
|
||||
|
||||
it('fires input and change events when clicked', async () => {
|
||||
const {element, harness} =
|
||||
await setUpTest('md-standard-icon-button-toggle');
|
||||
if (!(element instanceof MdStandardIconButtonToggle)) {
|
||||
throw new Error(
|
||||
'Icon button is not instance of MdStandardIconButtonToggle.');
|
||||
}
|
||||
const {element, harness} = await setUpTest('toggle');
|
||||
let changeEvent = false;
|
||||
let inputEvent = false;
|
||||
element.addEventListener('input', () => inputEvent = true);
|
||||
@ -142,11 +130,7 @@ describe('icon button tests', () => {
|
||||
|
||||
it('setting `selected` updates the aria-pressed attribute on the native button element',
|
||||
async () => {
|
||||
const {element} = await setUpTest('md-standard-icon-button-toggle');
|
||||
if (!(element instanceof MdStandardIconButtonToggle)) {
|
||||
throw new Error(
|
||||
'Icon button is not instance of MdStandardIconButtonToggle.');
|
||||
}
|
||||
const {element} = await setUpTest('toggle');
|
||||
|
||||
element.selected = true;
|
||||
await element.updateComplete;
|
||||
@ -159,11 +143,7 @@ describe('icon button tests', () => {
|
||||
});
|
||||
|
||||
it('button with toggled aria label toggles aria label', async () => {
|
||||
const {element} = await setUpTest('md-standard-icon-button-toggle');
|
||||
if (!(element instanceof MdStandardIconButtonToggle)) {
|
||||
throw new Error(
|
||||
'Icon button is not instance of MdStandardIconButtonToggle.');
|
||||
}
|
||||
const {element} = await setUpTest('toggle');
|
||||
element.ariaLabelSelected = 'aria label on';
|
||||
element.ariaLabel = 'aria label off';
|
||||
await element.updateComplete;
|
||||
@ -213,23 +193,24 @@ describe('icon button tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
async function setUpTest(tagName: string) {
|
||||
async function setUpTest(type: string) {
|
||||
let template;
|
||||
switch (tagName) {
|
||||
case 'md-standard-icon-button':
|
||||
switch (type) {
|
||||
case 'button':
|
||||
template = ICON_BUTTON_TEMPLATE;
|
||||
break;
|
||||
case 'md-standard-link-icon-button':
|
||||
case 'link':
|
||||
template = LINK_ICON_BUTTON_TEMPLATE;
|
||||
break;
|
||||
case 'md-standard-icon-button-toggle':
|
||||
case 'toggle':
|
||||
template = ICON_BUTTON_TOGGLE_TEMPLATE;
|
||||
break;
|
||||
default:
|
||||
throw new Error('Invalid tag name: ' + tagName);
|
||||
throw new Error('Invalid tag name: ' + type);
|
||||
}
|
||||
|
||||
const element = env.render(template).querySelector(tagName)!;
|
||||
const element =
|
||||
env.render(template).querySelector('md-standard-icon-button')!;
|
||||
await env.waitForStability();
|
||||
return {
|
||||
element,
|
||||
|
@ -1,91 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
// Required for @ariaProperty
|
||||
// tslint:disable:no-new-decorators
|
||||
|
||||
import '../../focus/focus-ring.js';
|
||||
import '../../icon/icon.js';
|
||||
import '../../ripple/ripple.js';
|
||||
|
||||
import {html, nothing, TemplateResult} from 'lit';
|
||||
import {property} from 'lit/decorators.js';
|
||||
import {ClassInfo, classMap} from 'lit/directives/class-map.js';
|
||||
import {when} from 'lit/directives/when.js';
|
||||
|
||||
import {ripple} from '../../ripple/directive.js';
|
||||
|
||||
import {IconButton} from './icon-button.js';
|
||||
|
||||
/**
|
||||
* @fires change {Event}
|
||||
* Dispatched whenever `selected` is changed via user click
|
||||
*
|
||||
* @fires input {InputEvent}
|
||||
* Dispatched whenever `selected` is changed via user click
|
||||
*/
|
||||
export class IconButtonToggle extends IconButton {
|
||||
/**
|
||||
* The `aria-label` of the button when the toggle button is selected.
|
||||
*/
|
||||
@property({type: String}) ariaLabelSelected!: string;
|
||||
|
||||
/**
|
||||
* Sets the selected state. When false, displays the default icon. When true,
|
||||
* displays the `selectedIcon`, or the default icon If no `selectedIcon` is
|
||||
* provided.
|
||||
*/
|
||||
@property({type: Boolean, reflect: true}) selected = false;
|
||||
|
||||
protected override render(): TemplateResult {
|
||||
const hasToggledAriaLabel = this.ariaLabel && this.ariaLabelSelected;
|
||||
const ariaPressedValue = hasToggledAriaLabel ? nothing : this.selected;
|
||||
const ariaLabelValue = (hasToggledAriaLabel && this.selected) ?
|
||||
this.ariaLabelSelected :
|
||||
this.ariaLabel;
|
||||
return html`<button
|
||||
class="md3-icon-button ${classMap(this.getRenderClasses())}"
|
||||
aria-pressed="${ariaPressedValue}"
|
||||
aria-label="${ariaLabelValue || nothing}"
|
||||
?disabled="${this.disabled}"
|
||||
@focus="${this.handleFocus}"
|
||||
@blur="${this.handleBlur}"
|
||||
@pointerdown="${this.handlePointerDown}"
|
||||
@click="${this.handleClick}"
|
||||
${ripple(this.getRipple)}>
|
||||
${this.renderFocusRing()}
|
||||
${when(this.showRipple, this.renderRipple)}
|
||||
${this.renderTouchTarget()}
|
||||
${!this.selected ? this.renderIcon() : nothing}
|
||||
${this.selected ? this.renderSelectedIcon() : nothing}
|
||||
</button>`;
|
||||
}
|
||||
|
||||
protected renderSelectedIcon() {
|
||||
// Use default slot as fallback to not require specifying multiple icons
|
||||
return html`<md-icon class="md3-icon-button__icon md3-icon-button__icon--selected"><slot name="selectedIcon"><slot></slot></slot></md-icon>`;
|
||||
}
|
||||
|
||||
protected override getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--selected': this.selected,
|
||||
};
|
||||
}
|
||||
|
||||
protected handleClick() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selected = !this.selected;
|
||||
this.dispatchEvent(
|
||||
new InputEvent('input', {bubbles: true, composed: true}));
|
||||
// Bubbles but does not compose to mimic native browser <input> & <select>
|
||||
// Additionally, native change event is not an InputEvent.
|
||||
this.dispatchEvent(new Event('change', {bubbles: true}));
|
||||
}
|
||||
}
|
@ -11,11 +11,11 @@ import '../../focus/focus-ring.js';
|
||||
import '../../icon/icon.js';
|
||||
import '../../ripple/ripple.js';
|
||||
|
||||
import {html, LitElement, TemplateResult} from 'lit';
|
||||
import {html, LitElement, nothing, TemplateResult} from 'lit';
|
||||
import {property, query, queryAsync, state} from 'lit/decorators.js';
|
||||
import {ClassInfo, classMap} from 'lit/directives/class-map.js';
|
||||
import {ifDefined} from 'lit/directives/if-defined.js';
|
||||
import {when} from 'lit/directives/when.js';
|
||||
import {html as staticHtml, literal} from 'lit/static-html.js';
|
||||
|
||||
import {isRtl} from '../../controller/is-rtl.js';
|
||||
import {ariaProperty} from '../../decorators/aria-property.js';
|
||||
@ -24,6 +24,8 @@ import {ripple} from '../../ripple/directive.js';
|
||||
import {MdRipple} from '../../ripple/ripple.js';
|
||||
import {ARIAExpanded, ARIAHasPopup} from '../../types/aria.js';
|
||||
|
||||
type LinkTarget = '_blank'|'_parent'|'_self'|'_top';
|
||||
|
||||
// tslint:disable-next-line:enforce-comments-on-exported-symbols
|
||||
export class IconButton extends LitElement {
|
||||
/**
|
||||
@ -36,20 +38,46 @@ export class IconButton extends LitElement {
|
||||
*/
|
||||
@property({type: Boolean}) flipIconInRtl = false;
|
||||
|
||||
@state() protected flipIcon: boolean = isRtl(this, this.flipIconInRtl);
|
||||
|
||||
@ariaProperty
|
||||
@property({type: String, attribute: 'data-aria-label'})
|
||||
@property({attribute: 'data-aria-label'})
|
||||
override ariaLabel!: string;
|
||||
|
||||
@ariaProperty
|
||||
@property({type: String, attribute: 'data-aria-haspopup'})
|
||||
@property({attribute: 'data-aria-haspopup'})
|
||||
override ariaHasPopup!: ARIAHasPopup;
|
||||
|
||||
@ariaProperty
|
||||
@property({type: String, attribute: 'data-aria-expanded'})
|
||||
@property({attribute: 'data-aria-expanded'})
|
||||
override ariaExpanded!: ARIAExpanded;
|
||||
|
||||
/**
|
||||
* Sets the underlying `HTMLAnchorElement`'s `href` resource attribute.
|
||||
*/
|
||||
@property() href = '';
|
||||
|
||||
/**
|
||||
* Sets the underlying `HTMLAnchorElement`'s `target` attribute.
|
||||
*/
|
||||
@property() target = '';
|
||||
|
||||
/**
|
||||
* The `aria-label` of the button when the button is toggleable and selected.
|
||||
*/
|
||||
@property() ariaLabelSelected!: string;
|
||||
|
||||
/**
|
||||
* When true, the button will toggle between selected and unselected
|
||||
* states
|
||||
*/
|
||||
@property({type: Boolean}) toggle = false;
|
||||
|
||||
/**
|
||||
* Sets the selected state. When false, displays the default icon. When true,
|
||||
* displays the `selectedIcon`, or the default icon If no `selectedIcon` is
|
||||
* provided.
|
||||
*/
|
||||
@property({type: Boolean, reflect: true}) selected = false;
|
||||
|
||||
@query('button') protected buttonElement!: HTMLElement;
|
||||
|
||||
@queryAsync('md-ripple') protected ripple!: Promise<MdRipple|null>;
|
||||
@ -58,36 +86,70 @@ export class IconButton extends LitElement {
|
||||
|
||||
@state() protected showRipple = false;
|
||||
|
||||
@state() protected flipIcon: boolean = isRtl(this, this.flipIconInRtl);
|
||||
|
||||
protected getRipple = () => {
|
||||
this.showRipple = true;
|
||||
return this.ripple;
|
||||
};
|
||||
|
||||
protected readonly renderRipple = () => {
|
||||
return html`<md-ripple ?disabled="${this.disabled}"></md-ripple>`;
|
||||
return html`<md-ripple ?disabled="${
|
||||
!this.href && this.disabled}"></md-ripple>`;
|
||||
};
|
||||
|
||||
/**
|
||||
* Link buttons cannot be disabled.
|
||||
*/
|
||||
override willUpdate() {
|
||||
if (this.href) {
|
||||
this.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override render(): TemplateResult {
|
||||
return html`<button
|
||||
const tag = this.href ? literal`div` : literal`button`;
|
||||
const hasToggledAriaLabel = this.ariaLabel && this.ariaLabelSelected;
|
||||
const ariaPressedValue = hasToggledAriaLabel ? nothing : this.selected;
|
||||
let ariaLabelValue: string|typeof nothing = nothing;
|
||||
if (!this.href) {
|
||||
ariaLabelValue = (hasToggledAriaLabel && this.selected) ?
|
||||
this.ariaLabelSelected :
|
||||
this.ariaLabel;
|
||||
}
|
||||
return staticHtml`<${tag}
|
||||
class="md3-icon-button ${classMap(this.getRenderClasses())}"
|
||||
aria-label="${ifDefined(this.ariaLabel)}"
|
||||
aria-haspopup="${ifDefined(this.ariaHasPopup)}"
|
||||
aria-expanded="${ifDefined(this.ariaExpanded)}"
|
||||
?disabled="${this.disabled}"
|
||||
aria-label="${ariaLabelValue || nothing}"
|
||||
aria-haspopup="${!this.href && this.ariaHasPopup || nothing}"
|
||||
aria-expanded="${!this.href && this.ariaExpanded || nothing}"
|
||||
aria-pressed="${ariaPressedValue}"
|
||||
?disabled="${!this.href && this.disabled}"
|
||||
@focus="${this.handleFocus}"
|
||||
@blur="${this.handleBlur}"
|
||||
@pointerdown="${this.handlePointerDown}"
|
||||
@click="${this.handleClick}"
|
||||
${ripple(this.getRipple)}>
|
||||
${this.renderFocusRing()}
|
||||
${when(this.showRipple, this.renderRipple)}
|
||||
${this.renderIcon()}
|
||||
${!this.selected ? this.renderIcon() : nothing}
|
||||
${this.selected ? this.renderSelectedIcon() : nothing}
|
||||
${this.renderTouchTarget()}
|
||||
</button>`;
|
||||
${this.href && this.renderLink()}
|
||||
</${tag}>`;
|
||||
}
|
||||
|
||||
protected renderLink() {
|
||||
return html`<a class="md3-icon-button__link" href="${this.href}"
|
||||
target="${this.target as LinkTarget || nothing}"
|
||||
@focus="${this.handleFocus}"
|
||||
@blur="${this.handleBlur}"
|
||||
aria-label="${this.ariaLabel || nothing}"></a>`;
|
||||
}
|
||||
|
||||
protected getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
'md3-icon-button--flip-icon': this.flipIcon,
|
||||
'md3-icon-button--selected': this.toggle && this.selected,
|
||||
};
|
||||
}
|
||||
|
||||
@ -95,6 +157,11 @@ export class IconButton extends LitElement {
|
||||
return html`<md-icon class="md3-icon-button__icon"><slot></slot></md-icon>`;
|
||||
}
|
||||
|
||||
protected renderSelectedIcon() {
|
||||
// Use default slot as fallback to not require specifying multiple icons
|
||||
return html`<md-icon class="md3-icon-button__icon md3-icon-button__icon--selected"><slot name="selectedIcon"><slot></slot></slot></md-icon>`;
|
||||
}
|
||||
|
||||
protected renderTouchTarget(): TemplateResult {
|
||||
return html`<span class="md3-icon-button__touch"></span>`;
|
||||
}
|
||||
@ -121,4 +188,17 @@ export class IconButton extends LitElement {
|
||||
protected handleBlur() {
|
||||
this.showFocusRing = false;
|
||||
}
|
||||
|
||||
protected handleClick() {
|
||||
if (!this.toggle || this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selected = !this.selected;
|
||||
this.dispatchEvent(
|
||||
new InputEvent('input', {bubbles: true, composed: true}));
|
||||
// Bubbles but does not compose to mimic native browser <input> & <select>
|
||||
// Additionally, native change event is not an InputEvent.
|
||||
this.dispatchEvent(new Event('change', {bubbles: true}));
|
||||
}
|
||||
}
|
||||
|
@ -1,65 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {html, TemplateResult} from 'lit';
|
||||
import {property} from 'lit/decorators.js';
|
||||
import {classMap} from 'lit/directives/class-map.js';
|
||||
import {ifDefined} from 'lit/directives/if-defined.js';
|
||||
import {when} from 'lit/directives/when.js';
|
||||
|
||||
import {ripple} from '../../ripple/directive.js';
|
||||
|
||||
import {IconButton} from './icon-button.js';
|
||||
|
||||
/**
|
||||
* Note that we cast `linkTarget` to this type, below. The Lit compiler
|
||||
* enforces the `target` attribute value to be of this type, but this is not
|
||||
* compatible with the generated Wit Soy/JS, which expects `linkTarget`
|
||||
* to be a string type.
|
||||
*/
|
||||
type LinkTarget = '_blank'|'_parent'|'_self'|'_top';
|
||||
|
||||
// tslint:disable-next-line:enforce-comments-on-exported-symbols
|
||||
export class LinkIconButton extends IconButton {
|
||||
/**
|
||||
* Sets the underlying `HTMLAnchorElement`'s `href` resource attribute.
|
||||
*/
|
||||
@property({type: String}) linkHref = '';
|
||||
|
||||
/**
|
||||
* Sets the underlying `HTMLAnchorElement`'s `target` attribute.
|
||||
*/
|
||||
@property({type: String}) linkTarget!: string;
|
||||
|
||||
/**
|
||||
* Link buttons cannot be disabled.
|
||||
*/
|
||||
override disabled = false;
|
||||
|
||||
override willUpdate() {
|
||||
this.disabled = false;
|
||||
}
|
||||
|
||||
protected override render(): TemplateResult {
|
||||
return html`<div
|
||||
class="md3-icon-button ${classMap(this.getRenderClasses())}"
|
||||
@focus="${this.handleFocus}"
|
||||
@blur="${this.handleBlur}"
|
||||
@pointerdown="${this.handlePointerDown}"
|
||||
${ripple(this.getRipple)}>
|
||||
${this.renderFocusRing()}
|
||||
${when(this.showRipple, this.renderRipple)}
|
||||
${this.renderIcon()}
|
||||
${this.renderTouchTarget()}
|
||||
<a class="md3-icon-button__link" href="${this.linkHref}"
|
||||
target="${ifDefined(this.linkTarget as LinkTarget)}"
|
||||
aria-label="${ifDefined(this.ariaLabel)}"
|
||||
@focus="${this.handleFocus}"
|
||||
@blur="${this.handleBlur}">
|
||||
</a>
|
||||
</div>`;
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
import {ClassInfo} from 'lit/directives/class-map.js';
|
||||
|
||||
import {IconButtonToggle} from './lib/icon-button-toggle.js';
|
||||
import {styles} from './lib/outlined-styles.css.js';
|
||||
import {styles as sharedStyles} from './lib/shared-styles.css.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-outlined-icon-button-toggle': MdOutlinedIconButtonToggle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Icon buttons help people take supplementary actions with a single
|
||||
* tap. This variant can toggle between icons.
|
||||
*
|
||||
* @description
|
||||
* __Emphasis:__ Low emphasis – For optional or supplementary actions with the
|
||||
* least amount of prominence.
|
||||
*
|
||||
* __Rationale:__ The most compact and unobtrusive type of button, icon buttons
|
||||
* are used for optional supplementary actions such as "Bookmark" or "Star."
|
||||
*
|
||||
* __Example usages:__
|
||||
* - Add to Favorites
|
||||
* - Print
|
||||
*/
|
||||
@customElement('md-outlined-icon-button-toggle')
|
||||
export class MdOutlinedIconButtonToggle extends IconButtonToggle {
|
||||
static override styles = [sharedStyles, styles];
|
||||
|
||||
protected override getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--outlined': true,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2022 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
import {ClassInfo} from 'lit/directives/class-map.js';
|
||||
|
||||
import {LinkIconButton} from './lib/link-icon-button.js';
|
||||
import {styles} from './lib/outlined-styles.css.js';
|
||||
import {styles as sharedStyles} from './lib/shared-styles.css.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-outlined-link-icon-button': MdOutlinedLinkIconButton;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Icon buttons help people take supplementary actions with a single
|
||||
* tap. This is a linkable variant.
|
||||
*
|
||||
* @description
|
||||
* __Emphasis:__ Low emphasis – For optional or supplementary actions with the
|
||||
* least amount of prominence.
|
||||
*
|
||||
* __Rationale:__ The most compact and unobtrusive type of button, icon buttons
|
||||
* are used for optional supplementary actions such as "Bookmark" or "Star."
|
||||
*
|
||||
* __Example usages:__
|
||||
* - Add to Favorites
|
||||
* - Print
|
||||
*/
|
||||
@customElement('md-outlined-link-icon-button')
|
||||
export class MdOutlinedLinkIconButton extends LinkIconButton {
|
||||
static override styles = [sharedStyles, styles];
|
||||
|
||||
protected override getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--outlined': true,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
import {ClassInfo} from 'lit/directives/class-map.js';
|
||||
|
||||
import {IconButtonToggle} from './lib/icon-button-toggle.js';
|
||||
import {styles as sharedStyles} from './lib/shared-styles.css.js';
|
||||
import {styles} from './lib/standard-styles.css.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-standard-icon-button-toggle': MdStandardIconButtonToggle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Icon buttons help people take supplementary actions with a single
|
||||
* tap. This variant can toggle between icons.
|
||||
*
|
||||
* @description
|
||||
* __Emphasis:__ Low emphasis – For optional or supplementary actions with the
|
||||
* least amount of prominence.
|
||||
*
|
||||
* __Rationale:__ The most compact and unobtrusive type of button, icon buttons
|
||||
* are used for optional supplementary actions such as "Bookmark" or "Star."
|
||||
*
|
||||
* __Example usages:__
|
||||
* - Add to Favorites
|
||||
* - Print
|
||||
*/
|
||||
@customElement('md-standard-icon-button-toggle')
|
||||
export class MdStandardIconButtonToggle extends IconButtonToggle {
|
||||
static override styles = [sharedStyles, styles];
|
||||
|
||||
protected override getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--standard': true,
|
||||
};
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2021 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import {customElement} from 'lit/decorators.js';
|
||||
import {ClassInfo} from 'lit/directives/class-map.js';
|
||||
|
||||
import {LinkIconButton} from './lib/link-icon-button.js';
|
||||
import {styles as sharedStyles} from './lib/shared-styles.css.js';
|
||||
import {styles} from './lib/standard-styles.css.js';
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
'md-standard-link-icon-button': MdStandardLinkIconButton;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Icon buttons help people take supplementary actions with a single
|
||||
* tap. This is a linkable variant.
|
||||
*
|
||||
* @description
|
||||
* __Emphasis:__ Low emphasis – For optional or supplementary actions with the
|
||||
* least amount of prominence.
|
||||
*
|
||||
* __Rationale:__ The most compact and unobtrusive type of button, icon buttons
|
||||
* are used for optional supplementary actions such as "Bookmark" or "Star."
|
||||
*
|
||||
* __Example usages:__
|
||||
* - Add to Favorites
|
||||
* - Print
|
||||
*/
|
||||
@customElement('md-standard-link-icon-button')
|
||||
export class MdStandardLinkIconButton extends LinkIconButton {
|
||||
static override styles = [sharedStyles, styles];
|
||||
|
||||
protected override getRenderClasses(): ClassInfo {
|
||||
return {
|
||||
...super.getRenderClasses(),
|
||||
'md3-icon-button--standard': true,
|
||||
};
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user