iter(g235): 新增 copyButton 复制按钮,剪贴板写入带已复制反馈与降级(72 组件)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
@@ -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; // 主标题;缺省不渲染
|
||||
|
||||
@@ -85,7 +85,8 @@
|
||||
"virtualList(props):虚拟列表,固定行高长列表只渲染可视窗口 + 上下缓冲条,万级数据滚动不随总量掉帧;返回 { el, scrollTo } 句柄",
|
||||
"commandPalette(props):命令面板,⌘K / Ctrl+K 唤起,输入即过滤(主文案/说明/关键词三层),↑↓ 环形选择 + 回车执行 + Esc 收起,选中与关闭都归还焦点;返回 { el, open, close } 句柄",
|
||||
"timePicker(props):时间选择,时 / 分两列弹层,value 信号双向绑定 HH:mm,分钟可配步长,Esc 收起",
|
||||
"pinInput(props):验证码输入,N 格单字符数字输入,输入自动进格、退格回删、方向键移格、粘贴自动分发,value 信号双向绑定,mask 可密文显示"
|
||||
"pinInput(props):验证码输入,N 格单字符数字输入,输入自动进格、退格回删、方向键移格、粘贴自动分发,value 信号双向绑定,mask 可密文显示",
|
||||
"copyButton(props):复制按钮,点击把 text 写入剪贴板并短暂显示已复制,text 可传函数取实时值,clipboard API 不可用时降级 execCommand"
|
||||
],
|
||||
"keyTypes": [
|
||||
"ButtonProps",
|
||||
@@ -3752,6 +3753,61 @@
|
||||
"粘贴会自动分发:整段验证码(位数 ≥ 格数)从首格铺起,零散片段从当前格续填",
|
||||
"mask: true 走密文显示并声明 one-time-code,短信验证码可自动填充;disabled 支持布尔或信号禁用"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "copyButton",
|
||||
"title": "复制按钮 CopyButton",
|
||||
"demo": "sec-copybutton",
|
||||
"props": [
|
||||
{
|
||||
"n": "text",
|
||||
"t": "string | (() => string)",
|
||||
"r": 1,
|
||||
"d": "待复制内容;传函数则在点击瞬间取实时值(如输入框当前值)"
|
||||
},
|
||||
{
|
||||
"n": "label",
|
||||
"t": "string",
|
||||
"df": "复制",
|
||||
"d": "按钮文案"
|
||||
},
|
||||
{
|
||||
"n": "copiedLabel",
|
||||
"t": "string",
|
||||
"df": "已复制",
|
||||
"d": "复制成功后的短暂文案(2s 后还原为 label)"
|
||||
},
|
||||
{
|
||||
"n": "variant",
|
||||
"t": "'primary' | 'ghost' | 'danger'",
|
||||
"df": "ghost",
|
||||
"d": "视觉强度,沿用按钮三档"
|
||||
},
|
||||
{
|
||||
"n": "size",
|
||||
"t": "'sm' | 'md' | 'lg'",
|
||||
"df": "md",
|
||||
"d": "高度档位"
|
||||
},
|
||||
{
|
||||
"n": "disabled",
|
||||
"t": "boolean | Signal<boolean>",
|
||||
"d": "禁用态:布尔直接置位,传信号则动态同步"
|
||||
},
|
||||
{
|
||||
"n": "onCopied",
|
||||
"t": "(text: string) => void",
|
||||
"k": 1,
|
||||
"d": "复制成功回调(参数为实际写入的内容)"
|
||||
}
|
||||
],
|
||||
"example": "copyButton({ text: 'npm install @ui-arch/ui', onCopied: (t) => console.log(t) })",
|
||||
"steps": [
|
||||
"传 text 即可用,缺省幽灵按钮、文案「复制」;复制成功后短暂换成「已复制」,2s 自动还原",
|
||||
"内容需要实时取就传函数:text: () => textFieldValue(),点击瞬间才求值",
|
||||
"label / copiedLabel 可改文案;variant / size 透传给内部 button,视觉与普通按钮完全一致",
|
||||
"非安全上下文(http)或旧浏览器自动降级为隐藏 textarea + execCommand,无需业务处理兼容"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user