elements-kit 0.0.15 → 0.0.16

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.
Files changed (112) hide show
  1. package/README.md +138 -31
  2. package/dist/attributes-Dtn68R1u.d.mts +94 -0
  3. package/dist/attributes.d.mts +1 -93
  4. package/dist/custom-elements.d.mts +74 -0
  5. package/dist/custom-elements.mjs +100 -0
  6. package/dist/define-CjbTZ3VG.d.mts +23 -0
  7. package/dist/{element-ChF24-2z.mjs → element-CGVy_8TW.mjs} +11 -12
  8. package/dist/for.d.mts +45 -0
  9. package/dist/for.mjs +136 -0
  10. package/dist/{index-DUshSQ_6.d.mts → index-DydGTqZU.d.mts} +60 -10
  11. package/dist/index.d.mts +1 -36
  12. package/dist/index.mjs +1 -144
  13. package/dist/infer-BfzRJoCn.d.mts +203 -0
  14. package/dist/integrations/react.d.mts +1 -1
  15. package/dist/integrations/react.mjs +1 -1
  16. package/dist/jsx-runtime/index.d.mts +11 -20
  17. package/dist/jsx-runtime/index.mjs +2 -2
  18. package/dist/signals/index.d.mts +2 -2
  19. package/dist/signals/index.mjs +2 -2
  20. package/dist/{signals-BHmWX6ox.mjs → signals-J8dK_rA4.mjs} +57 -17
  21. package/dist/slot-C7GQZe-r.d.mts +104 -0
  22. package/dist/{slot-Cydy7-0L.mjs → slot-Kb61AcgW.mjs} +21 -2
  23. package/dist/slot.d.mts +2 -84
  24. package/dist/slot.mjs +2 -2
  25. package/dist/{test.BmQO5GaM-ANkhHvbr.mjs → test.BmQO5GaM-DfGStnii.mjs} +1 -1
  26. package/dist/utilities/_observe.d.mts +11 -0
  27. package/dist/utilities/_observe.mjs +15 -0
  28. package/dist/utilities/active-element.d.mts +1 -1
  29. package/dist/utilities/active-element.mjs +6 -2
  30. package/dist/utilities/active-element.test.mjs +1 -1
  31. package/dist/utilities/async.d.mts +1 -1
  32. package/dist/utilities/async.mjs +1 -1
  33. package/dist/utilities/async.test.mjs +2 -2
  34. package/dist/utilities/debounced.d.mts +1 -1
  35. package/dist/utilities/debounced.mjs +1 -1
  36. package/dist/utilities/debounced.test.mjs +2 -2
  37. package/dist/utilities/element-rect.d.mts +1 -1
  38. package/dist/utilities/element-rect.mjs +1 -1
  39. package/dist/utilities/element-rect.test.mjs +2 -2
  40. package/dist/utilities/element-scroll.d.mts +1 -1
  41. package/dist/utilities/element-scroll.mjs +7 -34
  42. package/dist/utilities/element-scroll.test.mjs +2 -2
  43. package/dist/utilities/environment.d.mts +5 -0
  44. package/dist/utilities/environment.mjs +5 -0
  45. package/dist/utilities/event-driven.d.mts +1 -1
  46. package/dist/utilities/event-driven.mjs +2 -2
  47. package/dist/utilities/event-listener.d.mts +1 -1
  48. package/dist/utilities/event-listener.mjs +1 -1
  49. package/dist/utilities/event-listener.test.mjs +2 -2
  50. package/dist/utilities/focus-within.d.mts +1 -1
  51. package/dist/utilities/focus-within.mjs +1 -1
  52. package/dist/utilities/focus-within.test.mjs +2 -2
  53. package/dist/utilities/hover.d.mts +1 -1
  54. package/dist/utilities/hover.mjs +1 -1
  55. package/dist/utilities/hover.test.mjs +2 -2
  56. package/dist/utilities/intersection-observer.mjs +4 -6
  57. package/dist/utilities/intersection-observer.test.mjs +2 -2
  58. package/dist/utilities/interval.d.mts +1 -1
  59. package/dist/utilities/interval.mjs +1 -1
  60. package/dist/utilities/interval.test.mjs +2 -2
  61. package/dist/utilities/location.d.mts +2 -14
  62. package/dist/utilities/location.mjs +12 -3
  63. package/dist/utilities/location.test.mjs +5 -5
  64. package/dist/utilities/long-press.mjs +1 -1
  65. package/dist/utilities/long-press.test.mjs +2 -2
  66. package/dist/utilities/media-devices.d.mts +1 -1
  67. package/dist/utilities/media-devices.mjs +1 -1
  68. package/dist/utilities/media-devices.test.mjs +2 -2
  69. package/dist/utilities/media-player.d.mts +1 -1
  70. package/dist/utilities/media-player.test.mjs +2 -2
  71. package/dist/utilities/media-query.d.mts +2 -3
  72. package/dist/utilities/media-query.mjs +3 -3
  73. package/dist/utilities/mutation-observer.mjs +4 -7
  74. package/dist/utilities/mutation-observer.test.mjs +2 -2
  75. package/dist/utilities/network.d.mts +2 -2
  76. package/dist/utilities/network.mjs +5 -3
  77. package/dist/utilities/network.test.mjs +1 -1
  78. package/dist/utilities/on-click-outside.test.mjs +2 -2
  79. package/dist/utilities/orientation.d.mts +1 -1
  80. package/dist/utilities/orientation.mjs +9 -1
  81. package/dist/utilities/previous.d.mts +1 -1
  82. package/dist/utilities/previous.mjs +1 -1
  83. package/dist/utilities/previous.test.mjs +2 -2
  84. package/dist/utilities/promise.d.mts +1 -1
  85. package/dist/utilities/promise.mjs +1 -1
  86. package/dist/utilities/promise.test.mjs +2 -2
  87. package/dist/utilities/resize-observer.mjs +4 -6
  88. package/dist/utilities/retry.mjs +1 -1
  89. package/dist/utilities/retry.test.mjs +2 -2
  90. package/dist/utilities/routing.d.mts +1 -1
  91. package/dist/utilities/routing.mjs +1 -1
  92. package/dist/utilities/routing.test.mjs +1 -1
  93. package/dist/utilities/search-params.d.mts +1 -1
  94. package/dist/utilities/search-params.test.mjs +2 -2
  95. package/dist/utilities/ssr.test.d.mts +1 -0
  96. package/dist/utilities/ssr.test.mjs +64 -0
  97. package/dist/utilities/storage.d.mts +1 -1
  98. package/dist/utilities/storage.test.mjs +2 -2
  99. package/dist/utilities/throttled.d.mts +1 -1
  100. package/dist/utilities/throttled.mjs +1 -1
  101. package/dist/utilities/throttled.test.mjs +2 -2
  102. package/dist/utilities/timeout.d.mts +1 -1
  103. package/dist/utilities/timeout.mjs +1 -1
  104. package/dist/utilities/timeout.test.mjs +2 -2
  105. package/dist/utilities/window-focus.d.mts +2 -2
  106. package/dist/utilities/window-focus.mjs +5 -3
  107. package/dist/utilities/window-size.d.mts +1 -1
  108. package/dist/utilities/window-size.mjs +10 -5
  109. package/dist/utilities/window-size.test.mjs +1 -1
  110. package/package.json +11 -1
  111. package/dist/lib-JA05lzCN.d.mts +0 -4
  112. /package/dist/{polyfill-DR5XVnh_.d.mts → polyfill-BVNd6ogU.d.mts} +0 -0
