blob: 4a2bceef1d554d835e22201e1d8023f8088dcdf0 [file] [log] [blame]
/**
* @license
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import '@polymer/iron-input/iron-input';
import '../gr-button/gr-button';
import '../gr-icons/gr-icons';
import {IronIconElement} from '@polymer/iron-icon';
import {assertIsDefined, queryAndAssert} from '../../../utils/common-util';
import {classMap} from 'lit-html/directives/class-map';
import {css, customElement, html, property} from 'lit-element';
import {GrLitElement} from '../../lit/gr-lit-element';
import {GrButton} from '../gr-button/gr-button';
const COPY_TIMEOUT_MS = 1000;
declare global {
interface HTMLElementTagNameMap {
'gr-copy-clipboard': GrCopyClipboard;
}
}
@customElement('gr-copy-clipboard')
export class GrCopyClipboard extends GrLitElement {
@property({type: String})
text: string | undefined;
@property({type: String})
buttonTitle: string | undefined;
@property({type: Boolean})
hasTooltip = false;
@property({type: Boolean})
hideInput = false;
static get styles() {
return [
css`
.text {
align-items: center;
display: flex;
flex-wrap: wrap;
}
.copyText {
flex-grow: 1;
margin-right: var(--spacing-s);
}
.hideInput {
display: none;
}
input#input {
font-family: var(--monospace-font-family);
font-size: var(--font-size-mono);
line-height: var(--line-height-mono);
width: 100%;
}
/*
* Typically icons are 20px, which is the normal line-height.
* The copy icon is too prominent at 20px, so we choose 16px
* here, but add 2x2px padding below, so the entire
* component should still fit nicely into a normal inline
* layout flow.
*/
#icon {
height: 16px;
width: 16px;
}
iron-icon {
color: var(--deemphasized-text-color);
vertical-align: top;
}
`,
];
}
render() {
// To pass CSS mixins for @apply to Polymer components, they need to appear
// in <style> inside the template.
const customStyle = html`
<style>
iron-icon {
--iron-icon-height: 20px;
--iron-icon-width: 20px;
}
gr-button {
--gr-button: {
padding: 2px;
}
}
</style>
`;
return html`${customStyle}
<div class="text">
<iron-input
class="copyText"
type="text"
@click="${this._handleInputClick}"
readonly=""
bind-value=${this.text}
>
<input
id="input"
is="iron-input"
class="${classMap({hideInput: this.hideInput})}"
type="text"
@click="${this._handleInputClick}"
readonly=""
.value=${this.text}
part="text-container-style"
/>
</iron-input>
<gr-button
id="copy-clipboard-button"
link=""
?has-tooltip=${this.hasTooltip}
class="copyToClipboard"
title="${this.buttonTitle}"
@click="${this._copyToClipboard}"
aria-label="Click to copy to clipboard"
>
<iron-icon id="icon" icon="gr-icons:content-copy"></iron-icon>
</gr-button>
</div> `;
}
focusOnCopy() {
queryAndAssert<GrButton>(this, '#copy-clipboard-button').focus();
}
_handleInputClick(e: MouseEvent) {
e.preventDefault();
const rootTarget = e.composedPath()[0];
(rootTarget as HTMLInputElement).select();
}
_copyToClipboard(e: MouseEvent) {
e.preventDefault();
e.stopPropagation();
this.text = queryAndAssert<HTMLInputElement>(this, '#input').value;
assertIsDefined(this.text, 'text');
this.iconEl.icon = 'gr-icons:check';
navigator.clipboard.writeText(this.text);
setTimeout(
() => (this.iconEl.icon = 'gr-icons:content-copy'),
COPY_TIMEOUT_MS
);
}
private get iconEl(): IronIconElement {
return queryAndAssert<IronIconElement>(this, '#icon');
}
}