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 = { title: 'h3', text: 'span', paragraph: 'p' }; /*** 复制到剪贴板:优先异步 API,非安全上下文退回复选式复制,两者都失败返回 false ***/ function copyText(text: string): Promise { 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)); }