elements-kit 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -118
- package/dist/{attributes-Dtn68R1u.d.mts → attributes-DILeh3-s.d.mts} +42 -9
- package/dist/attributes.d.mts +1 -1
- package/dist/attributes.mjs +32 -0
- package/dist/{define-CjbTZ3VG.d.mts → custom-elements-D5_NMNyD.d.mts} +20 -2
- package/dist/custom-elements.d.mts +2 -74
- package/dist/custom-elements.mjs +15 -86
- package/dist/{element-CGVy_8TW.mjs → element-w1GCIMVp.mjs} +33 -21
- package/dist/for.d.mts +31 -2
- package/dist/for.mjs +18 -2
- package/dist/infer-DuFY-y2b.d.mts +657 -0
- package/dist/integrations/react.d.mts +1 -1
- package/dist/integrations/react.mjs +6 -4
- package/dist/jsx-runtime/index.d.mts +2 -73
- package/dist/jsx-runtime/index.mjs +8 -14
- package/dist/{signals-J8dK_rA4.mjs → lib-D6duEs38.mjs} +1 -105
- package/dist/render.d.mts +21 -0
- package/dist/render.mjs +32 -0
- package/dist/scope-DM2gzOkb.mjs +45 -0
- package/dist/signals/index.d.mts +1 -1
- package/dist/signals/index.mjs +134 -1
- package/dist/{slot-Kb61AcgW.mjs → slot-CKtUoy2X.mjs} +0 -1
- package/dist/{slot-C7GQZe-r.d.mts → slot-D5iBUSAm.d.mts} +18 -1
- package/dist/slot.d.mts +1 -1
- package/dist/slot.mjs +1 -1
- package/dist/{test.BmQO5GaM-DfGStnii.mjs → test.BmQO5GaM-BeO5pvCo.mjs} +1 -1
- package/dist/utilities/_observe.mjs +2 -1
- package/dist/utilities/active-element.d.mts +1 -1
- package/dist/utilities/active-element.mjs +2 -1
- package/dist/utilities/active-element.test.mjs +1 -1
- package/dist/utilities/async.d.mts +39 -1
- package/dist/utilities/async.mjs +39 -1
- package/dist/utilities/async.test.mjs +3 -2
- package/dist/utilities/debounced.d.mts +12 -1
- package/dist/utilities/debounced.mjs +13 -1
- package/dist/utilities/debounced.test.mjs +3 -2
- package/dist/utilities/element-rect.d.mts +1 -1
- package/dist/utilities/element-rect.mjs +2 -1
- package/dist/utilities/element-rect.test.mjs +3 -2
- package/dist/utilities/element-scroll.d.mts +1 -1
- package/dist/utilities/element-scroll.test.mjs +3 -2
- package/dist/utilities/environment.d.mts +2 -0
- package/dist/utilities/environment.mjs +2 -0
- package/dist/utilities/event-driven.d.mts +1 -1
- package/dist/utilities/event-driven.mjs +2 -1
- package/dist/utilities/event-listener.d.mts +12 -1
- package/dist/utilities/event-listener.mjs +2 -1
- package/dist/utilities/event-listener.test.mjs +3 -2
- package/dist/utilities/focus-within.d.mts +1 -1
- package/dist/utilities/focus-within.mjs +2 -1
- package/dist/utilities/focus-within.test.mjs +3 -2
- package/dist/utilities/hover.d.mts +1 -1
- package/dist/utilities/hover.mjs +2 -1
- package/dist/utilities/hover.test.mjs +3 -2
- package/dist/utilities/intersection-observer.test.mjs +3 -2
- package/dist/utilities/interval.d.mts +14 -1
- package/dist/utilities/interval.mjs +2 -1
- package/dist/utilities/interval.test.mjs +3 -2
- package/dist/utilities/location.d.mts +1 -1
- package/dist/utilities/location.mjs +2 -1
- package/dist/utilities/location.test.mjs +1 -1
- package/dist/utilities/long-press.test.mjs +3 -2
- package/dist/utilities/media-devices.d.mts +1 -1
- package/dist/utilities/media-devices.mjs +2 -1
- package/dist/utilities/media-devices.test.mjs +3 -2
- package/dist/utilities/media-player.d.mts +1 -1
- package/dist/utilities/media-player.test.mjs +3 -2
- package/dist/utilities/media-query.d.mts +1 -1
- package/dist/utilities/media-query.mjs +2 -1
- package/dist/utilities/mutation-observer.test.mjs +3 -2
- package/dist/utilities/network.d.mts +1 -1
- package/dist/utilities/network.mjs +2 -1
- package/dist/utilities/network.test.mjs +1 -1
- package/dist/utilities/on-click-outside.test.mjs +3 -2
- package/dist/utilities/orientation.d.mts +1 -1
- package/dist/utilities/orientation.mjs +2 -1
- package/dist/utilities/previous.d.mts +13 -1
- package/dist/utilities/previous.mjs +14 -1
- package/dist/utilities/previous.test.mjs +3 -2
- package/dist/utilities/promise.d.mts +7 -1
- package/dist/utilities/promise.mjs +2 -1
- package/dist/utilities/promise.test.mjs +3 -2
- package/dist/utilities/retry.d.mts +15 -0
- package/dist/utilities/retry.mjs +17 -1
- package/dist/utilities/retry.test.mjs +3 -2
- package/dist/utilities/routing.d.mts +12 -1
- package/dist/utilities/routing.mjs +13 -1
- package/dist/utilities/routing.test.mjs +1 -1
- package/dist/utilities/search-params.d.mts +1 -1
- package/dist/utilities/search-params.test.mjs +3 -2
- package/dist/utilities/ssr.test.mjs +1 -1
- package/dist/utilities/storage.d.mts +18 -1
- package/dist/utilities/storage.mjs +17 -0
- package/dist/utilities/storage.test.mjs +3 -2
- package/dist/utilities/throttled.d.mts +12 -1
- package/dist/utilities/throttled.mjs +13 -1
- package/dist/utilities/throttled.test.mjs +3 -2
- package/dist/utilities/timeout.d.mts +1 -1
- package/dist/utilities/timeout.mjs +2 -1
- package/dist/utilities/timeout.test.mjs +3 -2
- package/dist/utilities/window-focus.d.mts +1 -1
- package/dist/utilities/window-focus.mjs +2 -1
- package/dist/utilities/window-size.d.mts +1 -1
- package/dist/utilities/window-size.mjs +2 -1
- package/dist/utilities/window-size.test.mjs +1 -1
- package/package.json +1 -1
- package/dist/index-DydGTqZU.d.mts +0 -315
- package/dist/infer-BfzRJoCn.d.mts +0 -203
- package/dist/polyfill-BVNd6ogU.d.mts +0 -9
- /package/dist/{magic-string.es-i62WTP6J.mjs → magic-string.es-cTgJnTCj.mjs} +0 -0
package/README.md
CHANGED
|
@@ -1,41 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
<img src="docs/public/og.svg" alt="ElementsKit" width="600" />
|
|
3
|
-
</p>
|
|
1
|
+
# ElementsKit 🌱
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
**Universal reactive primitives for the web.** Signals, JSX, and custom elements that work anywhere — standalone, inside React, Vue, or any framework, or as the foundation of your own component model.
|
|
3
|
+
**Universal reactive primitives for the web.** Signals, JSX, custom elements, and browser-API helpers. Import one at a time, compose them, or use any of them inside vanilla JS, React, Vue, or any framework.
|
|
8
4
|
|
|
9
5
|
```tsx
|
|
10
|
-
import { signal, computed
|
|
11
|
-
import {
|
|
6
|
+
import { signal, computed } from "elements-kit/signals";
|
|
7
|
+
import { render } from "elements-kit/render";
|
|
8
|
+
import type { ReactiveProps } from "elements-kit/jsx-runtime";
|
|
9
|
+
|
|
10
|
+
function Counter(props: ReactiveProps<{ initial?: number }>) {
|
|
11
|
+
const count = signal(props.initial() ?? 0);
|
|
12
|
+
const doubled = computed(() => count() * 2);
|
|
13
|
+
|
|
14
|
+
return (
|
|
15
|
+
<section>
|
|
16
|
+
<p><strong>{count}</strong> × 2 = <strong>{doubled}</strong></p>
|
|
17
|
+
<button on:click={() => count(count() + 1)}>+1</button>{" "}
|
|
18
|
+
<button on:click={() => count(count() - 1)}>−1</button>
|
|
19
|
+
</section>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
12
22
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
static [attr] = {
|
|
16
|
-
count(this: CounterElement, value: string | null) {
|
|
17
|
-
this.count = Number(value ?? 0);
|
|
18
|
-
},
|
|
19
|
-
};
|
|
23
|
+
render(document.getElementById("app")!, () => <Counter initial={0} />);
|
|
24
|
+
```
|
|
20
25
|
|
|
21
|
-
|
|
22
|
-
doubled = computed(() => this.count * 2);
|
|
26
|
+
## Installation
|
|
23
27
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
```sh
|
|
29
|
+
npm install elements-kit
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Configure JSX in your `tsconfig.json`:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"compilerOptions": {
|
|
37
|
+
"jsx": "react-jsx",
|
|
38
|
+
"jsxImportSource": "elements-kit"
|
|
32
39
|
}
|
|
33
40
|
}
|
|
34
|
-
|
|
35
|
-
customElements.define("x-counter", CounterElement);
|
|
36
41
|
```
|
|
37
42
|
|
|
38
|
-
|
|
43
|
+
## Why ElementsKit
|
|
44
|
+
|
|
45
|
+
ElementsKit is a library of reactive primitives, not a framework. Each piece is its own import, runs on its own, and composes with the others — inside React, inside a custom element, or on its own in a script.
|
|
46
|
+
|
|
47
|
+
- **Compose, don't configure.** Small focused APIs — `signal`, `computed`, `on`, `fromEvent`, `async`. Combine primitives instead of maintaining an overloaded interface.
|
|
48
|
+
|
|
49
|
+
- **Close to the platform.** JSX compiles to `document.createElement`. `promise` extends `Promise`. Custom elements *are* `HTMLElement`. Thin or absent abstraction layers — no virtual DOM, no proxies, no build steps.
|
|
50
|
+
|
|
51
|
+
- **Predictable and explicit — no magic.** `signal/compose` are reactive; nothing else is. No heuristic dependency tracking, no hidden subscriptions.
|
|
52
|
+
|
|
53
|
+
- **Designed for the AI age.** Code is cheap; maintenance still isn't. Primitives compose into higher-level blocks. Swap one block at a time instead of maintaining long lines of code.
|
|
54
|
+
|
|
55
|
+
- **Bundler-friendly.** Every primitive is its own subpath — `elements-kit/signals`, `elements-kit/utilities/*`, `elements-kit/integrations/*`. Import only what you need.
|
|
39
56
|
|
|
40
57
|
## Packages
|
|
41
58
|
|
|
@@ -43,71 +60,16 @@ Every feature is a separate subpath export — import only what you use.
|
|
|
43
60
|
|
|
44
61
|
| Entry | Purpose |
|
|
45
62
|
|-------|---------|
|
|
46
|
-
| `elements-kit` | `For` component and core re-exports |
|
|
47
63
|
| `elements-kit/signals` | `signal`, `computed`, `effect`, `effectScope`, `batch`, `untracked`, `trigger`, `onCleanup`, `MaybeReactive`, `resolve`, `resolveProps`, `@reactive` |
|
|
48
64
|
| `elements-kit/render` | `render(target, setup)` — mount a node with a scoped lifetime; returns `unmount` |
|
|
49
65
|
| `elements-kit/attributes` | `@attributes` decorator + `ATTRIBUTES` symbol |
|
|
50
66
|
| `elements-kit/slot` | `Slot`, `Slots`, `SLOTS` symbol — comment-marker DOM regions |
|
|
51
67
|
| `elements-kit/custom-elements` | `defineElement`, `CustomElementRegistry` |
|
|
52
68
|
| `elements-kit/for` | `For` keyed-list component |
|
|
53
|
-
| `elements-kit/jsx-runtime` | JSX factory + type helpers (`ElementProps`, `Props`, `ComponentProps`, `MaybeReactiveProps`, `Require`) — configure via `jsxImportSource` |
|
|
69
|
+
| `elements-kit/jsx-runtime` | JSX factory + type helpers (`ElementProps`, `Props`, `ComponentProps`, `MaybeReactiveProps`, `ReactiveProps`, `Require`) — configure via `jsxImportSource` |
|
|
54
70
|
| `elements-kit/integrations/react` | `useSignal`, `useScope` React bridge hooks |
|
|
55
71
|
| `elements-kit/utilities/*` | Reactive browser-API utilities — see [src/utilities/README.md](src/utilities/README.md) |
|
|
56
72
|
|
|
57
|
-
## Repository
|
|
58
|
-
|
|
59
|
-
- [src/](src/) — library source ([signals](src/signals/), [jsx-runtime](src/jsx-runtime/), [utilities](src/utilities/), [integrations](src/integrations/))
|
|
60
|
-
- [docs/](docs/) — Astro + Starlight documentation site
|
|
61
|
-
- [example/](example/) — Vite sandbox
|
|
62
|
-
- [ARCHITECTURE.md](ARCHITECTURE.md) — how the library works (reactive model, JSX, custom elements, cleanup)
|
|
63
|
-
- [CONTRIBUTING.md](CONTRIBUTING.md) — quick start, quality bars, versioning, PR checklist
|
|
64
|
-
- [DOCS.md](DOCS.md) — doc-authoring rules
|
|
65
|
-
- [AGENTS.md](AGENTS.md) — agent navigation map
|
|
66
|
-
- [src/utilities/README.md](src/utilities/README.md) — utilities catalog and dependency graph
|
|
67
|
-
|
|
68
|
-
---
|
|
69
|
-
|
|
70
|
-
## Why ElementsKit
|
|
71
|
-
|
|
72
|
-
Modern UI frameworks solve reactivity and rendering together — you adopt the whole system or none of it. ElementsKit separates the two:
|
|
73
|
-
|
|
74
|
-
- **Signals** are the reactive core — fine-grained, framework-agnostic, composable with any rendering model.
|
|
75
|
-
- **JSX** compiles to real `document.createElement` calls — no virtual DOM, no runtime overhead.
|
|
76
|
-
- **Custom elements** are standard browser components — ElementsKit enhances them with signals and JSX without wrapping or abstracting the platform.
|
|
77
|
-
|
|
78
|
-
Use one piece, or all three. Integrate with React for complex UIs. Build web components that work anywhere HTML does.
|
|
79
|
-
|
|
80
|
-
### Primitives first, composable abstractions
|
|
81
|
-
|
|
82
|
-
Small primitives, one job each, typed interfaces at every boundary. Abstraction is assembled, not inherited — compose upward from `signal` → `sync(fromEvent(...), ...)` → `<For>` and stop at the level your feature needs. Designed for AI agents as much as for humans: each step is verifiable on its own, and there's no framework-specific mental model to learn before writing the first line.
|
|
83
|
-
|
|
84
|
-
- **No prop matrices.** No single-system components with forty flags where legal combinations aren't obvious until you read the source.
|
|
85
|
-
- **No hidden coupling.** Blocks meet at their types; invariants don't leak across layers.
|
|
86
|
-
- **Close to the platform.** `promise` wraps a native `Promise`. JSX compiles to `document.createElement`. Custom elements are `HTMLElement` subclasses. You already know the underlying shape.
|
|
87
|
-
- **Minimal surface.** `async` has no cache, no query client, no query/mutation split. If you need those, compose them on top — don't inherit them by default.
|
|
88
|
-
- **Swap, don't rewrite.** Replace one block (`throttled` → `debounced`) without touching the ones around it.
|
|
89
|
-
|
|
90
|
-
---
|
|
91
|
-
|
|
92
|
-
## Installation
|
|
93
|
-
|
|
94
|
-
```sh
|
|
95
|
-
npm install elements-kit
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
Configure JSX in your `tsconfig.json`:
|
|
99
|
-
|
|
100
|
-
```json
|
|
101
|
-
{
|
|
102
|
-
"compilerOptions": {
|
|
103
|
-
"jsx": "react-jsx",
|
|
104
|
-
"jsxImportSource": "elements-kit"
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
---
|
|
110
|
-
|
|
111
73
|
## Signals
|
|
112
74
|
|
|
113
75
|
Fine-grained reactive state. Signals track their dependencies automatically — only the exact computeds and effects that depend on a changed signal are re-evaluated.
|
|
@@ -161,15 +123,13 @@ export const cart = new CartStore();
|
|
|
161
123
|
|
|
162
124
|
Stores are **framework-agnostic** — the same instance drives a custom element, a React component, and a plain effect in sync.
|
|
163
125
|
|
|
164
|
-
---
|
|
165
|
-
|
|
166
126
|
## JSX → DOM
|
|
167
127
|
|
|
168
128
|
JSX compiles directly to `document.createElement`. No virtual DOM, no diffing.
|
|
169
129
|
|
|
170
130
|
```tsx
|
|
171
131
|
// This:
|
|
172
|
-
const el = <button
|
|
132
|
+
const el = <button on:click={() => count(count() + 1)}>{count}</button>;
|
|
173
133
|
|
|
174
134
|
// Is equivalent to:
|
|
175
135
|
const el = document.createElement("button");
|
|
@@ -193,14 +153,11 @@ const name = signal("Alice");
|
|
|
193
153
|
| Syntax | Effect |
|
|
194
154
|
|--------|--------|
|
|
195
155
|
| `{signal}` / `{() => fn()}` | Live-bound reactive child |
|
|
196
|
-
| `
|
|
197
|
-
| `on:click={fn}` | Explicit event namespace |
|
|
156
|
+
| `on:click={fn}` | Event listener (case-preserving event name) |
|
|
198
157
|
| `class:active={bool}` | Reactive `classList.toggle` |
|
|
199
158
|
| `style:color={value}` | Reactive inline style property |
|
|
200
159
|
| `prop:foo={val}` | Force property assignment (skips `setAttribute`) |
|
|
201
160
|
|
|
202
|
-
---
|
|
203
|
-
|
|
204
161
|
## Class Components
|
|
205
162
|
|
|
206
163
|
Any class with a `render()` method returning an `Element` is a component. Components own their state and produce elements.
|
|
@@ -217,7 +174,7 @@ class Counter {
|
|
|
217
174
|
return (
|
|
218
175
|
<section>
|
|
219
176
|
<p>{() => this.count} × 2 = {this.doubled}</p>
|
|
220
|
-
<button
|
|
177
|
+
<button on:click={() => this.count++}>+1</button>
|
|
221
178
|
</section>
|
|
222
179
|
) as Element;
|
|
223
180
|
}
|
|
@@ -226,8 +183,6 @@ class Counter {
|
|
|
226
183
|
const unmount = render(document.getElementById("app")!, () => <Counter/>);
|
|
227
184
|
```
|
|
228
185
|
|
|
229
|
-
---
|
|
230
|
-
|
|
231
186
|
## Custom Elements
|
|
232
187
|
|
|
233
188
|
ElementsKit enhances native `HTMLElement` subclasses — start with the platform, add only what you need.
|
|
@@ -254,7 +209,7 @@ class CounterElement extends HTMLElement {
|
|
|
254
209
|
this.#unmount = render(this, () => (
|
|
255
210
|
<section>
|
|
256
211
|
<p>{() => this.count} × 2 = {this.doubled}</p>
|
|
257
|
-
<button
|
|
212
|
+
<button on:click={() => this.count++}>+1</button>
|
|
258
213
|
</section>
|
|
259
214
|
));
|
|
260
215
|
}
|
|
@@ -288,9 +243,7 @@ declare module "elements-kit/custom-elements" {
|
|
|
288
243
|
// Now `<x-counter count={5} />` is fully typed — no hand-written `declare global` block.
|
|
289
244
|
```
|
|
290
245
|
|
|
291
|
-
See [Types](docs/src/content/docs/
|
|
292
|
-
|
|
293
|
-
---
|
|
246
|
+
See [Types](docs/src/content/docs/elements/types.mdx) for the full set of prop-inference helpers.
|
|
294
247
|
|
|
295
248
|
## React Integration
|
|
296
249
|
|
|
@@ -316,8 +269,6 @@ function CartSummary() {
|
|
|
316
269
|
|
|
317
270
|
The same `cart` store drives custom elements, React trees, and plain scripts — all in sync.
|
|
318
271
|
|
|
319
|
-
---
|
|
320
|
-
|
|
321
272
|
## Utilities
|
|
322
273
|
|
|
323
274
|
Pre-built reactive wrappers around common browser APIs. Each utility lives at its own subpath (`elements-kit/utilities/<name>`) and ships as its own entry — you pay only for what you import. Full catalog in [src/utilities/README.md](src/utilities/README.md).
|
|
@@ -344,8 +295,6 @@ import { windowFocused } from "elements-kit/utilities/window-focus";
|
|
|
344
295
|
effect(() => console.log("online:", online(), "focused:", windowFocused()));
|
|
345
296
|
```
|
|
346
297
|
|
|
347
|
-
---
|
|
348
|
-
|
|
349
298
|
## Async & Promise
|
|
350
299
|
|
|
351
300
|
Two primitives convert imperative async work into reactive state: `promise` (minimal, any `Promise` → reactive state) and `async` (full controller with start/stop/run and optional reactive input).
|
|
@@ -436,8 +385,6 @@ const fetchTodo = async(() => {
|
|
|
436
385
|
effect(() => console.log(fetchTodo.state, fetchTodo.value));
|
|
437
386
|
```
|
|
438
387
|
|
|
439
|
-
---
|
|
440
|
-
|
|
441
388
|
## `For` — Keyed List Rendering
|
|
442
389
|
|
|
443
390
|
Reconciles a reactive array into the DOM. Each item renders once per key — no full re-renders on reorder, add, or remove. `T` is inferred from `each`.
|
|
@@ -457,34 +404,31 @@ import { For } from "elements-kit/for";
|
|
|
457
404
|
</ul>
|
|
458
405
|
```
|
|
459
406
|
|
|
460
|
-
---
|
|
461
|
-
|
|
462
407
|
## Prop types
|
|
463
408
|
|
|
464
|
-
Six type helpers derive JSX prop shapes from your components — no parallel `declare global` block to maintain. Full guide at [docs/src/content/docs/
|
|
409
|
+
Six type helpers derive JSX prop shapes from your components — no parallel `declare global` block to maintain. Full guide at [docs/src/content/docs/elements/types.mdx](docs/src/content/docs/elements/types.mdx).
|
|
465
410
|
|
|
466
411
|
| Helper | For |
|
|
467
412
|
| ------ | --- |
|
|
468
413
|
| `ElementProps<typeof Cls>` | `HTMLElement` subclass — full surface (attrs, events, slots, children) |
|
|
469
414
|
| `Props<C>` | Class instance, constructor, or function component — unified |
|
|
470
415
|
| `ComponentProps<typeof Cls>` | Class components with `constructor(props: P)` |
|
|
471
|
-
| `MaybeReactiveProps<P>` |
|
|
416
|
+
| `MaybeReactiveProps<P>` | Caller-facing — wrap every prop in `MaybeReactive` (what parents pass) |
|
|
417
|
+
| `ReactiveProps<P>` | Component-facing — every prop becomes a `Computed<T>` getter (what function components receive) |
|
|
472
418
|
| `MaybeReactive<T>` | Scalar value-or-getter (from `elements-kit/signals`) |
|
|
473
419
|
| `Require<P, K>` | Promote optional keys to required |
|
|
474
420
|
|
|
475
|
-
|
|
421
|
+
The JSX runtime auto-wraps function-component props — each key arrives as a callable getter that subscribes on read. Pair the signature with `ReactiveProps<P>` and read `props.x()`:
|
|
476
422
|
|
|
477
423
|
```tsx
|
|
478
|
-
import {
|
|
479
|
-
import type { MaybeReactiveProps } from "elements-kit/jsx-runtime";
|
|
424
|
+
import type { ReactiveProps } from "elements-kit/jsx-runtime";
|
|
480
425
|
|
|
481
|
-
function Greeting(
|
|
482
|
-
const props = resolveProps(raw);
|
|
426
|
+
function Greeting(props: ReactiveProps<{ name: string }>) {
|
|
483
427
|
return <p>Hello, {props.name}</p>;
|
|
484
428
|
}
|
|
485
429
|
```
|
|
486
430
|
|
|
487
|
-
|
|
431
|
+
`resolveProps` stays exported for non-JSX call sites or nested prop bags.
|
|
488
432
|
|
|
489
433
|
## `@reactive()` Decorator
|
|
490
434
|
|
|
@@ -503,8 +447,6 @@ class TodoApp {
|
|
|
503
447
|
}
|
|
504
448
|
```
|
|
505
449
|
|
|
506
|
-
---
|
|
507
|
-
|
|
508
450
|
## `@attributes` Decorator
|
|
509
451
|
|
|
510
452
|
Wires `observedAttributes` and `attributeChangedCallback` from a static map:
|
|
@@ -541,10 +483,15 @@ For typed events, declare a `static events` map:
|
|
|
541
483
|
class XPicker extends HTMLElement {
|
|
542
484
|
declare static events: { commit: CustomEvent<number> };
|
|
543
485
|
}
|
|
544
|
-
// ElementProps<typeof XPicker> now includes `on:commit`
|
|
486
|
+
// ElementProps<typeof XPicker> now includes `on:commit`
|
|
545
487
|
```
|
|
546
488
|
|
|
547
|
-
|
|
489
|
+
## Learn more
|
|
490
|
+
|
|
491
|
+
- [Documentation site](docs/) — guides, playgrounds, reference
|
|
492
|
+
- [Philosophy](docs/src/content/docs/getting-started/philosophy.mdx) — deeper reasoning behind the five principles
|
|
493
|
+
- [ARCHITECTURE.md](ARCHITECTURE.md) — how the library works
|
|
494
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) — build, test, PR checklist
|
|
548
495
|
|
|
549
496
|
## Roadmap
|
|
550
497
|
|
|
@@ -1,7 +1,28 @@
|
|
|
1
1
|
//#region src/attributes.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Handler signature for a single observed attribute.
|
|
4
|
+
*
|
|
5
|
+
* `this` is bound to the element instance, letting the handler assign
|
|
6
|
+
* directly to reactive properties. `value` is the raw HTML string (or `null`
|
|
7
|
+
* when the attribute is removed) — conversion to typed JS is the handler's job.
|
|
8
|
+
*/
|
|
2
9
|
interface AttrChangeHandler<T> {
|
|
3
10
|
(this: T, value: string | null, oldValue?: string | null): void;
|
|
4
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* Static-field key used by the `@attributes` decorator (and
|
|
14
|
+
* {@link dispatchAttrChange} / {@link observedAttributes}) to locate the
|
|
15
|
+
* attribute handler map on a custom-element class.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* class MyElement extends HTMLElement {
|
|
20
|
+
* static [ATTRIBUTES]: Attributes<MyElement> = {
|
|
21
|
+
* name(value) { this.name = value ?? ""; },
|
|
22
|
+
* };
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
5
26
|
declare const ATTRIBUTES: unique symbol;
|
|
6
27
|
/**
|
|
7
28
|
* Dispatches an attribute change to the matching handler in the static `attributes` map,
|
|
@@ -27,6 +48,10 @@ declare function dispatchAttrChange<T extends {
|
|
|
27
48
|
[ATTRIBUTES]: Record<string, AttrChangeHandler<T>>;
|
|
28
49
|
};
|
|
29
50
|
}>(this: T, name: string, oldValue: string | null, newValue: string | null): void;
|
|
51
|
+
/**
|
|
52
|
+
* Shape of the static `[ATTRIBUTES]` map: attribute name → handler bound to
|
|
53
|
+
* the element instance `T`.
|
|
54
|
+
*/
|
|
30
55
|
type Attributes<T> = Record<string, AttrChangeHandler<T>>;
|
|
31
56
|
/**
|
|
32
57
|
* Returns a deduplicated array of all observed attribute names for a custom element class and its ancestors.
|
|
@@ -63,6 +88,22 @@ declare function observedAttributes(cls: {
|
|
|
63
88
|
observedAttributes?: string[];
|
|
64
89
|
prototype: unknown;
|
|
65
90
|
}): string[];
|
|
91
|
+
/**
|
|
92
|
+
* Pre-decoration shape required by `@attributes` — a class constructor
|
|
93
|
+
* carrying a static `[ATTRIBUTES]` handler map.
|
|
94
|
+
*/
|
|
95
|
+
type AttributeTarget<T extends abstract new (...args: any[]) => HTMLElement> = T & {
|
|
96
|
+
[ATTRIBUTES]: Record<string, AttrChangeHandler<InstanceType<T>>>;
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Post-decoration shape returned by `@attributes`: adds `observedAttributes`
|
|
100
|
+
* to the constructor and `attributeChangedCallback` to the prototype.
|
|
101
|
+
*/
|
|
102
|
+
type AttributeDecorated<T extends abstract new (...args: any[]) => HTMLElement> = T & (new (...args: any[]) => InstanceType<T> & {
|
|
103
|
+
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
104
|
+
}) & {
|
|
105
|
+
observedAttributes: string[];
|
|
106
|
+
};
|
|
66
107
|
/**
|
|
67
108
|
* A class decorator that automatically wires up `observedAttributes` and `attributeChangedCallback`
|
|
68
109
|
* from a static `[ATTRIBUTES]` map.
|
|
@@ -71,7 +112,7 @@ declare function observedAttributes(cls: {
|
|
|
71
112
|
*
|
|
72
113
|
* @example
|
|
73
114
|
* ```ts
|
|
74
|
-
*
|
|
115
|
+
* \@attributes
|
|
75
116
|
* class MyElement extends HTMLElement {
|
|
76
117
|
* static [ATTRIBUTES] = {
|
|
77
118
|
* count(this: MyElement, value: string | null) {
|
|
@@ -81,14 +122,6 @@ declare function observedAttributes(cls: {
|
|
|
81
122
|
* }
|
|
82
123
|
* ```
|
|
83
124
|
*/
|
|
84
|
-
type AttributeTarget<T extends abstract new (...args: any[]) => HTMLElement> = T & {
|
|
85
|
-
[ATTRIBUTES]: Record<string, AttrChangeHandler<InstanceType<T>>>;
|
|
86
|
-
};
|
|
87
|
-
type AttributeDecorated<T extends abstract new (...args: any[]) => HTMLElement> = T & (new (...args: any[]) => InstanceType<T> & {
|
|
88
|
-
attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null): void;
|
|
89
|
-
}) & {
|
|
90
|
-
observedAttributes: string[];
|
|
91
|
-
};
|
|
92
125
|
declare function attributes<T extends abstract new (...args: any[]) => HTMLElement>(target: AttributeTarget<T>, context: ClassDecoratorContext<T>): AttributeDecorated<T>;
|
|
93
126
|
//#endregion
|
|
94
127
|
export { Attributes as a, observedAttributes as c, AttributeTarget as i, AttrChangeHandler as n, attributes as o, AttributeDecorated as r, dispatchAttrChange as s, ATTRIBUTES as t };
|
package/dist/attributes.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as Attributes, c as observedAttributes, i as AttributeTarget, n as AttrChangeHandler, o as attributes, r as AttributeDecorated, s as dispatchAttrChange, t as ATTRIBUTES } from "./attributes-
|
|
1
|
+
import { a as Attributes, c as observedAttributes, i as AttributeTarget, n as AttrChangeHandler, o as attributes, r as AttributeDecorated, s as dispatchAttrChange, t as ATTRIBUTES } from "./attributes-DILeh3-s.mjs";
|
|
2
2
|
export { ATTRIBUTES, AttrChangeHandler, AttributeDecorated, AttributeTarget, Attributes, attributes, dispatchAttrChange, observedAttributes };
|
package/dist/attributes.mjs
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
//#region src/attributes.ts
|
|
2
|
+
/**
|
|
3
|
+
* Static-field key used by the `@attributes` decorator (and
|
|
4
|
+
* {@link dispatchAttrChange} / {@link observedAttributes}) to locate the
|
|
5
|
+
* attribute handler map on a custom-element class.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* class MyElement extends HTMLElement {
|
|
10
|
+
* static [ATTRIBUTES]: Attributes<MyElement> = {
|
|
11
|
+
* name(value) { this.name = value ?? ""; },
|
|
12
|
+
* };
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
2
16
|
const ATTRIBUTES = Symbol("attributes");
|
|
3
17
|
/**
|
|
4
18
|
* Dispatches an attribute change to the matching handler in the static `attributes` map,
|
|
@@ -68,6 +82,24 @@ function observedAttributes(cls) {
|
|
|
68
82
|
}
|
|
69
83
|
return Array.from(s);
|
|
70
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* A class decorator that automatically wires up `observedAttributes` and `attributeChangedCallback`
|
|
87
|
+
* from a static `[ATTRIBUTES]` map.
|
|
88
|
+
*
|
|
89
|
+
* The `this` type inside attribute handlers is automatically inferred from the decorated class.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* \@attributes
|
|
94
|
+
* class MyElement extends HTMLElement {
|
|
95
|
+
* static [ATTRIBUTES] = {
|
|
96
|
+
* count(this: MyElement, value: string | null) {
|
|
97
|
+
* this.count = Number(value);
|
|
98
|
+
* },
|
|
99
|
+
* };
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
71
103
|
function attributes(target, context) {
|
|
72
104
|
context.addInitializer(function() {
|
|
73
105
|
target.observedAttributes = observedAttributes(target);
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/custom-elements.d.ts
|
|
2
2
|
/**
|
|
3
3
|
* Registry of custom-element tags to their constructors.
|
|
4
4
|
* Users augment this interface to add typed JSX support for their elements:
|
|
5
5
|
*
|
|
6
6
|
* @example
|
|
7
7
|
* ```ts
|
|
8
|
-
* declare module "elements-kit" {
|
|
8
|
+
* declare module "elements-kit/custom-elements" {
|
|
9
9
|
* interface CustomElementRegistry {
|
|
10
10
|
* "x-range": typeof XRange;
|
|
11
11
|
* }
|
|
@@ -17,6 +17,24 @@ type AnyCtor = CustomElementConstructor;
|
|
|
17
17
|
/**
|
|
18
18
|
* Register a custom element with the browser and return its class.
|
|
19
19
|
* Pair with a module augmentation of `CustomElementRegistry` to get typed JSX.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* import { defineElement } from "elements-kit/custom-elements";
|
|
24
|
+
*
|
|
25
|
+
* class XCounter extends HTMLElement {}
|
|
26
|
+
*
|
|
27
|
+
* defineElement("x-counter", XCounter);
|
|
28
|
+
*
|
|
29
|
+
* declare module "elements-kit/custom-elements" {
|
|
30
|
+
* interface CustomElementRegistry {
|
|
31
|
+
* "x-counter": typeof XCounter;
|
|
32
|
+
* }
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* // JSX now gets typed props + typed ref
|
|
36
|
+
* // <x-counter />
|
|
37
|
+
* ```
|
|
20
38
|
*/
|
|
21
39
|
declare function defineElement<Tag extends `${string}-${string}`, C extends AnyCtor>(tag: Tag, cls: C, options?: ElementDefinitionOptions): C;
|
|
22
40
|
//#endregion
|
|
@@ -1,74 +1,2 @@
|
|
|
1
|
-
import { n as defineElement, t as CustomElementRegistry } from "./
|
|
2
|
-
|
|
3
|
-
//#region src/custom-elements.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Runs `setup` inside a fresh `effectScope` and returns both its result and
|
|
6
|
-
* the scope's dispose handle.
|
|
7
|
-
*
|
|
8
|
-
* Use this wherever you need to run reactive code with an explicit lifetime
|
|
9
|
-
* outside the JSX element flow — most commonly inside a custom element's
|
|
10
|
-
* `connectedCallback`. `onCleanup`, nested `effect`s and any other
|
|
11
|
-
* scope-bound registrations made in `setup` are owned by the returned
|
|
12
|
-
* `dispose`.
|
|
13
|
-
*
|
|
14
|
-
* `untracked` detaches the new scope from any enclosing effect so it isn't
|
|
15
|
-
* torn down when that effect re-runs — its lifetime is solely the caller's
|
|
16
|
-
* responsibility.
|
|
17
|
-
*
|
|
18
|
-
* @example
|
|
19
|
-
* ```ts
|
|
20
|
-
* class Clock extends HTMLElement {
|
|
21
|
-
* #dispose?: () => void;
|
|
22
|
-
*
|
|
23
|
-
* connectedCallback() {
|
|
24
|
-
* const { dispose } = renderScope(() => {
|
|
25
|
-
* const id = setInterval(() => (this.textContent = String(Date.now())), 1000);
|
|
26
|
-
* onCleanup(() => clearInterval(id));
|
|
27
|
-
* });
|
|
28
|
-
* this.#dispose = dispose;
|
|
29
|
-
* }
|
|
30
|
-
*
|
|
31
|
-
* disconnectedCallback() {
|
|
32
|
-
* this.#dispose?.();
|
|
33
|
-
* this.#dispose = undefined;
|
|
34
|
-
* }
|
|
35
|
-
* }
|
|
36
|
-
* ```
|
|
37
|
-
*/
|
|
38
|
-
declare function renderScope<T>(setup: () => T): {
|
|
39
|
-
result: T;
|
|
40
|
-
dispose: () => void;
|
|
41
|
-
};
|
|
42
|
-
/**
|
|
43
|
-
* Runs `setup` inside an `effectScope` tied to `el`'s connected lifetime.
|
|
44
|
-
*
|
|
45
|
-
* Call from `connectedCallback`. Effects, `onCleanup` registrations, and
|
|
46
|
-
* reactive reads inside `setup` belong to this scope. Pair with
|
|
47
|
-
* {@link disconnectedScope} from `disconnectedCallback` to dispose.
|
|
48
|
-
*
|
|
49
|
-
* Safe to call more than once (e.g. if the element is reconnected after
|
|
50
|
-
* disconnection): the previous scope is disposed first.
|
|
51
|
-
*
|
|
52
|
-
* @example
|
|
53
|
-
* ```ts
|
|
54
|
-
* class Clock extends HTMLElement {
|
|
55
|
-
* connectedCallback() {
|
|
56
|
-
* connectedScope(this, () => {
|
|
57
|
-
* const id = setInterval(() => this.textContent = String(Date.now()), 1000);
|
|
58
|
-
* onCleanup(() => clearInterval(id));
|
|
59
|
-
* });
|
|
60
|
-
* }
|
|
61
|
-
* disconnectedCallback() {
|
|
62
|
-
* disconnectedScope(this);
|
|
63
|
-
* }
|
|
64
|
-
* }
|
|
65
|
-
* ```
|
|
66
|
-
*/
|
|
67
|
-
declare function connectedScope(el: HTMLElement, setup: () => void): void;
|
|
68
|
-
/**
|
|
69
|
-
* Disposes the scope previously created by {@link connectedScope} for `el`.
|
|
70
|
-
* No-op if there is no active scope.
|
|
71
|
-
*/
|
|
72
|
-
declare function disconnectedScope(el: HTMLElement): void;
|
|
73
|
-
//#endregion
|
|
74
|
-
export { type CustomElementRegistry, connectedScope, defineElement, disconnectedScope, renderScope };
|
|
1
|
+
import { n as defineElement, t as CustomElementRegistry } from "./custom-elements-D5_NMNyD.mjs";
|
|
2
|
+
export { CustomElementRegistry, defineElement };
|