package/README.md CHANGED
@@ -1,3 +1,7 @@
1
+ <p align="center">
2
+ <img src="docs/public/og.svg" alt="ElementsKit" width="600" />
3
+ </p>
4
+
1
5
  # ElementsKit
2
6
 
3
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.
@@ -33,6 +37,33 @@ customElements.define("x-counter", CounterElement);
33
37
 
34
38
  ---
35
39
 
40
+ ## Packages
41
+
42
+ Every feature is a separate subpath export — import only what you use.
43
+
44
+ | Entry | Purpose |
45
+ |-------|---------|
46
+ | `elements-kit` | `For` and core re-exports |
47
+ | `elements-kit/signals` | `signal`, `computed`, `effect`, `effectScope`, `batch`, `untracked`, `trigger`, `onCleanup`, `@reactive` |
48
+ | `elements-kit/attributes` | `@attributes` decorator + `ATTRIBUTES` symbol for custom elements |
49
+ | `elements-kit/slot` | `Slot` class — comment-marker DOM regions |
50
+ | `elements-kit/jsx-runtime` | JSX factory (configure via `jsxImportSource`) |
51
+ | `elements-kit/integrations/react` | `useSignal`, `useScope` React bridge hooks |
52
+ | `elements-kit/utilities/*` | Reactive browser-API utilities — see [src/utilities/README.md](src/utilities/README.md) |
53
+
54
+ ## Repository
55
+
56
+ - [src/](src/) — library source ([signals](src/signals/), [jsx-runtime](src/jsx-runtime/), [utilities](src/utilities/), [integrations](src/integrations/))
57
+ - [docs/](docs/) — Astro + Starlight documentation site
58
+ - [example/](example/) — Vite sandbox
59
+ - [ARCHITECTURE.md](ARCHITECTURE.md) — how the library works (reactive model, JSX, custom elements, cleanup)
60
+ - [CONTRIBUTING.md](CONTRIBUTING.md) — quick start, quality bars, versioning, PR checklist
61
+ - [DOCS.md](DOCS.md) — doc-authoring rules
62
+ - [AGENTS.md](AGENTS.md) — agent navigation map
63
+ - [src/utilities/README.md](src/utilities/README.md) — utilities catalog and dependency graph
64
+
65
+ ---
66
+
36
67
  ## Why ElementsKit
