# @videojs/element

[![package-badge]][package]

> **⚠️ Beta** Close to stable. Experimental adoption in real projects.

A lightweight reactive custom element base class for Video.js. Type-aligned with [Lit's ReactiveElement](https://github.com/lit/lit/tree/main/packages/reactive-element) but stripped down to only what we use.

```bash
npm install @videojs/element
```

## Why?

Video.js web components used `@lit/reactive-element` but only needed a fraction of its API — reactive properties, batched updates, and controllers. The rest (Shadow DOM, `static styles`, decorators, complex attribute converters, `shouldUpdate`, custom scheduling) shipped as dead code (~2.8 kB brotli).

`@videojs/element` provides the same programming model at ~840 B brotli.

## Quick Start

```ts
import { ReactiveElement } from '@videojs/element';
import type { PropertyValues } from '@videojs/element';

class MyElement extends ReactiveElement {
  static override properties = {
    label: { type: String },
    disabled: { type: Boolean },
  };

  label = 'Click me';
  disabled = false;

  protected override update(changed: PropertyValues): void {
    super.update(changed);
    this.textContent = this.label;
  }
}

customElements.define('my-element', MyElement);
```

## Alignment with Lit

The API is a subset of Lit's `ReactiveElement`. Types are aligned so controllers written for Lit work with `@videojs/element` without changes.

### What's included

- **`static properties`** — Declare reactive properties with `type` (`String`, `Boolean`, `Number`) and `attribute` (custom attribute name)
- **Reactive accessors** — Installed automatically, change detection via `Object.is()`
- **Batched updates** — Multiple property changes in one tick trigger a single update via `queueMicrotask()`
- **Full lifecycle** — `willUpdate` → `update` → `firstUpdated` (first time) → `updated` → `updateComplete`
- **`hasUpdated`** — `false` until first update completes, `true` during `firstUpdated` and `updated` (matches Lit)
- **`isUpdatePending`** — `true` while an update is queued or in progress
- **`performUpdate()`** — Synchronously flush a pending update
- **`scheduleUpdate()`** — Override point for custom update timing (default calls `performUpdate()`)
- **Reactive controllers** — `addController`/`removeController` with `hostConnected`, `hostDisconnected`, `hostUpdate`, `hostUpdated`
- **Element upgrade handling** — Properties set before registration are preserved

### What's NOT included

| Lit feature | Why excluded |
|---|---|
| Shadow DOM / `createRenderRoot()` | We use light DOM exclusively |
| `static styles` / CSS adoption | No shadow root to adopt into |
| Decorators (`@property`, `@state`) | We use `static properties` |
| `shouldUpdate()` | No use case for skipping updates |
| `getUpdateComplete()` | No async update chaining needed |
| `reflect` option | No property-to-attribute reflection |
| `converter` option | Simple type coercion is sufficient |
| `state` option | All properties are observable |
| `hasChanged` option | `Object.is()` is always used |

### Property inheritance

Lit walks the prototype chain to collect properties from all ancestors. We don't — subclasses that define their own `static properties` must spread the parent:

```ts
class FancyButton extends MyButton {
  static override properties = {
    ...MyButton.properties,
    variant: { type: String },
  };
}
```

This is only needed when a subclass declares `static properties`. If it doesn't, JS static property inheritance means the parent's properties are used automatically.

## Context

Context is re-exported from [`@lit/context`](https://github.com/lit/lit/tree/main/packages/context) — the same implementation used across the Lit ecosystem:

```ts
import { createContext, ContextProvider, ContextConsumer } from '@videojs/element/context';
```

This provides tree-scoped data sharing without prop drilling, using Lit's [Context Protocol](https://github.com/webcomponents-cg/community-protocols/blob/main/proposals/context.md).

## Community

If you need help with anything related to Video.js v10, or if you'd like to casually chat with other
members:

- [Join Discord Server][discord]
- [See GitHub Discussions][gh-discussions]

## License

[Apache-2.0](./LICENSE)

[package]: https://www.npmjs.com/package/@videojs/element
[package-badge]: https://img.shields.io/npm/v/@videojs/element?label=@videojs/element
[discord]: https://discord.gg/JBqHh485uF
[gh-discussions]: https://github.com/videojs/v10/discussions
