Files
KanbanGit/resources/js/lib/clipboard.ts
T

65 lines
2.2 KiB
TypeScript

/** Copy only after a user action; WebViews may not expose the async Clipboard API. */
export async function copyText(text: string): Promise<void> {
if (navigator.clipboard?.writeText) {
try {
await navigator.clipboard.writeText(text);
return;
} catch {
// Embedded browsers can deny the async API while allowing a selected copy.
}
}
const active = document.activeElement;
const selection = window.getSelection();
const ranges = selection
? Array.from({ length: selection.rangeCount }, (_, index) =>
selection.getRangeAt(index).cloneRange(),
)
: [];
const inputSelection =
active instanceof HTMLInputElement ||
active instanceof HTMLTextAreaElement
? ([
active.selectionStart,
active.selectionEnd,
active.selectionDirection,
] as const)
: null;
const field = document.createElement('textarea');
field.value = text;
field.readOnly = true;
field.style.cssText =
'position:fixed;opacity:0;pointer-events:none;font-size:16px';
(active?.closest('[role="dialog"]') ?? document.body).append(field);
try {
field.focus({ preventScroll: true });
field.select();
field.setSelectionRange(0, text.length);
if (!document.execCommand('copy')) {
throw new Error('Clipboard access unavailable');
}
} finally {
field.remove();
if (active instanceof HTMLElement) {
active.focus({ preventScroll: true });
}
if (selection) {
selection.removeAllRanges();
ranges.forEach((range) => selection.addRange(range));
}
if (
inputSelection &&
inputSelection[0] !== null &&
inputSelection[1] !== null &&
(active instanceof HTMLInputElement ||
active instanceof HTMLTextAreaElement)
) {
active.setSelectionRange(
inputSelection[0],
inputSelection[1],
inputSelection[2] ?? undefined,
);
}
}
}