37
68
 
38
69
  Modern UI frameworks solve reactivity and rendering together — you adopt the whole system or none of it. ElementsKit separates the two:
@@ -43,6 +74,16 @@ Modern UI frameworks solve reactivity and rendering together — you adopt the w
43
74
 
44
75
  Use one piece, or all three. Integrate with React for complex UIs. Build web components that work anywhere HTML does.
45
76
 
77
+ ### Primitives first, composable abstractions
78
+
79
+ 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.
80
+
81
+ - **No prop matrices.** No single-system components with forty flags where legal combinations aren't obvious until you read the source.
82
+ - **No hidden coupling.** Blocks meet at their types; invariants don't leak across layers.
83
+ - **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.
84
+ - **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.
85
+ - **Swap, don't rewrite.** Replace one block (`throttled` → `debounced`) without touching the ones around it.
86
+
46
87
  ---
47
88
 
48
89
  ## Installation
@@ -247,53 +288,120 @@ The same `cart` store drives custom elements, React trees, and plain scripts —
247
288
 
248
289
  ## Utilities
249
290
 
250
- Pre-built signal factories for common browser APIs:
291
+ 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).
292
+
293
+ `createMediaQuery` wraps `window.matchMedia` into a reactive signal — reads inside effects or computeds re-run automatically when the media query result changes.
294
+
295
+ ```tsx
296
+ import { effect } from "elements-kit/signals";
297
+ import { createMediaQuery } from "elements-kit/utilities/media-query";
298
+
299
+ const isDark = createMediaQuery("(prefers-color-scheme: dark)");
300
+ const isMobile = createMediaQuery("(max-width: 640px)");
301
+
302
+ effect(() => document.documentElement.classList.toggle("dark", isDark()));
303
+ ```
304
+
305
+ Singletons like `online`, `windowFocused`, `activeElement`, and `currentLocation` are pre-instantiated — import and read them directly inside any reactive context.
306
+
307
+ ```ts
308
+ import { effect } from "elements-kit/signals";
309
+ import { online } from "elements-kit/utilities/network";
310
+ import { windowFocused } from "elements-kit/utilities/window-focus";
311
+
312
+ effect(() => console.log("online:", online(), "focused:", windowFocused()));
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Async & Promise
318
+
319
+ 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).
320
+
321
+ ### `promise`
322
+
323
+ Wraps an async function (or raw `Promise`) into a `ComputedPromise<T>` — awaitable **and** callable as a reactive value. Exposes `.state`, `.value`, `.reason`, `.result` as reactive reads.
324
+
325
+ ```ts
326
+ import { promise } from "elements-kit/utilities/promise";
327
+ import { effect } from "elements-kit/signals";
328
+
329
+ const user = promise(() => fetch("/api/user").then((r) => r.json()));
330
+
331
+ effect(() => {
332
+ if (user.state === "pending") console.log("loading…");
333
+ if (user.state === "fulfilled") console.log("user:", user.value);
334
+ if (user.state === "rejected") console.log("error:", user.reason);
335
+ });
336
+
337
+ await user; // awaitable
338
+ ```
339
+
340
+ `ReactivePromise` is the underlying class — use it when you want the reactive state getters without the `Computed` callable interface.
341
+
342
+ ### `async`
343
+
344
+ A controller around `promise`. The async function may be a plain function or a `MaybeReactive<Fn>` (so the body itself can re-read signals and rerun on change).
345
+
346
+ ```ts
347
+ import { async } from "elements-kit/utilities/async";
348
+
349
+ const op = async(() => fetch("/api/items").then((r) => r.json()));
350
+
351
+ op.start(); // run with reactive tracking — reruns when tracked signals change
352
+ await op; // awaitable (delegates to .then/.catch/.finally via .raw)
353
+ op.stop(); // halt reruns + fire registered cleanup
354
+ ```
355
+
356
+ Reactive state getters: `.state`, `.value`, `.reason`, `.result`, `.pending`, `.raw` (the underlying `ComputedPromise`).
357
+
358
+ One-shot mutation (no tracking):
359
+
360
+ ```ts
361
+ const del = async((id: number) =>
362
+ fetch(`/api/items/${id}`, { method: "DELETE" }).then((r) => r.json()),
363
+ );
364
+
365
+ await del.run(42);
366
+ ```
367
+
368
+ `Async` implements `Symbol.dispose`, so `using` auto-stops on scope exit:
369
+
370
+ ```ts
371
+ {
372
+ using poll = async(() => fetch("/api/poll").then((r) => r.json())).start();
373
+ await poll;
374
+ } // poll.stop() here
375
+ ```
376
+
377
+ ### Composing with retry, online, storage
378
+
379
+ `async`'s reactive body composes with other utilities. Below: fetch a todo by `id()`, retry on failure with exponential backoff, pause while offline (returning the stale cached value), and refetch when the tab regains focus.
251
380
 
252
381
  ```ts
