iter(g235): 新增 copyButton 复制按钮,剪贴板写入带已复制反馈与降级(72 组件)

This commit is contained in:
HIsheR
2026-10-26 21:26:11 +08:00
parent 7e5e9f50d6
commit 5443aad8be
11 changed files with 208 additions and 10 deletions
+32
View File
@@ -0,0 +1,32 @@
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);
}
+1
View File
@@ -71,3 +71,4 @@ export { virtualList } from './virtualList.js';
export { commandPalette } from './commandPalette.js';
export { timePicker } from './timePicker.js';
export { pinInput } from './pinInput.js';
export { copyButton } from './copyButton.js';
+11
View File
@@ -254,6 +254,17 @@ export interface PinInputProps {
onChange?: (value: string) => void; // 任一格变化回调(参数为当前已输入拼接串)
}
/*** 复制按钮入参 ***/
export interface CopyButtonProps {
text: string | (() => string); // 待复制内容;传函数则在点击瞬间取实时值
label?: string; // 按钮文案;缺省「复制」
copiedLabel?: string; // 复制成功后的短暂文案(2s 后还原);缺省「已复制」
variant?: 'primary' | 'ghost' | 'danger'; // 视觉强度;缺省 ghost
size?: 'sm' | 'md' | 'lg'; // 高度档位;缺省 md
disabled?: boolean | Signal<boolean>; // 禁用态:布尔直接置位,传信号则动态同步
onCopied?: (text: string) => void; // 复制成功回调(参数为实际写入的内容)
}
/*** 空状态入参 ***/
export interface EmptyProps {
title?: string; // 主标题;缺省不渲染