Skip to content
Merged
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a75697a
Make copy button style configurable
isaachilly Sep 3, 2026
c30924d
Stealth fix to restart the success timer if clicked before completed
isaachilly Sep 8, 2026
87d42a5
Simplify clipboard button styling
isaachilly Sep 8, 2026
1603d00
Improve clipboard button docs and accessibility
isaachilly Sep 8, 2026
2523f0a
Fix CopyToClipboard button class handling
isaachilly Sep 8, 2026
688ce0f
Handle clipboard copy failures
isaachilly Sep 8, 2026
194eb37
Fix copying/pasting error
isaachilly Sep 8, 2026
b46f61b
Await clipboard writes in copy component
isaachilly Sep 8, 2026
9875cd9
Fix tooltip text when no disabled message
isaachilly Sep 8, 2026
f77c82f
Clarify onFailure callback type
isaachilly Sep 9, 2026
848c7c4
Replace 2nd occurrence of vague param type
isaachilly Sep 9, 2026
c7ec5b0
Make clipboard component more readable
isaachilly Sep 9, 2026
f2fe2f1
Fix JSDOC
isaachilly Sep 9, 2026
bd46474
Remove aria-live
isaachilly Sep 9, 2026
42b10b8
Another JSDoc fix
isaachilly Sep 9, 2026
28bcab3
More JSDoc fixes
isaachilly Sep 9, 2026
52aa90c
Avoid undefined copy button IDs
isaachilly Sep 9, 2026
d04229f
Cache clipboard availability in constructor
isaachilly Sep 9, 2026
e3bf0cd
More JSDoc
isaachilly Sep 9, 2026
7698e40
Check onFailure callback is a function
isaachilly Sep 16, 2026
63bc8ca
Add loading class to copy button example className attr
isaachilly Sep 16, 2026
a32c1cc
Fix CopyToClipboard button type
isaachilly Sep 16, 2026
241b63b
Add content class support to copy button
isaachilly Sep 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 54 additions & 25 deletions Framework/Frontend/js/src/components/CopyToClipboardComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,44 @@ export class CopyToClipboardComponent extends StatefulComponent {
constructor() {
super();
this._successStateTimeout = null;

this._available = true;
this._message = '';

try {
this.checkClipboardAvailability();
} catch ({ message: errorMessage }) {
this._available = false;
this._message = errorMessage;
}
Comment thread
graduta marked this conversation as resolved.
}

/**
* Copies the specified text to the clipboard.
*
* @param {string} clipboardTargetValue The text to be copied to the clipboard.
* @returns {void}
* @param {(error: Error) => void} [onFailure] The callback function to be invoked if copying to the clipboard fails.
* @returns {Promise<void>}
*/
copyToClipboard(clipboardTargetValue) {
navigator.clipboard.writeText(clipboardTargetValue);
this._successStateTimeout = setTimeout(() => {
this._successStateTimeout = null;
async copyToClipboard(clipboardTargetValue, onFailure) {
try {
await navigator.clipboard.writeText(clipboardTargetValue);

if (this._successStateTimeout) {
clearTimeout(this._successStateTimeout);
}

this._successStateTimeout = setTimeout(() => {
this._successStateTimeout = null;
this.notify();
}, 2000);

this.notify();
}, 2000);
this.notify();
} catch (error) {
if (typeof onFailure === 'function') {
onFailure(error);
Comment thread
graduta marked this conversation as resolved.
}
}
}

/**
Expand Down Expand Up @@ -73,45 +96,51 @@ export class CopyToClipboardComponent extends StatefulComponent {
}

/**
* Check if the window is embeded in a frame.
* Check if the window is embedded in a frame.
*
* @returns {boolean} Returns `true` if it is embeded
* @returns {boolean} Returns `true` if it is embedded
*/
isWindowEmbedded() {
return window !== window.parent;
}

/**
* Renders the button that allows copying text to the clipboard.
* Attributes other than those listed are not forwarded to the button element.
*
* @param {vnode} vnode The virtual DOM node containing the attrs and children.
* @param {object} vnode.attrs The attributes passed to the component.
* @param {string} [vnode.attrs.value=''] The text to be copied to the clipboard.
* @param {string} [vnode.attrs.id] The unique identifier for the copy button will become 'copy-{id}'.
* @param {string} [vnode.attrs.className='btn-primary loading'] The CSS classes to be applied to the copy button.
* @param {string|object} [vnode.attrs.style] The inline styles to be applied to the copy button.
* @param {string} [vnode.attrs.contentClassName] The CSS classes to be applied to the content of the copy button.
* @param {(error: Error) => void} [vnode.attrs.onFailure] The callback function to be invoked if copying to the clipboard fails.
* @returns {Component} The copyToClipboard button component
*/
view(vnode) {
const { attrs, children } = vnode;
const { value: clipboardTargetValue = '', id } = attrs;
let available = true;
let message = '';

try {
this.checkClipboardAvailability();
} catch ({ message: errorMessage }) {
available = false;
message = errorMessage;
}
const { value: clipboardTargetValue = '', id, className = 'btn-primary', style, contentClassName, onFailure } = attrs;

const defaultContent = [iconLinkIntact(), children];
const successContent = [iconCheck(), h('', 'Copied!')];

return h(
'button.btn.btn-primary',
'button.btn',
Comment thread
graduta marked this conversation as resolved.
{
id: `copy-${id}`,
onclick: () => this.copyToClipboard(clipboardTargetValue),
disabled: !available,
title: message || null,
id: id ? `copy-${id}` : undefined,
type: 'button',
onclick: () => this.copyToClipboard(clipboardTargetValue, onFailure),
disabled: !this._available,
title: this._message,
style,
Comment thread
graduta marked this conversation as resolved.
className,
},
h('div.flex-row.g1', this._successStateTimeout ? successContent : defaultContent),
h(
'div.flex-row.g1',
{ className: contentClassName },
this._successStateTimeout ? successContent : defaultContent,
),
);
}
}
Loading