253
382
  import { signal, effect, untracked, onCleanup } from "elements-kit/signals";
254
- import { createMediaQuery } from "elements-kit/utilities/media-query";
255
383
  import { async } from "elements-kit/utilities/async";
256
384
  import { retry } from "elements-kit/utilities/retry";
257
385
  import { online } from "elements-kit/utilities/network";
258
386
  import { windowFocused } from "elements-kit/utilities/window-focus";
259
- import { storage } from "elements-kit/utilities/storage";
387
+ import { createLocalStorage } from "elements-kit/utilities/storage";
260
388
 
261
389
  const id = signal(1);
262
- const cache = storage("todos"); // persists across reloads, used as initial value
390
+ const cache = createLocalStorage<unknown>("todo-cache", null);
263
391
 
264
- // Query — retries on failure, refetches when back online or tab regains focus
265
392
  const fetchTodo = async(() => {
266
- if (!online()) return untracked(cache); // pause while offline, return stale value
267
- windowFocused(); // refetch on tab focus
393
+ if (!online()) return untracked(cache); // pause while offline
394
+ windowFocused(); // refetch on tab focus
268
395
  return retry(() => {
269
396
  const controller = new AbortController();
270
- onCleanup(() => controller.abort()); // abort before each retry
397
+ onCleanup(() => controller.abort()); // abort before each retry
271
398
  return fetch(`/api/todos/${id()}`, { signal: controller.signal })
272
399
  .then((r) => r.json())
273
- .then(cache); // update cache on success
274
- }, 3, (n) => n * 500)(); // 0 ms, 500 ms, 1000 ms backoff
400
+ .then((value) => (cache(value), value));
401
+ }, 3, (n) => n * 500)(); // 0 ms, 500 ms, 1000 ms backoff
275
402
  }).start();
276
403
 
277
404
  effect(() => console.log(fetchTodo.state, fetchTodo.value));
