import { effect, h } from '@ui-arch/frame'; import type { ProgressRingProps } from './types.js'; const NS = 'http://www.w3.org/2000/svg'; // 缺省尺寸与环宽:96px 装得下百分比数字,8px 环宽在小尺寸下也不显笨 const SIZE = 96; const THICK = 8; /*** 建 SVG 元素并写属性:svg 必须 createElementNS,属性只能 setAttribute ***/ function svgEl(tag: string, attrs: Record): SVGElement { const el = document.createElementNS(NS, tag); for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, String(v)); return el; } /*** 环形进度:SVG 双圆(底环 + 进度弧)靠 dasharray 画弧,value 变化时弧长与文字同步 ***/ export function progressRing(props: ProgressRingProps): HTMLElement { const size = props.size ?? SIZE; const thick = props.thickness ?? THICK; const mid = size / 2; const r = (size - thick) / 2; // 半径要扣掉环宽,否则弧会超出画布 const c = 2 * Math.PI * r; // 周长:dasharray 的分母 const read = (): number => (typeof props.value === 'function' ? props.value() : props.value); // 数值 / 信号同一口径 const svg = svgEl('svg', { viewBox: `0 0 ${size} ${size}`, width: size, height: size, 'aria-hidden': 'true' }); const track = svgEl('circle', { cx: mid, cy: mid, r, fill: 'none', 'stroke-width': thick, class: 'ua-ring-track' }); // 弧从 12 点方向顺时针长:整圆旋转 -90°,否则默认从 3 点位置起步 const arc = svgEl('circle', { cx: mid, cy: mid, r, fill: 'none', 'stroke-width': thick, class: 'ua-ring-arc', 'stroke-dasharray': `${0} ${c}`, 'stroke-linecap': 'round', transform: `rotate(-90 ${mid} ${mid})`, }); const text = h('span', { class: 'ua-ring-text' }); const wrap = h( 'div', { class: 'ua-ring', role: 'progressbar', 'aria-valuemin': '0', 'aria-valuemax': '100', style: `width:${size}px;height:${size}px`, }, svg, text, ); svg.append(track, arc); // 底环先画,进度弧盖在上面 /*** 重绘:把百分比夹到 0-100,弧长按比例裁,文字与 aria-valuenow 同步 ***/ const paint = (): void => { const pct = Math.min(100, Math.max(0, Math.round(read()))); arc.setAttribute('stroke-dasharray', `${(c * pct) / 100} ${c}`); // 只画这一段,剩下留空 if (props.showText !== false) text.textContent = props.label ?? `${pct}%`; wrap.setAttribute('aria-valuenow', String(pct)); }; effect(paint); return wrap; }