elements-kit 0.18.2 → 0.20.0

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 (156) hide show
  1. package/README.md +14 -0
  2. package/dist/await.d.mts +53 -0
  3. package/dist/await.mjs +70 -0
  4. package/dist/custom-elements.d.mts +1 -1
  5. package/dist/{element-xQwq4ulc.mjs → element-D3om9yA6.mjs} +71 -4
  6. package/dist/for.d.mts +20 -26
  7. package/dist/for.mjs +29 -4
  8. package/dist/fragment-ROPRBJQd.mjs +61 -0
  9. package/dist/hydrate/index.d.mts +41 -0
  10. package/dist/hydrate/index.mjs +2 -0
  11. package/dist/hydrate-6Qx4n4Su.mjs +380 -0
  12. package/dist/integrations/astro-client.d.mts +17 -0
  13. package/dist/integrations/astro-client.mjs +23 -0
  14. package/dist/integrations/astro-server.d.mts +15 -0
  15. package/dist/integrations/astro-server.mjs +39 -0
  16. package/dist/integrations/astro-slots.d.mts +12 -0
  17. package/dist/integrations/astro-slots.mjs +30 -0
  18. package/dist/integrations/astro.d.mts +46 -0
  19. package/dist/integrations/astro.mjs +64 -0
  20. package/dist/integrations/react.d.mts +1 -1
  21. package/dist/integrations/react.mjs +2 -2
  22. package/dist/jsx-runtime/index.d.mts +1 -1
  23. package/dist/jsx-runtime/index.mjs +2 -20
  24. package/dist/{lib-Dkc7cV5F.mjs → lib-DYypKhxk.mjs} +63 -5
  25. package/dist/render.mjs +1 -1
  26. package/dist/{scope-CnEvL9Nd.mjs → scope-zbEG299F.mjs} +1 -1
  27. package/dist/server/index.d.mts +43 -0
  28. package/dist/server/index.mjs +2 -0
  29. package/dist/server-iFSng50i.mjs +385 -0
  30. package/dist/signals/index.d.mts +2 -2
  31. package/dist/signals/index.mjs +5 -3
  32. package/dist/{slot-B8y0aEoz.d.mts → slot-BFWXeFRl.d.mts} +6 -0
  33. package/dist/{slot-Cne__au7.mjs → slot-Dvf30O0B.mjs} +12 -1
  34. package/dist/slot.d.mts +1 -1
  35. package/dist/slot.mjs +1 -1
  36. package/dist/{infer-B7T5hi_D.d.mts → types-ncUK9rki.d.mts} +140 -141
  37. package/dist/ui/overlay/index.css +70 -0
  38. package/dist/ui/overlay/index.d.mts +209 -0
  39. package/dist/ui/overlay/index.mjs +1001 -0
  40. package/dist/ui/overlay/overlay.css +332 -0
  41. package/dist/utilities/_observe.mjs +1 -1
  42. package/dist/utilities/active-element.d.mts +1 -1
  43. package/dist/utilities/active-element.mjs +1 -1
  44. package/dist/utilities/async.d.mts +19 -2
  45. package/dist/utilities/async.mjs +49 -2
  46. package/dist/utilities/context.mjs +1 -1
  47. package/dist/utilities/debounced.d.mts +1 -1
  48. package/dist/utilities/debounced.mjs +1 -1
  49. package/dist/utilities/dom-lifecycle.bench.mjs +3255 -1
  50. package/dist/utilities/dom-lifecycle.mjs +1 -1
  51. package/dist/utilities/element-rect.d.mts +1 -1
  52. package/dist/utilities/element-rect.mjs +1 -1
  53. package/dist/utilities/element-scroll.d.mts +1 -1
  54. package/dist/utilities/event-driven.d.mts +1 -1
  55. package/dist/utilities/event-driven.mjs +1 -1
  56. package/dist/utilities/event-listener.d.mts +1 -1
  57. package/dist/utilities/event-listener.mjs +1 -1
  58. package/dist/utilities/focus-within.d.mts +1 -1
  59. package/dist/utilities/focus-within.mjs +1 -1
  60. package/dist/utilities/form-object.d.mts +96 -0
  61. package/dist/utilities/form-object.mjs +285 -0
  62. package/dist/utilities/hover.d.mts +1 -1
  63. package/dist/utilities/hover.mjs +1 -1
  64. package/dist/utilities/interval.d.mts +1 -1
  65. package/dist/utilities/interval.mjs +1 -1
  66. package/dist/utilities/location.d.mts +1 -1
  67. package/dist/utilities/location.mjs +1 -1
  68. package/dist/utilities/media-devices.d.mts +1 -1
  69. package/dist/utilities/media-devices.mjs +1 -1
  70. package/dist/utilities/media-player.d.mts +1 -1
  71. package/dist/utilities/media-query.d.mts +1 -1
  72. package/dist/utilities/media-query.mjs +1 -1
  73. package/dist/utilities/network.d.mts +1 -1
  74. package/dist/utilities/network.mjs +1 -1
  75. package/dist/utilities/orientation.d.mts +1 -1
  76. package/dist/utilities/orientation.mjs +1 -1
  77. package/dist/utilities/previous.d.mts +1 -1
  78. package/dist/utilities/previous.mjs +1 -1
  79. package/dist/utilities/promise.d.mts +19 -2
  80. package/dist/utilities/promise.mjs +33 -3
  81. package/dist/utilities/retry.mjs +1 -1
  82. package/dist/utilities/routing.d.mts +1 -1
  83. package/dist/utilities/routing.mjs +1 -1
  84. package/dist/utilities/search-params.d.mts +1 -1
  85. package/dist/utilities/storage.d.mts +1 -1
  86. package/dist/utilities/throttled.d.mts +1 -1
  87. package/dist/utilities/throttled.mjs +1 -1
  88. package/dist/utilities/timeout.d.mts +1 -1
  89. package/dist/utilities/timeout.mjs +1 -1
  90. package/dist/utilities/window-focus.d.mts +1 -1
  91. package/dist/utilities/window-focus.mjs +1 -1
  92. package/dist/utilities/window-size.d.mts +1 -1
  93. package/dist/utilities/window-size.mjs +1 -1
  94. package/package.json +20 -5
  95. package/dist/benchmark.CX_oY03V-CSLAXL5f.mjs +0 -5145
  96. package/dist/magic-string.es-CXefOmkB.mjs +0 -1011
  97. package/dist/test.BmQO5GaM-Ddz9cDxc.mjs +0 -8266
  98. package/dist/utilities/active-element.test.d.mts +0 -1
  99. package/dist/utilities/active-element.test.mjs +0 -25
  100. package/dist/utilities/async.test.d.mts +0 -1
  101. package/dist/utilities/async.test.mjs +0 -275
  102. package/dist/utilities/context.test.d.mts +0 -1
  103. package/dist/utilities/context.test.mjs +0 -171
  104. package/dist/utilities/debounced.test.d.mts +0 -1
  105. package/dist/utilities/debounced.test.mjs +0 -68
  106. package/dist/utilities/dom-lifecycle.test.d.mts +0 -1
  107. package/dist/utilities/dom-lifecycle.test.mjs +0 -228
  108. package/dist/utilities/element-rect.test.d.mts +0 -1
  109. package/dist/utilities/element-rect.test.mjs +0 -101
  110. package/dist/utilities/element-scroll.test.d.mts +0 -1
  111. package/dist/utilities/element-scroll.test.mjs +0 -27
  112. package/dist/utilities/event-listener.test.d.mts +0 -1
  113. package/dist/utilities/event-listener.test.mjs +0 -60
  114. package/dist/utilities/focus-within.test.d.mts +0 -1
  115. package/dist/utilities/focus-within.test.mjs +0 -51
  116. package/dist/utilities/hover.test.d.mts +0 -1
  117. package/dist/utilities/hover.test.mjs +0 -54
  118. package/dist/utilities/intersection-observer.test.d.mts +0 -1
  119. package/dist/utilities/intersection-observer.test.mjs +0 -46
  120. package/dist/utilities/interval.test.d.mts +0 -1
  121. package/dist/utilities/interval.test.mjs +0 -115
  122. package/dist/utilities/location.test.d.mts +0 -1
  123. package/dist/utilities/location.test.mjs +0 -61
  124. package/dist/utilities/long-press.test.d.mts +0 -1
  125. package/dist/utilities/long-press.test.mjs +0 -54
  126. package/dist/utilities/media-devices.test.d.mts +0 -1
  127. package/dist/utilities/media-devices.test.mjs +0 -44
  128. package/dist/utilities/media-player.test.d.mts +0 -1
  129. package/dist/utilities/media-player.test.mjs +0 -102
  130. package/dist/utilities/mutation-observer.test.d.mts +0 -1
  131. package/dist/utilities/mutation-observer.test.mjs +0 -48
  132. package/dist/utilities/network.test.d.mts +0 -1
  133. package/dist/utilities/network.test.mjs +0 -30
  134. package/dist/utilities/on-click-outside.test.d.mts +0 -1
  135. package/dist/utilities/on-click-outside.test.mjs +0 -45
  136. package/dist/utilities/previous.test.d.mts +0 -1
  137. package/dist/utilities/previous.test.mjs +0 -90
  138. package/dist/utilities/promise.test.d.mts +0 -1
  139. package/dist/utilities/promise.test.mjs +0 -158
  140. package/dist/utilities/retry.test.d.mts +0 -1
  141. package/dist/utilities/retry.test.mjs +0 -85
  142. package/dist/utilities/routing.test.d.mts +0 -1
  143. package/dist/utilities/routing.test.mjs +0 -129
  144. package/dist/utilities/search-params.test.d.mts +0 -1
  145. package/dist/utilities/search-params.test.mjs +0 -37
  146. package/dist/utilities/ssr.test.d.mts +0 -1
  147. package/dist/utilities/ssr.test.mjs +0 -65
  148. package/dist/utilities/storage.test.d.mts +0 -1
  149. package/dist/utilities/storage.test.mjs +0 -139
  150. package/dist/utilities/throttled.test.d.mts +0 -1
  151. package/dist/utilities/throttled.test.mjs +0 -58
  152. package/dist/utilities/timeout.test.d.mts +0 -1
  153. package/dist/utilities/timeout.test.mjs +0 -121
  154. package/dist/utilities/window-size.test.d.mts +0 -1
  155. package/dist/utilities/window-size.test.mjs +0 -43
  156. /package/dist/{custom-elements-D5CgfNKc.d.mts → custom-elements-DK5VUcWA.d.mts} +0 -0
