An open API service indexing awesome lists of open source software.

https://github.com/febalist/element-detector


https://github.com/febalist/element-detector

Last synced: 4 months ago
JSON representation

Awesome Lists containing this project

README

          

# element-detector

A library for detecting elements appearing in the DOM. Executes callbacks when elements matching a selector are added to the page.

Uses [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) to efficiently track DOM changes and notify about new elements. Works at any stage of page lifecycle - during initial load, after DOMContentLoaded, or during dynamic content updates.

## Installation

### For Bundlers

```bash
npm install element-detector
```

### For Userscripts

Add the library via `@require` in your userscript metadata:

```javascript
// ==UserScript==
// @name My Userscript
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Example userscript using element-detector
// @require https://cdn.jsdelivr.net/npm/element-detector/dist/index.global.min.js
// @grant none
// ==/UserScript==

(function () {
'use strict';

// Library is available as window.ElementDetector
const {detect} = window.ElementDetector;

// Now you can use detect() in your script
detect('.some-element', (element) => {
console.log('Element found:', element);
});
})();
```

Alternative CDN links:

- jsDelivr (minified): `https://cdn.jsdelivr.net/npm/element-detector/dist/index.global.min.js`
- jsDelivr (unminified): `https://cdn.jsdelivr.net/npm/element-detector/dist/index.global.js`
- unpkg (minified): `https://unpkg.com/element-detector/dist/index.global.min.js`
- unpkg (unminified): `https://unpkg.com/element-detector/dist/index.global.js`

## Usage

### With callback

```typescript
import {detect} from 'element-detector';

// Called for each new element matching the selector
detect('.notification', (element) => {
console.log('New notification:', element);
});
```

### Without callback (Promise)

```typescript
import {detect} from 'element-detector';

// Returns a promise that resolves with the first matching element
const modal = await detect('.modal');
console.log('Modal appeared:', modal);
```

## API

### `detect(selector, callback?, options?)`

#### Parameters

**selector**: `string`

- CSS selector to match elements

**callback**: `(element: T) => void` (optional)

- Function called for each matching element
- If omitted, returns a Promise

**options**: `DetectOptions` (optional)

- Configuration object

#### Returns

- `Detector` - when callback is provided
- `Promise` - when callback is omitted (defaults to `{ once: true }`)

### Options

**existing**: `boolean` (default: `false`)

- When `true`, processes elements that already exist in the DOM at the time `detect` is called
- When `false`, only processes elements added after the call

```typescript
detect('.item', callback, {existing: true});
```

**once**: `boolean` (default: `false`)

- When `true`, stops watching after the first matching element
- Automatically calls `stop()` on the detector

```typescript
detect('.dialog', callback, {once: true});
```

**filter**: `(element: T) => boolean`

- Additional filtering function applied to matched elements
- Only elements for which the function returns `true` will trigger the callback

```typescript
detect('a', callback, {
filter: (link) => link.hostname !== window.location.hostname
});
```

**timeout**: `number`

- Time in milliseconds after which watching automatically stops
- Creates an internal AbortController that triggers after the specified time

```typescript
detect('.widget', callback, {timeout: 5000});
```

**signal**: `AbortSignal`

- External AbortSignal for manual control
- When the signal is aborted, watching stops

```typescript
const controller = new AbortController();
detect('.element', callback, {signal: controller.signal});

// Later
controller.abort();
```

### TypeScript Generics

Specify element type for proper typing:

```typescript
detect('.submit', (button) => {
button.disabled = false;
});

detect('a', (link) => {
console.log(link.href);
});

const img = await detect('img.hero');
```

### Interfaces

```typescript
interface DetectOptions {
existing?: boolean;
filter?: (element: T) => boolean;
once?: boolean;
timeout?: number;
signal?: AbortSignal;
}

interface Detector {
signal: AbortSignal; // Fires when watching stops
stop: () => void; // Manually stop watching
}
```

## Examples

### Initializing widgets

```typescript
detect('.date-picker', (element) => {
new DatePicker(element);
}, {existing: true});
```

### Waiting for dynamic content

```typescript
const item = await detect('.product[data-id="12345"]', {
timeout: 10000
});

item.scrollIntoView();
```

### Modal dialogs

```typescript
detect('.modal.confirmation', (modal) => {
const confirmBtn = modal.querySelector('.confirm');
confirmBtn?.addEventListener('click', handleConfirm);
});
```

### Processing external links

```typescript
detect('a', (link) => {
link.setAttribute('target', '_blank');
link.setAttribute('rel', 'noopener noreferrer');
}, {
filter: (link) => link.hostname !== window.location.hostname,
existing: true
});
```

### Timeout with fallback

```typescript
const detector = detect('.slow-widget', (widget) => {
initialize(widget);
}, {timeout: 5000});

detector.signal.addEventListener('abort', () => {
showFallback();
});
```

### Manual control with AbortController

```typescript
const controller = new AbortController();

detect('.live-update', (update) => {
processUpdate(update);
}, {signal: controller.signal});

// Stop watching when user leaves page section
document.addEventListener('navigate', () => {
controller.abort();
});
```

### Combining timeout and signal

```typescript
const controller = new AbortController();

const detector = detect('.element', callback, {
signal: controller.signal,
timeout: 10000
});

// Stops when either timeout is reached OR controller.abort() is called
// detector.signal combines both signals
```

### Waiting for third-party scripts

```typescript
const widget = await detect('.third-party-widget', {
timeout: 5000,
existing: true
});

initializeIntegration(widget);
```

## Browser Support

[Check browser compatibility](https://caniuse.com/mutationobserver,abortcontroller,mdn-api_abortsignal_any_static,promises)

Requires:

- [MutationObserver](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
- [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController)
- [AbortSignal.any()](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static)
- [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)