56 lines
3.0 KiB
TypeScript
56 lines
3.0 KiB
TypeScript
import { h } from '@ui-arch/frame';
|
|
import type { TypographyProps } from './types.js';
|
|
|
|
// 标题层级 → 语义标签(level 已在入口夹到 1-5)
|
|
const HEADING = ['h1', 'h2', 'h3', 'h4', 'h5'] as const;
|
|
// 形态 → 缺省标签
|
|
const TAG: Record<string, string> = { title: 'h3', text: 'span', paragraph: 'p' };
|
|
|
|
/*** 复制到剪贴板:优先异步 API,非安全上下文退回复选式复制,两者都失败返回 false ***/
|
|
function copyText(text: string): Promise<boolean> {
|
|
if (navigator.clipboard?.writeText) return navigator.clipboard.writeText(text).then(() => true, () => false);
|
|
const box = h('textarea', { class: 'ua-type-copybox', readonly: true }, text) as HTMLTextAreaElement;
|
|
document.body.append(box);
|
|
box.select(); // 不可见文本框选中后再走兼容接口
|
|
const ok = document.execCommand('copy');
|
|
box.remove();
|
|
return Promise.resolve(ok);
|
|
}
|
|
|
|
/*** 类名拼装:形态 + 语义色 + 装饰位,均为纯类名,颜色留给样式表与令牌 ***/
|
|
function classOf(props: TypographyProps): string {
|
|
const variant = props.variant ?? 'text';
|
|
return ['ua-type', `ua-type-${variant}`, props.type ? `is-${props.type}` : '', props.strong ? 'is-strong' : '',
|
|
props.underline ? 'is-underline' : '', props.delete ? 'is-delete' : '', props.mark ? 'is-mark' : '',
|
|
props.code ? 'is-code' : '', props.align ? `is-${props.align}` : '', props.ellipsis ? 'is-clamped' : '']
|
|
.filter(Boolean).join(' ');
|
|
}
|
|
|
|
/*** 复制按钮:点击后短暂回显结果,1.2 秒复原;按钮自身不参与文本截断 ***/
|
|
function copyButton(el: HTMLElement): HTMLElement {
|
|
const btn = h('button', { class: 'ua-type-copy', type: 'button', 'aria-label': '复制文本' }, '复制');
|
|
btn.addEventListener('click', async () => {
|
|
const ok = await copyText(el.textContent ?? '');
|
|
btn.textContent = ok ? '已复制' : '复制失败';
|
|
btn.classList.toggle('is-ok', ok); // 成功态换色,失败保持中性
|
|
setTimeout(() => {
|
|
btn.textContent = '复制';
|
|
btn.classList.remove('is-ok');
|
|
}, 1200); // 回显窗口
|
|
});
|
|
return btn;
|
|
}
|
|
|
|
/*** 排版文本:variant 决定语义标签(title 出 h1-h5、paragraph 出 p、text 出 span),ellipsis 走多行截断,copyable 附复制按钮 ***/
|
|
export function typography(props: TypographyProps): HTMLElement {
|
|
const variant = props.variant ?? 'text';
|
|
const level = Math.min(Math.max(props.level ?? 3, 1), 5); // 层级夹取,避免越界索引
|
|
const tag = variant === 'title' ? HEADING[level - 1] : TAG[variant];
|
|
const plain = typeof props.content === 'string' ? props.content : null; // 只有字符串可安全截断
|
|
const el = h(tag, { class: classOf(props) }, props.content);
|
|
el.style.setProperty('--ua-type-clamp', String(props.ellipsis ?? 0)); // 行数交给 CSS 变量,样式表统一 clamp
|
|
if (plain && props.ellipsis) el.title = plain; // 截断后悬停可读全文
|
|
if (!props.copyable) return el;
|
|
return h('span', { class: 'ua-type-wrap' }, el, copyButton(el));
|
|
}
|