33 lines
1.7 KiB
TypeScript
33 lines
1.7 KiB
TypeScript
import { h, signal } from '@ui-arch/frame';
|
|
import type { CopyButtonProps } from './types.js';
|
|
|
|
/*** 复制按钮:点击把 text 写入剪贴板,成功后短暂换成已复制文案再还原;clipboard API 不可用时降级 execCommand ***/
|
|
export function copyButton(props: CopyButtonProps): HTMLElement {
|
|
const label = signal(props.label ?? '复制'); // 文案信号:成功后短暂切换(h() 对函数型子节点响应式刷新)
|
|
let timer = 0; // 还原定时器句柄:连点时先清旧计时,避免文案提前回跳
|
|
|
|
/*** 写入剪贴板:标准 API 优先,失败(http 上下文 / 旧浏览器)走隐藏 textarea + execCommand 兜底 ***/
|
|
const write = async (): Promise<void> => {
|
|
const text = typeof props.text === 'function' ? props.text() : props.text; // 函数形式取实时值
|
|
try {
|
|
await navigator.clipboard.writeText(text);
|
|
} catch {
|
|
const ta = document.createElement('textarea');
|
|
ta.value = text;
|
|
ta.style.position = 'fixed';
|
|
ta.style.opacity = '0';
|
|
document.body.append(ta);
|
|
ta.select();
|
|
document.execCommand('copy'); // 降级路径:依赖临时选区
|
|
ta.remove();
|
|
}
|
|
label(props.copiedLabel ?? '已复制');
|
|
if (props.onCopied) props.onCopied(text);
|
|
clearTimeout(timer);
|
|
timer = window.setTimeout(() => label(props.label ?? '复制'), 2000); // 2s 后还原文案
|
|
};
|
|
|
|
// 直接构造按钮(ButtonProps.label 收窄为 string,无法传信号,复用会破坏其类型口径)
|
|
return h('button', { class: `ua-btn ua-btn-${props.variant ?? 'ghost'}${props.size && props.size !== 'md' ? ` is-${props.size}` : ''}`, type: 'button', disabled: props.disabled, onClick: () => { void write(); } }, label);
|
|
}
|