{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "highlight",
  "title": "Highlight",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-svelte/components/highlight/Highlight.svelte",
      "content": "<script lang=\"ts\" module>\n  import type { HTMLAttributes } from 'svelte/elements'\n\n  export interface HighlightProps extends HTMLAttributes<HTMLSpanElement> {\n    /** Text to search within. */\n    text: string\n    /** Query string or RegExp to highlight. */\n    query: string | RegExp\n    /** HTML tag used to wrap matched substrings. Default 'mark'. */\n    highlightTag?: 'mark' | 'span'\n    /** Class applied to each highlight wrapper. */\n    highlightClass?: string\n    /** Inline style applied to each highlight wrapper. */\n    highlightStyle?: string\n    /** Case-sensitive matching. Default false. */\n    caseSensitive?: boolean\n    /** Match whole words only. Default false. */\n    wholeWord?: boolean\n    /** Cap the number of highlights rendered. 0 = unlimited. Default 0. */\n    maxHighlights?: number\n    /** Total match count before maxHighlights cap. Mirrors the Vue `matchCount` emit.\n     *  NOTE: React's `onMatchCount` reports the *rendered* count instead — the\n     *  React names for these two payloads are `onTotalMatchCount` (total) and\n     *  `onMatchCount` (rendered), i.e. swapped relative to the Vue-legacy\n     *  names kept here. Both payloads are available; pick by payload. */\n    onMatchCount?: (count: number) => void\n    /** Alias of `onMatchCount` under React's name: total matches before the\n     *  `maxHighlights` cap (React `onTotalMatchCount` parity). */\n    onTotalMatchCount?: (count: number) => void\n    /** Highlights actually rendered after maxHighlights cap. Mirrors the Vue `renderedMatchCount` emit.\n     *  Same payload as React's `onMatchCount` (rendered). */\n    onRenderedMatchCount?: (count: number) => void\n  }\n\n  interface Segment {\n    text: string\n    match: boolean\n  }\n\n  function buildPattern(query: string | RegExp, caseSensitive: boolean, wholeWord: boolean): RegExp | null {\n    if (!query) return null\n    if (query instanceof RegExp) {\n      const flags = query.flags.includes('g') ? query.flags : query.flags + 'g'\n      return new RegExp(query.source, flags)\n    }\n    const escaped = query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n    const body = wholeWord ? `\\\\b${escaped}\\\\b` : escaped\n    return new RegExp(body, caseSensitive ? 'g' : 'gi')\n  }\n</script>\n\n<script lang=\"ts\">\n  import { untrack } from 'svelte'\n  import { cn } from '$lib/utils'\n\n  let {\n    class: className,\n    text,\n    query,\n    highlightTag = 'mark',\n    highlightClass,\n    highlightStyle,\n    caseSensitive = false,\n    wholeWord = false,\n    maxHighlights = 0,\n    onMatchCount,\n    onTotalMatchCount,\n    onRenderedMatchCount,\n    ...restProps\n  }: HighlightProps = $props()\n\n  const segments = $derived.by((): Segment[] => {\n    if (!text) return []\n    const pattern = buildPattern(query, caseSensitive, wholeWord)\n    if (!pattern) return [{ text, match: false }]\n\n    const out: Segment[] = []\n    let last = 0\n    let count = 0\n    let m: RegExpExecArray | null\n    while ((m = pattern.exec(text)) !== null) {\n      if (m.index > last) out.push({ text: text.slice(last, m.index), match: false })\n      out.push({ text: m[0], match: true })\n      last = m.index + m[0].length\n      count++\n      if (maxHighlights > 0 && count >= maxHighlights) break\n      if (m[0] === '') pattern.lastIndex++\n    }\n    if (last < text.length) out.push({ text: text.slice(last), match: false })\n\n    return out\n  })\n\n  const totalMatchCount = $derived.by(() => {\n    if (!text) return 0\n    const pattern = buildPattern(query, caseSensitive, wholeWord)\n    if (!pattern) return 0\n\n    let total = 0\n    let m: RegExpExecArray | null\n    while ((m = pattern.exec(text)) !== null) {\n      total++\n      if (m[0] === '') pattern.lastIndex++\n    }\n    return total\n  })\n\n  const renderedMatchCount = $derived(segments.filter((s) => s.match).length)\n\n  // Mirror the Vue emits (fired on mount + whenever the counts change). The\n  // callbacks run untracked so inline parent closures don't retrigger the effect.\n  $effect(() => {\n    const total = totalMatchCount\n    untrack(() => {\n      onMatchCount?.(total)\n      onTotalMatchCount?.(total)\n    })\n  })\n  $effect(() => {\n    const rendered = renderedMatchCount\n    untrack(() => onRenderedMatchCount?.(rendered))\n  })\n</script>\n\n<span data-uipkge data-slot=\"highlight\" {...restProps} class={cn(className)}>\n  {#each segments as seg, i (i)}\n    {#if seg.match}\n      <svelte:element\n        this={highlightTag}\n        data-slot=\"highlight-match\"\n        class={cn(\n          'bg-accent text-accent-foreground dark:bg-accent/30 dark:text-accent-foreground rounded px-0.5 font-medium',\n          highlightClass,\n        )}\n        style={highlightStyle}\n        >{seg.text}</svelte:element\n      >\n    {:else}\n      {seg.text}\n    {/if}\n  {/each}\n</span>\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/highlight/Highlight.svelte"
    },
    {
      "path": "packages/registry-svelte/components/highlight/index.ts",
      "content": "export { default as Highlight, type HighlightProps } from './Highlight.svelte'\n",
      "type": "registry:ui",
      "target": "~/src/lib/components/ui/highlight/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Highlights matching substrings in text for search results. Supports string or regex queries, case-sensitive and whole-word matching, custom highlight tag, and a max-highlight cap.",
  "categories": [
    "display",
    "utility"
  ]
}