package/README.md CHANGED
@@ -67,7 +67,11 @@ Every feature is a separate subpath export — import only what you use.
67
67
  | `elements-kit/custom-elements` | `defineElement`, `CustomElementRegistry` |
68
68
  | `elements-kit/for` | `For` keyed-list component |
69
69
  | `elements-kit/jsx-runtime` | JSX factory + type helpers (`ElementProps`, `Props`, `ComponentProps`, `MaybeReactiveProps`, `ReactiveProps`, `Require`) — configure via `jsxImportSource` |
70
+ | `elements-kit/server` | `renderToStream`, `renderToString` — streaming HTML rendering in any JS runtime (Node, edge/Workers), no DOM required *(experimental)* |
71
+ | `elements-kit/hydrate` | `hydrate(container, () => <App/>)` — adopt server-rendered DOM and make it interactive *(experimental)* |
72
+ | `elements-kit/await` | `Await` — loading boundary (Suspense equivalent); code splitting = `async` + dynamic import *(experimental)* |
70
73
  | `elements-kit/integrations/react` | `useSignal`, `useScope` React bridge hooks |
74
+ | `elements-kit/integrations/astro` | `elementsKit()` Astro integration — elements-kit components as server-rendered, hydrated islands *(experimental)* |
71
75
  | `elements-kit/utilities/*` | Reactive browser-API utilities — see [src/utilities/README.md](src/utilities/README.md) |