278
-
279
- // Mutation — run once, no reactive tracking
280
- const deleteTodo = async((todoId: number) =>
281
- fetch(`/api/todos/${todoId}`, { method: "DELETE" }).then((r) => r.json()),
282
- );
283
-
284
- const result = await deleteTodo.run(42);
285
- ```
286
-
287
- `createMediaQuery` wraps `window.matchMedia` into a reactive signal — reads inside effects or computeds re-run automatically when the media query result changes.
288
-
289
- ```tsx
290
- import { effect } from "elements-kit/signals";
291
- import { createMediaQuery } from "elements-kit/utilities/media-query";
292
-
293
- const isDark = createMediaQuery("(prefers-color-scheme: dark)");
294
- const isMobile = createMediaQuery("(max-width: 640px)");
295
-
296
- effect(() => document.documentElement.classList.toggle("dark", isDark()));
297
405
  ```
298
406
 
299
407
  ---
@@ -303,7 +411,7 @@ effect(() => document.documentElement.classList.toggle("dark", isDark()));
303
411
  Reconciles a reactive array into the DOM. Each item renders once per key — no full re-renders on reorder, add, or remove.
304
412
 
305
413
  ```tsx
306
- import { For } from "elements-kit";
414
+ import { For } from "elements-kit/for";
307
415
 
308
416
  <ul>
309
417
  <For each={todos} by={(todo) => todo.id}>
