31 lines
1.5 KiB
TypeScript
31 lines
1.5 KiB
TypeScript
import { effect, h, type Signal } from '@ui-arch/frame';
|
|
import type { HighlightProps } from './types.js';
|
|
|
|
/*** 作用:读信号或原始值(string | Signal<string> 统一取值) ***/
|
|
const read = (v: string | Signal<string>): string => (typeof v === 'function' ? v() : v);
|
|
|
|
/*** 关键词高亮:命中片段包进 mark(大小写不敏感、全部命中),text / keyword 均支持信号响应式刷新,空关键词原样展示 ***/
|
|
export function highlight(props: HighlightProps): HTMLSpanElement {
|
|
const root = h('span', { class: 'ua-highlight' }) as HTMLSpanElement;
|
|
|
|
/*** 作用:按当前 text / keyword 重画内容:命中段包 mark,未命中段原样拼接(effect 追踪两个信号,任一变化重画) ***/
|
|
const render = (): void => {
|
|
const text = read(props.text);
|
|
const kw = read(props.keyword).trim();
|
|
root.textContent = '';
|
|
if (!kw) { root.textContent = text; return; } // 空关键词不高亮
|
|
const lower = text.toLowerCase();
|
|
const k = kw.toLowerCase();
|
|
let cursor = 0;
|
|
for (let idx = lower.indexOf(k); idx !== -1; idx = lower.indexOf(k, cursor)) {
|
|
if (idx > cursor) root.append(text.slice(cursor, idx)); // 命中前缀原样
|
|
root.append(h('mark', { class: 'ua-highlight-mark' }, text.slice(idx, idx + k.length))); // 命中段包 mark
|
|
cursor = idx + k.length;
|
|
}
|
|
if (cursor < text.length) root.append(text.slice(cursor)); // 尾段原样
|
|
};
|
|
|
|
effect(render);
|
|
return root;
|
|
}
|