72
76
 
73
77
  ## Signals
@@ -488,6 +492,16 @@ class XPicker extends HTMLElement {
488
492
  // ElementProps<typeof XPicker> now includes `on:commit`
489
493
  ```
490
494
 
495
+ ## Roadmap
496
+
497
+ Directions under consideration for the SSR/islands layer (nothing here is committed):
498
+
499
+ - **Declarative Shadow DOM & custom-element SSR** — emit `<template shadowrootmode>` on the server and claim shadow roots at hydration, so elements-kit custom elements server-render inside *any* framework's HTML.
500
+ - **`ErrorBoundary` + `Await` error fallback** — first-class rejection handling; today a rejected awaitable settles into its reason.
501
+ - **Out-of-order streaming + richer `ek-data` serialization** — stream fallbacks immediately and swap content in when ready (today `Await` holds the stream); serialize Dates/Maps/cyclic values instead of JSON-only.
502
+ - **Astro extras** — View Transitions survival, a custom `client:interaction` directive, a live `server:defer` demo.
503
+ - **Hardening toward stable** — real-Astro end-to-end tests in CI (dev + build/preview), an Astro renderer-contract type-pin, SSR↔client parity and escaping property tests, seeding-alignment and stream-robustness suites. The `server` / `hydrate` / `await` / `integrations/astro` entries drop their *experimental* badges when this lands.
504
+
491
505
  ## Learn more
492
506
 
493
507
  - [Documentation site](docs/) — guides, playgrounds, reference
@@ -0,0 +1,53 @@
1
+ import { t as Child } from "./types-ncUK9rki.mjs";
2
+
3
+ //#region src/await.d.ts
4
+ /** @internal Metadata stamped on an Await region getter (see claim walk). */
5
+ interface AsyncRegionMeta {
6
+ /** ek-data ids the server consumed for this region. */
7
+ ids: number;
8
+ /** True while any of the region's awaitables is pending. */
9
+ pending(): boolean;
10
+ }
11
+ /**
12
+ * Loading boundary for async children — elements-kit's `Suspense` equivalent.
13
+ *
14
+ * Direct async children (`promise`/`async` values — including the
15
+ * code-splitting pattern `async(() => import("./chart"))`) are detected
16
+ * automatically; pass `when` to gate on arbitrary awaitables instead. While
17
+ * anything is pending the client renders `fallback`; once everything settles
18
+ * the children show. The server never renders the fallback — the stream
19
+ * awaits and emits the real content. During hydration the server content
20
+ * stays visible while the region is pending (no fallback flash).
21
+ *
22
+ * Prefer a thunk fallback (`fallback={() => <Spinner/>}`) so it renders
23
+ * fresh each time the boundary re-enters a pending state.
24
+ *
25
+ * @example Code splitting with `async` + dynamic import:
26
+ * ```tsx
27
+ * const chart = async(() => import("./chart").then((m) => m.default));
28
+ *
29
+ * function Panel() {
30
+ * chart.run(); // server: awaited · hydration: deferred · client: import
31
+ * return (
32
+ * <Await fallback={() => <em>loading…</em>}>{chart}</Await>
33
+ * );
34
+ * }
35
+ * ```
36
+ *
37
+ * @example Passing props to a code-split component:
38
+ * ```tsx
39
+ * const chart = async(() => import("./chart").then((m) => m.default));
40
+ * chart.run();
41
+ *
42
+ * <Await fallback={() => <em>loading…</em>}>
43
+ * {promise(chart.then((C) => () => <C data={data} />))}
44
+ * </Await>
45
+ * ```
46
+ */
47
+ declare function Await(props: {
48
+ fallback?: Child | (() => Child);
49
+ when?: unknown;
50
+ children?: Child;
51
+ }): Element;
52
+ //#endregion
53
+ export { AsyncRegionMeta, Await };
package/dist/await.mjs ADDED
@@ -0,0 +1,70 @@
1
+ import { f as effectsInert, t as ASYNC_REGION } from "./lib-DYypKhxk.mjs";
2
+ import { isReactive } from "./signals/index.mjs";
3
+ import { isReactivePromiseLike, promise } from "./utilities/promise.mjs";
4
+ import { isAsyncLike } from "./utilities/async.mjs";
5
+ //#region src/await.ts
6
+ function isThenable(value) {
7
+ return value != null && (typeof value === "object" || typeof value === "function") && typeof value.then === "function";
8
+ }
9
+ function toAwaitables(value) {
10
+ const list = Array.isArray(value) ? value.flat(Infinity) : [value];
11
+ const out = [];
12
+ for (const item of list) if (isReactivePromiseLike(item) || isAsyncLike(item)) out.push(item);
13
+ else if (isThenable(item)) out.push(promise(item));
14
+ return out;
15
+ }
16
+ /**
17
+ * Loading boundary for async children — elements-kit's `Suspense` equivalent.
18
+ *
19
+ * Direct async children (`promise`/`async` values — including the
20
+ * code-splitting pattern `async(() => import("./chart"))`) are detected
21
+ * automatically; pass `when` to gate on arbitrary awaitables instead. While
22
+ * anything is pending the client renders `fallback`; once everything settles
23
+ * the children show. The server never renders the fallback — the stream
24
+ * awaits and emits the real content. During hydration the server content
25
+ * stays visible while the region is pending (no fallback flash).
26
+ *
27
+ * Prefer a thunk fallback (`fallback={() => <Spinner/>}`) so it renders
28
+ * fresh each time the boundary re-enters a pending state.
29
+ *
30
+ * @example Code splitting with `async` + dynamic import:
31
+ * ```tsx
32
+ * const chart = async(() => import("./chart").then((m) => m.default));
33
+ *
34
+ * function Panel() {
35
+ * chart.run(); // server: awaited · hydration: deferred · client: import
36
+ * return (
37
+ * <Await fallback={() => <em>loading…</em>}>{chart}</Await>
38
+ * );
39
+ * }
40
+ * ```
41
+ *
42
+ * @example Passing props to a code-split component:
43
+ * ```tsx
44
+ * const chart = async(() => import("./chart").then((m) => m.default));
45
+ * chart.run();
46
+ *
47
+ * <Await fallback={() => <em>loading…</em>}>
48
+ * {promise(chart.then((C) => () => <C data={data} />))}
49
+ * </Await>
50
+ * ```
51
+ */
52
+ function Await(props) {
53
+ const read = (value) => typeof value === "function" && !isReactive(value) ? value() : value;
54
+ const children = read(props.children);
55
+ const when = read(props.when);
56
+ const fromChildren = toAwaitables(children);
57
+ const awaitables = when != null ? toAwaitables(when) : fromChildren;
58
+ if (awaitables.length === 0) return children;
59
+ if (effectsInert()) return promise(Promise.all(awaitables).then(() => () => children));
60
+ const fallback = props.fallback;
61
+ const pending = () => awaitables.some((a) => a.state === "pending");
62
+ const region = () => pending() ? read(fallback) : children;
63
+ region[ASYNC_REGION] = {
64
+ ids: 1 + fromChildren.length,
65
+ pending
66
+ };
67
+ return region;
68
+ }
69
+ //#endregion
70
+ export { Await };
@@ -1,2 +1,2 @@
1
- import { n as defineElement, t as CustomElementRegistry } from "./custom-elements-D5CgfNKc.mjs";
1
+ import { n as defineElement, t as CustomElementRegistry } from "./custom-elements-DK5VUcWA.mjs";
2
2
  export { CustomElementRegistry, defineElement };
@@ -1,8 +1,8 @@
1
- import { c as effectScope, g as untracked, p as onCleanup, s as effect } from "./lib-Dkc7cV5F.mjs";
1
+ import { C as untracked, _ as onCleanup, d as effectScope, u as effect } from "./lib-DYypKhxk.mjs";
2
2
  import "./polyfill-CdZVCxdo.mjs";
3
3
  import { isReactive, resolveProps } from "./signals/index.mjs";
4
4
  import { on } from "./utilities/event-listener.mjs";
5
- import { n as Slot, r as resolveNode$1, t as SLOTS } from "./slot-Cne__au7.mjs";
5
+ import { n as Slot, r as resolveNode$1, t as SLOTS } from "./slot-Dvf30O0B.mjs";
6
6
  //#region src/jsx-runtime/constants.ts
7
7
  /**
8
8
  * IDL properties that take precedence over the default `setAttribute` path.
@@ -80,6 +80,59 @@ const Properties = new Set([
80
80
  "sharedstoragewritable",
81
81
  "defaultValue"
82
82
  ]);
83
+ /**
84
+ * Content attributes with boolean semantics: presence means true. Emitters
85
+ * write them bare when the value is truthy and omit them entirely when falsy
86
+ * — never `disabled="0"`. Synced from `dom-expressions/src/constants.js`.
87
+ */
88
+ const BooleanAttributes = new Set([
89
+ "allowfullscreen",
90
+ "async",
91
+ "alpha",
92
+ "autofocus",
93
+ "autoplay",
94
+ "checked",
95
+ "controls",
96
+ "default",
97
+ "disabled",
98
+ "formnovalidate",
99
+ "hidden",
100
+ "indeterminate",
101
+ "inert",
102
+ "ismap",
103
+ "loop",
104
+ "multiple",
105
+ "muted",
106
+ "nomodule",
107
+ "novalidate",
108
+ "open",
109
+ "playsinline",
110
+ "readonly",
111
+ "required",
112
+ "reversed",
113
+ "seamless",
114
+ "selected",
115
+ "adauctionheaders",
116
+ "browsingtopics",
117
+ "credentialless",
118
+ "defaultchecked",
119
+ "defaultmuted",
120
+ "defaultselected",
121
+ "defer",
122
+ "disablepictureinpicture",
123
+ "disableremoteplayback",
124
+ "preservespitch",
125
+ "shadowrootclonable",
126
+ "shadowrootcustomelementregistry",
127
+ "shadowrootdelegatesfocus",
128
+ "shadowrootserializable",
129
+ "sharedstoragewritable"
130
+ ]);
131
+ /** React-compat property→attribute aliases (dom-expressions `Aliases`). */
132
+ const AttrAliases = {
133
+ className: "class",
134
+ htmlFor: "for"
135
+ };
83
136
  /** Properties whose assignment replaces the element's children. */
84
137
  const ChildProperties = new Set([
85
138
  "innerHTML",
@@ -460,6 +513,17 @@ function setAttribute(el, key, value) {
460
513
  el.setAttribute(key, next);
461
514
  }
462
515
  //#endregion
516
+ //#region src/jsx-runtime/renderer.ts
517
+ const ACTIVE_RENDERER = Symbol.for("elements-kit.active-renderer");
518
+ const slot = globalThis;
519
+ /** Install or clear (`null`) the active renderer. */
520
+ function setRenderer(renderer) {
521
+ slot[ACTIVE_RENDERER] = renderer;
522
+ }
523
+ function getRenderer() {
524
+ return slot[ACTIVE_RENDERER] ?? null;
525
+ }
526
+ //#endregion
463
527
  //#region src/jsx-runtime/dispose.ts
464
528
  const DISPOSABLES = /* @__PURE__ */ new WeakMap();
465
529
  const sharedDispose = function() {
@@ -483,7 +547,10 @@ function attachDisposables(target, disposers) {
483
547
  }
484
548
  //#endregion
485
549
  //#region src/jsx-runtime/element.ts
486
- function createElement(type, { ref, ...props } = {}) {
550
+ function createElement(type, allProps = {}) {
551
+ const renderer = getRenderer();
552
+ if (renderer) return renderer.jsx(type, allProps);
553
+ const { ref, ...props } = allProps;
487
554
  if (typeof type === "function" && !type.prototype?.render) return createFunctionElement(type, props, ref);
488
555
  return createNodeElement(type, props, ref);
489
556
  }
@@ -537,4 +604,4 @@ function renderNode(node) {
537
604
  return renderNode(node.render());
538
605
  }
539
606
  //#endregion
540
- export { disposeElement as n, mountChild as r, createElement as t };
607
+ export { mountChild as a, BooleanAttributes as c, applyProps as i, ChildProperties as l, disposeElement as n, resolveChild as o, setRenderer as r, AttrAliases as s, createElement as t, Properties as u };
package/dist/for.d.mts CHANGED
@@ -1,31 +1,16 @@
1
- import { i as Props, s as Require } from "./infer-B7T5hi_D.mjs";
1
+ import { _ as Require, m as Props } from "./types-ncUK9rki.mjs";
2
2
 
3
3
  //#region src/for.d.ts
4
4
  type KeyFn<T> = (item: T, index: number) => string | number;
5
5
  type RenderFn<T> = (item: T, index: number) => Element | DocumentFragment | null;
6
- /**
7
- * Keyed list renderer. Reconciles a reactive array into the DOM using a key
8
- * function to match existing nodes — minimising create/destroy churn.
9
- *
10
- * Reconciliation strategy (inspired by udomdiff / dom-expressions):
11
- * 1. Remove stale entries (keys absent from the new array).
12
- * 2. Skip unchanged common prefix and suffix.
13
- * 3. Pure-append fast path when the surviving old range is empty.
14
- * 4. Backward scan of the middle region — move or create entries so that
15
- * each entry's range lands immediately before the already-correct cursor.
16
- *
17
- * @example
18
- * ```tsx
19
- * <For each={visibleTodos} by={(todo) => todo.id}>
20
- * {(todo) => <li>{todo.text}</li>}
21
- * </For>
22
- * ```
23
- *
24
- * Props:
25
- * each — reactive getter returning the array (e.g. a `computed`)
26
- * by — extracts a stable key per item (default: index)
27
- * children — render function called once per new key
28
- */
6
+ interface Entry {
7
+ /** Marks the start of this item's DOM range. */
8
+ start: Comment;
9
+ /** Marks the end of this item's DOM range. */
10
+ end: Comment;
11
+ /** Disposes effects/onCleanup registered by the render callback for this item. */
12
+ dispose: () => void;
13
+ }
29
14
  /**
30
15
  * Props for `<For>`, derived from its public instance fields.
31
16
  * All props are optional — the class initializes sane defaults at runtime.
@@ -65,10 +50,19 @@ declare class For<T = unknown> {
65
50
  #private;
66
51
  constructor(_props?: ForProps<T>);
67
52
  get each(): T[];
68
- set each(v: T[]);
53
+ set each(v: T[] | (() => T[]));
69
54
  by: KeyFn<T>;
70
55
  children: RenderFn<T>;
71
56
  render(): DocumentFragment;
57
+ /**
58
+ * @internal Hydrate support: adopt server-rendered range markers and
59
+ * pre-claimed entries instead of creating a fresh range. The first
60
+ * reconcile run sees matching key order and performs no DOM operations;
61
+ * later `each` changes reconcile against the adopted entries as usual.
62
+ */
63
+ hydrateRange(start: Comment, end: Comment, entries: ReadonlyMap<string | number, Entry>, order: ReadonlyArray<string | number>): void;
72
64
  }
65
+ /** @internal Cross-instance-safe For check (identity or brand). */
66
+ declare function isForComponent(type: unknown): boolean;
73
67
  //#endregion
74
- export { For };
68
+ export { Entry, For, isForComponent };
package/dist/for.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { c as effectScope, g as untracked, h as trigger, m as signal, p as onCleanup, s as effect } from "./lib-Dkc7cV5F.mjs";
1
+ import { C as untracked, S as trigger, _ as onCleanup, d as effectScope, u as effect, x as signal } from "./lib-DYypKhxk.mjs";
2
2
  import "./signals/index.mjs";
3
- import { n as disposeElement } from "./element-xQwq4ulc.mjs";
3
+ import { n as disposeElement } from "./element-D3om9yA6.mjs";
4
4
  //#region src/for.ts
5
5
  /**
6
6
  * Keyed list renderer. See {@link ForProps} for prop details.
@@ -21,7 +21,8 @@ var For = class {
21
21
  constructor(_props) {}
22
22
  #each = signal([]);
23
23
  get each() {
24
- return this.#each();
24
+ const v = this.#each();
25
+ return typeof v === "function" ? v() : v;
25
26
  }
26
27
  set each(v) {
27
28
  if (v === untracked(this.#each)) {
@@ -49,6 +50,24 @@ var For = class {
49
50
  });
50
51
  return fragment;
51
52
  }
53
+ /**
54
+ * @internal Hydrate support: adopt server-rendered range markers and
55
+ * pre-claimed entries instead of creating a fresh range. The first
56
+ * reconcile run sees matching key order and performs no DOM operations;
57
+ * later `each` changes reconcile against the adopted entries as usual.
58
+ */
59
+ hydrateRange(start, end, entries, order) {
60
+ this.#start = start;
61
+ this.#end = end;
62
+ for (const [key, entry] of entries) this.#cache.set(key, entry);
63
+ this.#order = [...order];
64
+ effect(() => this.#reconcile());
65
+ onCleanup(() => {
66
+ for (const entry of this.#cache.values()) cleanEntry(entry);
67
+ this.#cache.clear();
68
+ this.#order = [];
69
+ });
70
+ }
52
71
  #reconcile() {
53
72
  const parent = this.#start.parentNode;
54
73
  if (!parent) return;
@@ -148,5 +167,11 @@ function moveEntry(parent, entry, before) {
148
167
  range.setEndAfter(entry.end);
149
168
  parent.insertBefore(range.extractContents(), before);
150
169
  }
170
+ const FOR_BRAND = Symbol.for("elements-kit.for");
171
+ For[FOR_BRAND] = true;
172
+ /** @internal Cross-instance-safe For check (identity or brand). */
173
+ function isForComponent(type) {
174
+ return type === For || typeof type === "function" && type[FOR_BRAND] === true;
175
+ }
151
176
  //#endregion
152
- export { For };
177
+ export { For, isForComponent };
@@ -0,0 +1,61 @@
1
+ import { _ as onCleanup, u as effect } from "./lib-DYypKhxk.mjs";
2
+ import "./signals/index.mjs";
3
+ import { a as mountChild } from "./element-D3om9yA6.mjs";
4
+ import { n as Slot } from "./slot-Dvf30O0B.mjs";
5
+ //#region src/jsx-runtime/fragment.ts
6
+ /**
7
+ * Parse an HTML string script-inertly: markup renders, `<script>` tags are
8
+ * created but never execute (template parsing sets the "already started"
9
+ * flag). Sanitizing the input is the caller's responsibility.
10
+ */
11
+ function parseHtml(html) {
12
+ const template = document.createElement("template");
13
+ template.innerHTML = html;
14
+ return template.content;
15
+ }
16
+ /**
17
+ * Used by the JSX transform for `<>...</>` fragments.
18
+ *
19
+ * Each child is routed through `mountChild`, which handles Nodes, strings,
20
+ * numbers, arrays, and reactive getters — matching the behavior of any other
21
+ * JSX container. `mountChild` also wires each child's cleanup via its own
22
+ * `effectScope`, which links to the enclosing `effectScope` created by
23
+ * `createElement(Fragment, ...)` for disposal propagation.
24
+ *
25
+ * **Raw HTML mode** — `<Fragment html>{markup}</Fragment>`: the child is a
26
+ * `MaybeReactive<string>` rendered as markup inside a Slot region (comment
27
+ * markers), so the server renderer and the hydration claim pass share the
28
+ * region boundary. Reactive sources re-render the region on change. This is
29
+ * the library's only raw-HTML sink: the string is NOT escaped — sanitize
30
+ * untrusted input at the call site. `<script>` tags never execute.
31
+ */
32
+ function Fragment(props) {
33
+ const fragment = document.createDocumentFragment();
34
+ if ("html" in props && props.html) {
35
+ const slot = new Slot();
36
+ fragment.appendChild(slot.render());
37
+ const source = props.children;
38
+ if (typeof source === "function") {
39
+ effect(() => {
40
+ const value = source();
41
+ slot.set(parseHtml(value == null ? "" : String(value)));
42
+ });
43
+ onCleanup(() => slot.clear());
44
+ } else if (source != null) slot.set(parseHtml(String(source)));
45
+ return fragment;
46
+ }
47
+ const children = props.children;
48
+ const raw = typeof children === "function" ? children() : children;
49
+ if (raw == null) return fragment;
50
+ const nodes = Array.isArray(raw) ? raw.flat(Infinity) : [raw];
51
+ for (const child of nodes) mountChild(fragment, child);
52
+ return fragment;
53
+ }
54
+ const FRAGMENT_BRAND = Symbol.for("elements-kit.fragment");
55
+ Fragment[FRAGMENT_BRAND] = true;
56
+ /** @internal Cross-instance-safe Fragment check (identity or brand). */
57
+ function isFragmentComponent(type) {
58
+ return type === Fragment || typeof type === "function" && type[FRAGMENT_BRAND] === true;
59
+ }
60
+ //#endregion
61
+ export { isFragmentComponent as n, parseHtml as r, Fragment as t };
@@ -0,0 +1,41 @@
1
+ //#region src/hydrate/claim.d.ts
2
+ interface MismatchInfo {
3
+ expected: string;
4
+ found: Node | null;
5
+ }
6
+ type OnMismatch = (info: MismatchInfo) => void;
7
+ //#endregion
8
+ //#region src/hydrate/index.d.ts
9
+ interface HydrateOptions {
10
+ /**
11
+ * Called when the server DOM does not match the component tree. The
12
+ * mismatched subtree is discarded and rendered fresh; the caller owns
13
+ * logging/reporting.
14
+ */
15
+ onMismatch?: OnMismatch;
16
+ }
17
+ interface HydrateResult {
18
+ /** Tear down every effect, handler and live binding wired by hydration. */
19
+ dispose: () => void;
20
+ }
21
+ /**
22
+ * Make server-rendered HTML interactive by re-executing the component tree
23
+ * in claim mode: existing DOM nodes are adopted instead of rebuilt, `on:`
24
+ * handlers attach to the claimed nodes, and dynamic children bind live to
25
+ * the server-emitted slot markers.
26
+ *
27
+ * The component code runs once on the client (closures are recreated by
28
+ * re-execution — no serialized handlers). Async children keep their
29
+ * server-rendered content visible until the client-side value settles.
30
+ *
31
+ * Server/client trees must match; on mismatch the affected subtree is
32
+ * rendered fresh and `options.onMismatch` is invoked.
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * const { dispose } = hydrate(document.getElementById("app")!, () => <App />);
37
+ * ```
38
+ */
39
+ declare function hydrate(container: Element, app: () => unknown, options?: HydrateOptions): HydrateResult;
40
+ //#endregion
41
+ export { HydrateOptions, HydrateResult, type MismatchInfo, type OnMismatch, hydrate };
@@ -0,0 +1,2 @@
1
+ import { t as hydrate } from "../hydrate-6Qx4n4Su.mjs";
2
+ export { hydrate };