@@ -362,7 +470,6 @@ class MyElement extends HTMLElement {
362
470
  ## Roadmap
363
471
 
364
472
  - [ ] Context — share state across a subtree without prop drilling
365
- - [ ] Async signal — `signal.from(promise)`, `signal.from(observable)`
366
473
  - [ ] UI library — pre-built reactive components built on ElementsKit primitives
367
474
  - [ ] More framework integrations (Vue, Solid, Angular, …)
368
475
  - [ ] Tutorial — building a full app from scratch
@@ -0,0 +1,94 @@
1
+ //#region src/attributes.d.ts
2
+ interface AttrChangeHandler<T> {
3
+ (this: T, value: string | null, oldValue?: string | null): void;
4
+ }
5
+ declare const ATTRIBUTES: unique symbol;
6
+ /**
7
+ * Dispatches an attribute change to the matching handler in the static `attributes` map,
8
+ * walking the prototype chain for inherited handlers.
9
+ * @example
10
+ * ```ts
11
+ * class MyElement extends HTMLElement {
12
+ * static [ATTRIBUTES]: Attributes<MyElement> = {
13
+ * count(value) {
14
+ * this.#count = Number(value);
15
+ * },
16
+ * };
17
+ * static observedAttributes: string[] = observedAttributes(MyElement);
18
+ *
19
+ * attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
20
+ * dispatchAttrChange.call(this, name, oldValue, newValue);
21
+ * }
22
+ * }
23
+ * ```
24
+ */
25
+ declare function dispatchAttrChange<T extends {
26
+ constructor: {
27
+ [ATTRIBUTES]: Record<string, AttrChangeHandler<T>>;
28
+ };
29
+ }>(this: T, name: string, oldValue: string | null, newValue: string | null): void;
30
+ type Attributes<T> = Record<string, AttrChangeHandler<T>>;
31
+ /**
32
+ * Returns a deduplicated array of all observed attribute names for a custom element class and its ancestors.
33
+ *
34
+ * Call after defining static `[ATTRIBUTES]`, and assign to static `observedAttributes`.
35
+ *
36
+ * Example:
37
+ * ```ts
38
+ * class MyElement extends HTMLElement {
39
+ * static [ATTRIBUTES]: Attributes<MyElement> = {
40
+ * count(value) {
41
+ * this.#count = Number(value);
42
+ * },
43
+ * };
44
+ * static observedAttributes: string[] = observedAttributes(MyElement);
45
+ * }
46
+ *
47
+ * class ChildElement extends MyElement {
48
+ * static [ATTRIBUTES]: Attributes<ChildElement> = {
49
+ * bar(value) {
50
+ * // ...
51
+ * },
52
+ * };
53
+ * static observedAttributes: string[] = observedAttributes(ChildElement);
54
+ * }
55
+ * // ChildElement.observedAttributes will include both 'count' and 'bar', deduplicated.
56
+ * ```
57
+ *
58
+ * @param cls The custom element class constructor
59
+ * @returns Array of unique attribute names to observe
60
+ */
61
+ declare function observedAttributes(cls: {
62
+ [ATTRIBUTES]?: Record<string, unknown>;
63
+ observedAttributes?: string[];
64
+ prototype: unknown;
65
+ }): string[];
66
+ /**
67
+ * A class decorator that automatically wires up `observedAttributes` and `attributeChangedCallback`
68
+ * from a static `[ATTRIBUTES]` map.
69
+ *
70
+ * The `this` type inside attribute handlers is automatically inferred from the decorated class.
71
+ *
72
+ * @example
73
+ * ```ts
74
+ * @attributes
75
+ * class MyElement extends HTMLElement {
76
+ * static [ATTRIBUTES] = {
77
+ * count(this: MyElement, value: string | null) {
78
+ * this.count = Number(value);
79
+ * },
80
+ * };
81
+ * }
82
+ * ```
83
+ */
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
+ declare function attributes<T extends abstract new (...args: any[]) => HTMLElement>(target: AttributeTarget<T>, context: ClassDecoratorContext<T>): AttributeDecorated<T>;
93
+ //#endregion
94
+ 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 };
@@ -1,94 +1,2 @@
1
- //#region src/attributes.d.ts
2
- interface AttrChangeHandler<T> {
3
- (this: T, value: string | null, oldValue?: string | null): void;
4
- }
5
- declare const ATTRIBUTES: unique symbol;
6
- /**
7
- * Dispatches an attribute change to the matching handler in the static `attributes` map,
8
- * walking the prototype chain for inherited handlers.
9
- * @example
10
- * ```ts
11
- * class MyElement extends HTMLElement {
12
- * static [ATTRIBUTES]: Attributes<MyElement> = {
13
- * count(value) {
14
- * this.#count = Number(value);
15
- * },
16
- * };
17
- * static observedAttributes: string[] = observedAttributes(MyElement);
18
- *
19
- * attributeChangedCallback(name: string, oldValue: string | null, newValue: string | null) {
20
- * dispatchAttrChange.call(this, name, oldValue, newValue);
21
- * }
22
- * }
23
- * ```
24
- */
25
- declare function dispatchAttrChange<T extends {
26
- constructor: {
27
- [ATTRIBUTES]: Record<string, AttrChangeHandler<T>>;
28
- };
29
- }>(this: T, name: string, oldValue: string | null, newValue: string | null): void;
30
- type Attributes<T> = Record<string, AttrChangeHandler<T>>;
31
- /**
32
- * Returns a deduplicated array of all observed attribute names for a custom element class and its ancestors.
33
- *
34
- * Call after defining static `[ATTRIBUTES]`, and assign to static `observedAttributes`.
35
- *
36
- * Example:
37
- * ```ts
38
- * class MyElement extends HTMLElement {
39
- * static [ATTRIBUTES]: Attributes<MyElement> = {
40
- * count(value) {
41
- * this.#count = Number(value);
42
- * },
43
- * };
44
- * static observedAttributes: string[] = observedAttributes(MyElement);
45
- * }
46
- *
47
- * class ChildElement extends MyElement {
48
- * static [ATTRIBUTES]: Attributes<ChildElement> = {
49
- * bar(value) {
50
- * // ...
51
- * },
52
- * };
53
- * static observedAttributes: string[] = observedAttributes(ChildElement);
54
- * }
55
- * // ChildElement.observedAttributes will include both 'count' and 'bar', deduplicated.
56
- * ```
57
- *
58
- * @param cls The custom element class constructor
59
- * @returns Array of unique attribute names to observe
60
- */
61
- declare function observedAttributes(cls: {
62
- [ATTRIBUTES]?: Record<string, unknown>;
63
- observedAttributes?: string[];
64
- prototype: unknown;
65
- }): string[];
66
- /**
67
- * A class decorator that automatically wires up `observedAttributes` and `attributeChangedCallback`
68
- * from a static `[ATTRIBUTES]` map.
69
- *
70
- * The `this` type inside attribute handlers is automatically inferred from the decorated class.
71
- *
72
- * @example
73
- * ```ts
74
- * @attributes
75
- * class MyElement extends HTMLElement {
76
- * static [ATTRIBUTES] = {
77
- * count(this: MyElement, value: string | null) {
78
- * this.count = Number(value);
79
- * },
80
- * };
81
- * }
82
- * ```
83
- */
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
- declare function attributes<T extends abstract new (...args: any[]) => HTMLElement>(target: AttributeTarget<T>, context: ClassDecoratorContext<T>): AttributeDecorated<T>;
93
- //#endregion
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-Dtn68R1u.mjs";
94
2
  export { ATTRIBUTES, AttrChangeHandler, AttributeDecorated, AttributeTarget, Attributes, attributes, dispatchAttrChange, observedAttributes };
