npm package: @lorb/ghost-highlight
Description: Highlight text without touching the DOM. Full CSS control, animations, live updates
Author: Lorb.studio
Latest version: 0.1.1

<p align="center">
  <a href="https://lorb.studio">
    <picture>
      <source media="(prefers-color-scheme: dark)" srcset="../.github/logo-light.svg">
      <img alt="Lorb.studio" src="../.github/logo-dark.svg" height="40">
    </picture>
  </a>
</p>

# @lorb/ghost-highlight

Highlight text without touching the DOM.

**Zero DOM mutation.** Uses the [Custom Highlight API](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API) — highlights are a rendering layer, not DOM nodes. No wrappers, no broken selectors, no killed event handlers.

**Full CSS control.** Background color, text color, underlines, text shadows, custom animations — anything CSS `::highlight()` supports.

**Live.** Highlights follow DOM changes automatically. Content updates? Highlights re-apply on their own.

```js
import { search } from '@lorb/ghost-highlight';

search('keyword');
// Every instance of "keyword" on the page is now highlighted.
// The DOM is untouched.
```

## Install

```bash
npm install @lorb/ghost-highlight
```

## What you can do

### Search and highlight text

```js
import { search, clear } from '@lorb/ghost-highlight';

search('error');              // highlight all matches
search('warning', {           // with custom color
  style: { backgroundColor: 'rgba(255, 200, 0, 0.3)' },
});
clear();                      // remove all highlights
```

### Style highlights with full CSS

Beyond background color — use text color, underlines, text shadows, and decorations.

```js
search('deprecated', {
  style: {
    backgroundColor: 'rgba(255, 0, 0, 0.1)',
    color: '#ff4444',
    textDecoration: 'line-through red',
  },
});

search('important', {
  style: {
    textShadow: '0 0 8px gold',
    backgroundColor: 'rgba(255, 215, 0, 0.2)',
  },
});
```

### Animate highlights

Three built-in animation presets, or define your own keyframes.

```js
// Built-in presets: fade-in, pulse, sweep
search('new', {
  style: {
    backgroundColor: 'gold',
    animation: { preset: 'pulse', iterations: 3 },
  },
});

// Custom keyframes — anything CSS animation supports
search('alert', {
  style: {
    animation: {
      keyframes: '@keyframes glow { 0% { background-color: transparent } 50% { background-color: red } 100% { background-color: transparent } }',
      duration: '1s',
      easing: 'ease-in-out',
      iterations: 'infinite',
    },
  },
});
```

### Multiple highlight groups that overlap

Different groups stack visually. Priority controls which renders on top.

```js
import { search, createGroup, clear } from '@lorb/ghost-highlight';

// Built-in presets with priority levels
search('error message', { preset: 'error' });    // priority 2 (top)
search('network', { preset: 'warning' });          // priority 1
search('response', { preset: 'info' });            // priority 0 (bottom)

clear('error');   // clear one group
clear();          // clear all
```

### Highlight specific ranges

For precise control — highlight exact positions, not just text matches.

```js
import { highlight } from '@lorb/ghost-highlight';

const range = new Range();
range.setStart(textNode, 10);
range.setEnd(textNode, 25);

highlight(range, 'selection', {
  backgroundColor: 'rgba(0, 120, 255, 0.3)',
});
```

### Live-update when content changes

Highlights automatically re-apply when the DOM mutates.

```js
import { search, observe } from '@lorb/ghost-highlight';

observe();          // start watching
search('keyword');  // highlights persist through content updates

// Dynamic content? SPA navigation? Editor changes?
// Highlights follow automatically.
```

### Check what's highlighted at a position

```js
import { getGroupsAt } from '@lorb/ghost-highlight';

const groups = getGroupsAt(textNode, 5);
// Returns array of group names at that cursor position
// Useful for context menus, inspectors, or tooltips
```

## Browser support

Uses the [Custom Highlight API](https://developer.mozilla.org/en-US/docs/Web/API/CSS_Custom_Highlight_API) — zero DOM mutation. Supported in Chrome 105+, Edge 105+, Safari 17.2+, Firefox 132+.

In older browsers, falls back to `<span>` wrapping (DOM mutation, with a console warning). Use `isSupported()` to check which mode is active.

## API

| Export | Description |
|--------|-------------|
| `search(query, options?)` | Highlight all text matches. Returns match count |
| `highlight(range, group?, style?)` | Highlight a specific Range |
| `clear(group?)` | Clear one or all groups |
| `createGroup(name, options?)` | Pre-configure a highlight group with styles and priority |
| `observe(root?, debounceMs?)` | Auto-update highlights on DOM changes |
| `getGroupsAt(node, offset)` | Query which groups exist at a position |
| `isSupported()` | Check for Custom Highlight API support |

**Built-in presets:** `search`, `error`, `warning`, `info`, `selection`
**Animation presets:** `fade-in`, `pulse`, `sweep`

## License

𖦹 MIT — [Lorb.studio](https://lorb.studio)