npm package: @jdlanglois/els
Description: Ultra-lightweight stream-based DOM library with keyed lists and routing
Author: Joe Langlois
Latest version: 1.4.0

# @jdlanglois/els

A lightweight, stream-based DOM library that combines the simplicity of **RE:DOM**'s `el` utility with the reactive power of **Mithril Streams**.

- ⚡️ **Direct DOM:** No virtual DOM overhead.
- 🔄 **Reactive:** Attributes, styles, and text nodes bind directly to streams.
- 🧹 **Auto-Cleanup:** Subscriptions are tracked and automatically ended when elements are removed.
- 🎯 **Simple API:** Familiar `el(selector, props, ...children)` pattern.

---

## Quick Start

```bash
bun add @jdlanglois/els mithril
```

### 1. Creating Elements
The core of the library is the `el` function. It uses a CSS-like selector to create real DOM nodes.

```typescript
import { el, mount } from '@jdlanglois/els';

// Create a simple card
const card = el('div.card#main', 
  { title: 'Hover me' }, 
  el('h1', 'Hello World'),
  el('p', 'This is a standard DOM element.')
);

mount(document.body, card);
```

### 2. Adding Reactivity
Instead of manual DOM updates, use **Streams**. When a stream's value changes, any part of the DOM bound to it updates automatically.

```typescript
import { el, stream } from '@jdlanglois/els';

const count = stream(0);

const counter = el('div',
  el('p', 'Count: ', count), // Text updates automatically
  el('button', { 
    onclick: () => count(count() + 1),
    style: { color: count.map(c => c > 10 ? 'red' : 'black') } // Styles update too!
  }, 'Increment')
);
```

### 3. Derived State with `computed`
Use `computed` to create values that depend on other streams. It automatically tracks dependencies—no manual dependency arrays needed.

```typescript
import { stream, computed } from '@jdlanglois/els';

const first = stream('John');
const last = stream('Doe');

// Automatically reacts when either stream changes
const full = computed(() => `${first()} ${last()}`);

const greeting = el('h1', 'Hello, ', full);
```

### 4. Components & Cleanup
Components are just functions that return elements. `@jdlanglois/els` handles memory management by tracking subscriptions and cleaning them up when an element is removed.

```typescript
import { el, remove } from '@jdlanglois/els';

const UserProfile = (name: string) => {
  return el('div.profile', 
    el('h2', name),
    { onremove: () => console.log('Cleaning up...') }
  );
};

const profile = UserProfile('Alice');
// ... later ...
remove(profile); // Recursively stops all child streams and triggers onremove
```

---

## Advanced Features

### Keyed Lists
For large collections, use `list()` for efficient reconciliation. It surgically moves DOM nodes instead of recreating them.

```typescript
import { el, list, stream } from '@jdlanglois/els';

const users = stream([{ id: 1, name: 'Alice' }]);

const view = el('ul', 
  list(users, u => u.id, (user, index) => 
    el('li', user.map(u => u.name))
  )
);
```

### Routing
The built-in router supports both Hash and History API modes, with automatic link interception.

```typescript
import { createRouter, tags } from '@jdlanglois/els';
const { h1, div } = tags;

const router = createRouter({ useHash: false });

const app = div(
  router.view({
    '/': () => h1('Home'),
    '/users/:id': (params) => h1(`User: ${params.id}`),
    '*': () => h1('404')
  })
);
```

### Styling (CSS-in-JS)
Write scoped CSS using tagged template literals. 

```typescript
import { css } from '@jdlanglois/els';

const cardStyle = css`
  padding: 20px;
  &:hover { transform: scale(1.05); }
  & span { color: blue; }
`;

const globalStyles = css.global`
  body { margin: 0; background: #eee; }
`;

const myCard = el('div', { class: cardStyle }, 'Hover me!');

---

## Alternative Syntax Options

While the builder-pattern (`el`) is the fastest and smallest, you can also use these optional syntaxes for a more declarative feel.

### 1. HTML Tagged Templates (`@jdlanglois/els/html`)
For a `lit-html` like experience without needing a transpiler.

```typescript
import { html } from '@jdlanglois/els/html';
import { stream } from '@jdlanglois/els';

const count = stream(0);
const view = html`
  <div class="counter">
    <p>Count: ${count}</p>
    <button @click="${() => count(count() + 1)}">Increment</button>
  </div>
`;
```

### 2. JSX / TSX (`@jdlanglois/els/jsx`)
For the most familiar declarative experience. Requires a transpiler (Vite, Bun, etc.).

**Configuration (`tsconfig.json`)**:
```json
{
  "compilerOptions": {
    "jsx": "react",
    "jsxFactory": "h",
    "jsxFragmentFactory": "Fragment"
  }
}
```

**Usage**:
```tsx
/** @jsx h */
import { h } from '@jdlanglois/els/jsx';
import { stream } from '@jdlanglois/els';

const App = () => {
  const count = stream(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onclick={() => count(count() + 1)}>Increment</button>
    </div>
  );
};
```

---

## Production Optimization (Vite)

To achieve **zero runtime overhead** for styles, use the Vite plugin. It extracts `css` template literals into a static CSS file during build.

```typescript
// vite.config.ts
import elPlugin from '@jdlanglois/els/vite';

export default {
  plugins: [elPlugin()]
};
```

---

## Technical Appendix

### Subscription Management
Subscriptions created via streams are stored using `Symbol.for('el-subscriptions')` on the DOM node. The `remove(el)` utility recursively traverses the tree to call `.end(true)` on every tracked subscription, ensuring no memory leaks.

### Attribute vs Property
`el` smartly decides how to apply props:
1. If the key exists as a property (e.g., `className`, `onclick`, `value`), it sets it directly.
2. If it's a `style` or `class` object, it handles merging/toggling.
3. Otherwise, it falls back to `setAttribute`.