@@ -0,0 +1,74 @@
1
+ import { n as defineElement, t as CustomElementRegistry } from "./define-CjbTZ3VG.mjs";
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 };
@@ -0,0 +1,100 @@
1
+ import { b as untracked, f as effectScope } from "./signals-J8dK_rA4.mjs";
2
+ //#region src/define.ts
3
+ /**
4
+ * Register a custom element with the browser and return its class.
5
+ * Pair with a module augmentation of `CustomElementRegistry` to get typed JSX.
6
+ */
7
+ function defineElement(tag, cls, options) {
8
+ customElements.define(tag, cls, options);
9
+ return cls;
10
+ }
11
+ //#endregion
12
+ //#region src/custom-elements.ts
13
+ /**
14
+ * Runs `setup` inside a fresh `effectScope` and returns both its result and
15
+ * the scope's dispose handle.
16
+ *
17
+ * Use this wherever you need to run reactive code with an explicit lifetime
18
+ * outside the JSX element flow — most commonly inside a custom element's
19
+ * `connectedCallback`. `onCleanup`, nested `effect`s and any other
20
+ * scope-bound registrations made in `setup` are owned by the returned
21
+ * `dispose`.
22
+ *
23
+ * `untracked` detaches the new scope from any enclosing effect so it isn't
24
+ * torn down when that effect re-runs — its lifetime is solely the caller's
25
+ * responsibility.
26
+ *
27
+ * @example
28
+ * ```ts
29
+ * class Clock extends HTMLElement {
30
+ * #dispose?: () => void;
31
+ *
32
+ * connectedCallback() {
33
+ * const { dispose } = renderScope(() => {
34
+ * const id = setInterval(() => (this.textContent = String(Date.now())), 1000);
35
+ * onCleanup(() => clearInterval(id));
36
+ * });
37
+ * this.#dispose = dispose;
38
+ * }
39
+ *
40
+ * disconnectedCallback() {
41
+ * this.#dispose?.();
42
+ * this.#dispose = undefined;
43
+ * }
44
+ * }
45
+ * ```
46
+ */
47
+ function renderScope(setup) {
48
+ let result;
49
+ let dispose;
50
+ untracked(() => {
51
+ dispose = effectScope(() => {
52
+ result = setup();
53
+ });
54
+ });
55
+ return {
56
+ result,
57
+ dispose
58
+ };
59
+ }
60
+ const scopes = /* @__PURE__ */ new WeakMap();
61
+ /**
62
+ * Runs `setup` inside an `effectScope` tied to `el`'s connected lifetime.
63
+ *
64
+ * Call from `connectedCallback`. Effects, `onCleanup` registrations, and
65
+ * reactive reads inside `setup` belong to this scope. Pair with
66
+ * {@link disconnectedScope} from `disconnectedCallback` to dispose.
67
+ *
68
+ * Safe to call more than once (e.g. if the element is reconnected after
69
+ * disconnection): the previous scope is disposed first.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * class Clock extends HTMLElement {
74
+ * connectedCallback() {
75
+ * connectedScope(this, () => {
76
+ * const id = setInterval(() => this.textContent = String(Date.now()), 1000);
77
+ * onCleanup(() => clearInterval(id));
78
+ * });
79
+ * }
80
+ * disconnectedCallback() {
81
+ * disconnectedScope(this);
82
+ * }
83
+ * }
84
+ * ```
85
+ */
86
+ function connectedScope(el, setup) {
87
+ scopes.get(el)?.();
88
+ const { dispose } = renderScope(setup);
89
+ scopes.set(el, dispose);
90
+ }
91
+ /**
92
+ * Disposes the scope previously created by {@link connectedScope} for `el`.
93
+ * No-op if there is no active scope.
94
+ */
95
+ function disconnectedScope(el) {
96
+ scopes.get(el)?.();
97
+ scopes.delete(el);
98
+ }
99
+ //#endregion
100
+ export { connectedScope, defineElement, disconnectedScope, renderScope };
@@ -0,0 +1,23 @@
1
+ //#region src/define.d.ts
2
+ /**
3
+ * Registry of custom-element tags to their constructors.
4
+ * Users augment this interface to add typed JSX support for their elements:
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * declare module "elements-kit" {
9
+ * interface CustomElementRegistry {
10
+ * "x-range": typeof XRange;
11
+ * }
12
+ * }
13
+ * ```
14
+ */
15
+ interface CustomElementRegistry {}
16
+ type AnyCtor = CustomElementConstructor;
17
+ /**
18
+ * Register a custom element with the browser and return its class.
19
+ * Pair with a module augmentation of `CustomElementRegistry` to get typed JSX.
20
+ */
21
+ declare function defineElement<Tag extends `${string}-${string}`, C extends AnyCtor>(tag: Tag, cls: C, options?: ElementDefinitionOptions): C;
22
+ //#endregion
23
+ export { defineElement as n, CustomElementRegistry as t };
@@ -1,6 +1,7 @@
1
- import { d as effectScope, g as onCleanup, t as isReactive, u as effect, y as untracked } from "./signals-BHmWX6ox.mjs";
1
+ import { _ as onCleanup, b as untracked, d as effect, f as effectScope, t as isReactive } from "./signals-J8dK_rA4.mjs";
2
2
  import "./polyfill-B1lNNcum.mjs";
3
- import { i as resolveNode$1, n as Slot, r as Slots, t as $slots } from "./slot-Cydy7-0L.mjs";
3
+ import { on } from "./utilities/event-listener.mjs";
4
+ import { i as resolveNode$1, n as Slot, r as Slots, t as SLOTS } from "./slot-Kb61AcgW.mjs";
4
5
  //#region \0rolldown/runtime.js
5
6
  var __defProp = Object.defineProperty;
6
7
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -40,21 +41,21 @@ const ReservedNameSpaces = new Set([
40
41
  //#endregion
41
42
  //#region src/jsx-runtime/children.ts
42
43
  function hasSlots(node) {
43
- return $slots in node;
44
+ return SLOTS in node;
44
45
  }
45
46
  function isChildrenProperty(node, key) {
46
47
  if (key === "children" && (node instanceof Element || node instanceof DocumentFragment)) return true;
47
48
  if (hasSlots(node)) {
48
49
  const slotName = key.replace(/^slot:/, "");
49
- if (Slots.has(node[$slots], slotName)) return true;
50
+ if (Slots.has(node[SLOTS], slotName)) return true;
50
51
  }
51
52
  return key in node && node[key] instanceof Slot;
52
53
  }
53
54
  function applyChildren(node, key, value) {
54
55
  if (hasSlots(node)) {
55
56
  const slotName = key.replace(/^slot:/, "");
56
- if (Slots.has(node[$slots], slotName)) {
57
- applySlot(node[$slots][slotName], value);
57
+ if (Slots.has(node[SLOTS], slotName)) {
58
+ applySlot(node[SLOTS][slotName], value);
58
59
  return;
59
60
  }
60
61
  }
@@ -126,7 +127,7 @@ function applyProps(node, props) {
126
127
  if (isReactive(value)) {
127
128
  if (isEventKey(key)) {
128
129
  effect(() => {
129
- onCleanup(setEvent(node, key, value()));
130
+ on(node, eventName(key), value());
130
131
  });
131
132
  continue;
132
133
  }
@@ -134,7 +135,7 @@ function applyProps(node, props) {
134
135
  continue;
135
136
  }
136
137
  if (isEventKey(key)) {
137
- onCleanup(setEvent(node, key, value));
138
+ on(node, eventName(key), value);
138
139
  continue;
139
140
  }
140
141
  setProp(node, key, value);
@@ -210,10 +211,8 @@ function setAttribute(el, key, value) {
210
211
  function isEventKey(key) {
211
212
  return key.startsWith("on:") || key.length > 2 && key.startsWith("on") && key[2] >= "A" && key[2] <= "Z";
212
213
  }
213
- function setEvent(el, key, handler) {
214
- const event = key.startsWith("on:") ? key.slice(3) : key[2].toLowerCase() + key.slice(3);
215
- el.addEventListener(event, handler);
216
- return () => el.removeEventListener(event, handler);
214
+ function eventName(key) {
215
+ return key.startsWith("on:") ? key.slice(3) : key[2].toLowerCase() + key.slice(3);
217
216
  }
218
217
  //#endregion
219
218
  //#region src/jsx-runtime/element.ts